how to get the trigger information in Jenkins programmatically - jenkins

I need to add the next build time scheduled in a build email notification after a build in Jenkins.
The trigger can be "Build periodically" or "Poll SCM", or anything with schedule time.
I know the trigger info is in the config.xml file e.g.
<triggers>
<hudson.triggers.SCMTrigger>
<spec>8 */2 * * 1-5</spec>
<ignorePostCommitHooks>false</ignorePostCommitHooks>
</hudson.triggers.SCMTrigger>
</triggers>
and I also know how to get the trigger type and spec with custom scripting from the config.xml file, and calculate the next build time.
I wonder if Jenkins has the API to expose this information out-of-the-box. I have done the search, but not found anything.

I realise you probably no longer need help with this, but I just had to solve the same problem, so here is a script you can use in the Jenkins console to output all trigger configurations:
#!groovy
Jenkins.instance.getAllItems().each { it ->
if (!(it instanceof jenkins.triggers.SCMTriggerItem)) {
return
}
def itTrigger = (jenkins.triggers.SCMTriggerItem)it
def triggers = itTrigger.getSCMTrigger()
println("Job ${it.name}:")
triggers.each { t->
println("\t${t.getSpec()}")
println("\t${t.isIgnorePostCommitHooks()}")
}
}
This will output all your jobs that use SCM configuration, along with their specification (cron-like expression regarding when to run) and whether post-commit hooks are set to be ignored.
You can modify this script to get the data as JSON like this:
#!groovy
import groovy.json.*
def result = [:]
Jenkins.instance.getAllItems().each { it ->
if (!(it instanceof jenkins.triggers.SCMTriggerItem)) {
return
}
def itTrigger = (jenkins.triggers.SCMTriggerItem)it
def triggers = itTrigger.getSCMTrigger()
triggers.each { t->
def builder = new JsonBuilder()
result[it.name] = builder {
spec "${t.getSpec()}"
ignorePostCommitHooks "${t.isIgnorePostCommitHooks()}"
}
}
}
return new JsonBuilder(result).toPrettyString()
And then you can use the Jenkins Script Console web API to get this from an HTTP client.
For example, in curl, you can do this by saving your script as a text file and then running:
curl --data-urlencode "script=$(<./script.groovy)" <YOUR SERVER>/scriptText
If Jenkins is using basic authentication, you can supply that with the -u <USERNAME>:<PASSWORD> argument.
Ultimately, the request will result in something like this:
{
"Build Project 1": {
"spec": "H/30 * * * *",
"ignorePostCommitHooks": "false"
},
"Test Something": {
"spec": "#hourly",
"ignorePostCommitHooks": "false"
},
"Deploy ABC": {
"spec": "H/20 * * * *",
"ignorePostCommitHooks": "false"
}
}
You should be able to tailor these examples to fit your specific use case. It seems you won't need to access this remotely but just from a job, but I also included the remoting part as it might come in handy for someone else.

Related

Is it possible to use build user vars plugin in a Jenkins shared library?

I'm implementing functionality to expose the triggering user for a Jenkins pipeline to our CD system, so I've grabbed the build user vars plugin: https://plugins.jenkins.io/build-user-vars-plugin/
The way the plugin seems to work is that you wrap the code you need the variables exposed to, like so:
wrap([$class: 'BuildUser']) {
userId = env.BUILD_USER_ID
}
I tried this on a general pipeline and just echoed it out, all was well.
I then tried implementing it in our shared library so that this would happen on all calls to CD, and I'm hitting an error.
wrap([$class: 'BuildUser']) {
jobBuildUrl ="${jobBuildUrl}&USER_ID=${env.BUILD_USER_ID}"
}
[2021-08-19T10:20:22.852Z] hudson.remoting.ProxyException: groovy.lang.MissingMethodException: No signature of method: com.company.jenkins.pipelines.BuildManager.wrap() is applicable for argument types: (java.util.LinkedHashMap, org.jenkinsci.plugins.workflow.cps.CpsClosure2) values: [[$class:BuildUser], org.jenkinsci.plugins.workflow.cps.CpsClosure2#1c9a210c]
Is there any way to use this plugin code within a shared library? If so how?
I'm lead to believe not, but I thought it was worth the asking. For reference, there is this outstanding issue: https://issues.jenkins.io/browse/JENKINS-44741
Side note, I'm trying to do this without touching everybody's pipelines themselves. If this is impossible with this plugin, I'll probably just implement my own version of it within the shared lib.
That plugin is hard to use and has many issues, one of them is with shared libraries.
Instead just implement it by yourself, it is relatively easy and allows you much more control over the logic of what is done, error handling and which parameters you return.
You can use something like the following:
/**
* Get the last upstream build that triggered the current build
* #return Build object (org.jenkinsci.plugins.workflow.job.WorkflowRun) representing the upstream build
*/
#NonCPS
def getUpstreamBuild() {
def build = currentBuild.rawBuild
def upstreamCause
while (upstreamCause = build.getCause(hudson.model.Cause$UpstreamCause)) {
build = upstreamCause.upstreamRun
}
return build
}
/**
* Get the properties of the build Cause (the event that triggered the build)
* #param upstream If true (Default) return the cause properties of the last upstream job (If the build was triggered by another job or by a chain of jobs)
* #return Map representing the properties of the user that triggered the current build.
* Contains keys: USER_NAME, USER_ID
*/
#NonCPS
def getUserCauseProperties(Boolean upstream = true) {
def build = upstream ? getUpstreamBuild() : currentBuild.rawBuild
def cause = build.getCause(hudson.model.Cause$UserIdCause)
if (cause) {
return ['USER_NAME': cause.userName, 'USER_ID': cause.userId]
}
println "Job was not started by a user, it was ${build.getCauses()[0].shortDescription}"
return [:]
}
I was also not able to use wrap([$class: 'BuildUser']) into groovy method. Therefore I think I found alternative way which seems to do the trick. You may get the username and userid (which might return email, depends on your setup) by just the following:
def build = currentBuild.rawBuild
def cause = build.getCause(hudson.model.Cause.UserIdCause.class)
def userName = cause.getUserName() // or use getUserId()

Looking for a Jenkins plugin to allow per-branch default parameter values

I have a multi-branch pipeline job set to build by Jenkinsfile every minute if new changes are available from the git repo. I have a step that deploys the artifact to an environment if the branch name is of a certain format. I would like to be able to configure the environment on a per-branch basis without having to edit Jenkinsfile every time I create a new such branch. Here is a rough sketch of my Jenkinsfile:
pipeline {
agent any
parameters {
string(description: "DB name", name: "dbName")
}
stages {
stage("Deploy") {
steps {
deployTo "${params.dbName}"
}
}
}
}
Is there a Jenkins plugin that will let me define a default value for the dbName parameter per branch in the job configuration page? Ideally something like the mock-up below:
The values should be able to be reordered to set priority. The plugin stops checking for matches after the first one. Matching can be exact or regex.
If there isn't such a plugin currently, please point me to the closest open-source one you can think of. I can use it as a basis for coding a custom plugin.
A possible plugin you could use as a starting point for a custom plugin is the Dynamic Parameter Plugin
Here is a workaround :
Using the Jenkins Config File Provider plugin create a config json with parameters defined in it per branch. Example:
{
"develop": {
"dbName": "test_db",
"param2": "value"
},
"master": {
"dbName": "prod_db",
"param2": "value1"
},
"test_branch_1": {
"dbName": "zss_db",
"param2": "value2"
},
"default": {
"dbName": "prod_db",
"param2": "value3"
}
}
In your Jenkinsfile:
final commit_data = checkout(scm)
BRANCH = commit_data['GIT_BRANCH']
configFileProvider([configFile(fileId: '{Your config file id}', variable: 'BRANCH_SETTINGS')]) {
def config = readJSON file:"$BRANCH_SETTINGS"
def branch_config = config."${BRANCH}"
if(branch_config){
echo "using config for branch ${BRANCH}"
}
else{
branch_config = config.default
}
echo branch_config.'dbName'
}
You can then use branch_config.'dbName', branch_config.'param2' etc. You can even set it to a global variable and then use throughout your pipeline.
The config file can easily be edited via the Jenkins UI(Provided by the plugin) to provision for new branches/params in the future. This doesn't need access to any non sandbox methods.
Not really an answer to your question, but possibly a workaround...
I don't know that the rest of your parameter list looks like, but if it is a static list, you could potentially have your static list with a "use Default" option as the first one.
When the job is run, if the value is "use Default", then gather the default from a file stored in the SCM branch and use that.

Providing different values in Jenkins dsl configure block to create different jobs

I need my builds to timeout at a specific time (deadline) but currently Jenkins dsl only support the "absolute" strategy. So I tried to write the configure block but couldn't create jobs with different deadline values.
def settings = [
[
jobname: 'job1',
ddl: '13:10:00'
],
[
jobname: 'job2',
ddl: '14:05:00'
]
]
for (i in settings) {
job(i.jobname) {
configure {
it / buildWrappers << 'hudson.plugins.build__timeout.BuildTimeoutWrapper' {
strategy(class:'hudson.plugins.build_timeout.impl.DeadlineTimeOutStrategy') {
deadlineTime(i.ddl)
deadlineToleranceInMinutes(1)
}
}
}
steps {
// some stuff to do here
}
}
}
The above script gives me two jobs with the same deadline time(14:05:00):
<project>
<actions></actions>
<description></description>
<keepDependencies>false</keepDependencies>
<properties></properties>
<scm class='hudson.scm.NullSCM'></scm>
<canRoam>true</canRoam>
<disabled>false</disabled>
<blockBuildWhenDownstreamBuilding>false</blockBuildWhenDownstreamBuilding>
<blockBuildWhenUpstreamBuilding>false</blockBuildWhenUpstreamBuilding>
<triggers></triggers>
<concurrentBuild>false</concurrentBuild>
<builders></builders>
<publishers></publishers>
<buildWrappers>
<hudson.plugins.build__timeout.BuildTimeoutWrapper>
<strategy class='hudson.plugins.build_timeout.impl.DeadlineTimeOutStrategy'>
<deadlineTime>14:05:00</deadlineTime>
<deadlineToleranceInMinutes>1</deadlineToleranceInMinutes>
</strategy>
</hudson.plugins.build__timeout.BuildTimeoutWrapper>
</buildWrappers>
</project>
I found this question but still couldn't get it to work.
You can use the Automatic Generated API
The generated DSL is only supported when running in Jenkins, e.g. it is
not available when running from the command line or in the Playground.
Use The Configure Block to generate custom config elements when not
running in Jenkins.
The generated DSL will not work for all plugins, e.g. if a plugin does
not use the #DataBoundConstructor and #DataBoundSetter annotations to
declare parameters. In that case The Configure Block can be used to
generate the config XML.
Fortunately the Timeout plugin support DataBoundConstructors
#DataBoundConstructor
public DeadlineTimeOutStrategy(String deadlineTime, int deadlineToleranceInMinutes) {
this.deadlineTime = deadlineTime;
this.deadlineToleranceInMinutes = deadlineToleranceInMinutes <= MINIMUM_DEADLINE_TOLERANCE_IN_MINUTES ? MINIMUM_DEADLINE_TOLERANCE_IN_MINUTES
: deadlineToleranceInMinutes;
}
So you should be able to do something like
def settings = [
[
jobname: 'job1',
ddl: '13:10:00'
],
[
jobname: 'job2',
ddl: '14:05:00'
]
]
for (i in settings) {
job(i.jobname) {
wrappers {
buildTimeoutWrapper {
strategy {
deadlineTimeOutStrategy {
deadlineTime(i.ddl)
deadlineToleranceInMinutes(1)
}
}
timeoutEnvVar('WHAT_IS_THIS_FOR')
}
}
steps {
// some stuff to do here
}
}
}
There is an extra layer in BuildTimeoutWrapper which houses the different strategies
When using nested classes you need to set the first letter of the class to lowercase
EDIT
You can see this in your own Jenkins install by using the 'Job DSL API Reference' link in a jobs page
http://<your jenkins>/plugin/job-dsl/api-viewer/index.html#method/javaposse.jobdsl.dsl.helpers.wrapper.WrapperContext.buildTimeoutWrapper
I saw very similar behaviour to this in a Jenkins DSL groovy script.
I was looping over a List of Maps in a for each, and I also have a configure closure like your example.
The behaviour I saw was that the Map object in the configure closure seemed to be the same for all iterations of the for each loop. Similar to how you are seeing the same deadline time.
I was actually referencing the same value in the Map both inside and outside the configure closure and the DSL was outputting different values. Outside the configure closure was as expected, but inside was the same value for all iterations.
My solution was just to use a variable to reference the Map value and use that both inside and outside the configure closure, when I did that, the value was consistent.
For your example (just adding a deadlineValue variable, and setting it outside the configure closure):
for (i in settings) {
def deadlineValue = i.ddl
job(i.jobname) {
configure {
it / buildWrappers << 'hudson.plugins.build__timeout.BuildTimeoutWrapper' {
strategy(class:'hudson.plugins.build_timeout.impl.DeadlineTimeOutStrategy') {
deadlineTime(deadlineValue)
deadlineToleranceInMinutes(1)
}
}
}
steps {
// some stuff to do here
}
}
}
I would not expect this to make a difference, but it worked for me.
However I agree as per the the other solution it is better to use buildTimeoutWrapper, so you can avoid using the configure block.
See: <Your Jenkins URL>/plugin/job-dsl/api-viewer/index.html#path/javaposse.jobdsl.dsl.DslFactory.job-wrappers-buildTimeoutWrapper-strategy-deadlineTimeOutStrategy for more details, obviously you'll need the Build Timeout plugin installed.
For my example I still needed the configure closure for the MultiJob plugin where some parameters were still not configurable via the DSL api.

Can a Job DSL script be tested

Ideally I'd like to be able to invoke the script with some kind of unit test before I have it execute on a Jenkins.
Is there any way to test a Job DSL script other than having jenkins run it?
Besides the examples in job-dsl-gradle-example, you can also go a step further and write tests for individual files or jobs. For example let's assume you have a job configuration file located in jobs/deployJob.groovy
import javaposse.jobdsl.dsl.DslScriptLoader
import javaposse.jobdsl.dsl.MemoryJobManagement
import javaposse.jobdsl.dsl.ScriptRequest
import spock.lang.Specification
class TestDeployJobs extends Specification {
def 'test basic job configuration'() {
given:
URL scriptURL = new File('jobs').toURI().toURL()
ScriptRequest scriptRequest = new ScriptRequest('deployJob.groovy', null, scriptURL)
MemoryJobManagement jobManagement = new MemoryJobManagement()
when:
DslScriptLoader.runDslEngine(scriptRequest, jobManagement)
then:
jobManagement.savedConfigs.each { String name, String xml ->
with(new XmlParser().parse(new StringReader(xml))) {
// Make sure jobs only run manually
triggers.'hudson.triggers.TimerTrigger'.spec.text().isEmpty()
// only deploy every environment once at a time
concurrentBuild.text().equals('false')
// do a workspace cleanup
buildWrappers.'hudson.plugins.ws__cleanup.PreBuildCleanup'
// make sure masked passwords are active
!buildWrappers.'com.michelin.cio.hudson.plugins.maskpasswords.MaskPasswordsBuildWrapper'.isEmpty()
}
}
}
}
This way you are able to go through every XML node you want to make sure to have all the right values set.
Have a look at the job-dsl-gradle-example. The repo contains a test for DSL scripts.
Doing it in the same way as crasp but using Jenkins test harness as explained in Jenkins Unit Test page, which is slower but would work with auto-generated DSL giving syntax errors as explained here.
After setting the code as explained here, you can just do a test like this one:
#Unroll
void 'check descriptions #file.name'(File file) {
given:
JobManagement jobManagement = new JenkinsJobManagement(System.out, [:], new File('.'))
Jenkins jenkins = jenkinsRule.jenkins
when:
GeneratedItems items = new DslScriptLoader(jobManagement).runScript(file.text)
then:
if (!items.jobs.isEmpty()) {
items.jobs.each { GeneratedJob generatedJob ->
String text = getItemXml(generatedJob, jenkins)
with(new XmlParser().parse(new StringReader(text))) {
// Has some description
!description.text().isEmpty()
}
}
}
where:
file << TestUtil.getJobFiles()
}

Quickly testing Jelly templates in email-ext of Jenkins

The Email-ext of Jenkins allows you to write a Jelly email template. How do you write and test one without triggering a build every time? Basically, I'm looking for a 1 second iteration where I can modify a Jelly script, hit refresh on a browser, and it will automatically render the template based upon a hard-code project and build result.
Open Jenkins script console at _http://server/script/ (Stackoverflow is having issues saving an edit when this is an actual URL).
Enter the following code and replace your-project-name with the name of your project and me#me.com with your email address:
import hudson.model.StreamBuildListener
import hudson.plugins.emailext.ExtendedEmailPublisher
import java.io.ByteArrayOutputStream
def projectName = "your-project-name"
def project = Jenkins.instance.getItem(projectName)
try
{
def testing = Jenkins.instance.copy(project, "$projectName-Testing")
def build = project.lastUnsuccessfulBuild
// see the javadoc for the Job class for other ways to get builds
def baos = new ByteArrayOutputStream()
def listener = new StreamBuildListener(baos)
testing.publishersList.each() { p ->
println(p)
if(p instanceof ExtendedEmailPublisher) {
// modify the properties as necessary here
p.recipientList = 'me#me.com' // set the recipient list while testing
// run the publisher
p.perform((AbstractBuild<?,?>)build, null, listener)
// print out the build log from ExtendedEmailPublisher
println(new String( baos.toByteArray(), "UTF-8" ))
}
}
}
finally
{
if (testing != null)
{
testing.delete()
}
}
SOURCE: https://earl-of-code.com/2013/02/prototyping-and-testing-groovy-email-templates/
There is also an issue that tracks making this easier:
JENKINS-9594 - Should be able to send test e-mail based on previous build
There is now an option to test templates against builds in the more recent versions of the plugin. When you are on a job's screen, there should be a link on the left side that says Email Template Testing. It will let you select a build to test again and it will render the template right there.

Resources