Skip to content
Gary Hale edited this page Jan 13, 2014 · 45 revisions

This plugin allows you to maintain jenkins job configurations in source control and apply them to a Jenkins server via gradle. Jobs can be stored as straight xml files, xml strings, or as markup builder closures. Job templates can be defined that can then be manipulated in Groovy XmlSlurper fashion such that multiple jobs can be generated off of a single template definition.

Powerful use of this plugin requires intimate knowledge of a job's config.xml. A standard use case would be to configure a job in Jenkins, download the config.xml and store it in source control. The xml could then be manipulated at runtime to customize the job based on values from the gradle configuration, configure multiple jobs and/or apply them to multiple servers. Additionally, purely programmatic configuration of a job is also possible (ie without the use of stored xml).

Adding the Plugin

    buildscript {
            repositories { mavenCentral() }
            dependencies {
                    classpath('com.terrafolio:gradle-jenkins-plugin:0.4.0')
            }
    }

Examples

This first example shows a very simple case where the jenkins job is stored as an xml file and applied to the server as is.

apply plugin: 'jenkins'

jenkins {
        servers {
                testing {
                        url 'http://jenkins.somewhere.com:8080'
                        secure true         // optional
                        username "testuser" // optional
                        password "testpass" // optional
                }
        }
        
        defaultServer servers.testing // optional
        jobs {
        		test {
        				server servers.testing
        				definition {
        					name "Build ${project.name}" //optional
        					xml file('config.xml')
        				}
        		}
        }
}

The following gradle file demonstrates the use of templates. It uses a single template xml file (downloaded straight from <JOB_URL>/config.xml) and then generates two new jobs - one for the master branch of a git repository and one for the develop branch. For each, it uses a closure to override the template to 1) disable the job, 2) set a custom workspace, 3) set the repository url, and 4) set the branch name in the SCM configuration.

apply plugin: 'jenkins'

jenkins {
        servers {
                testing {
                        url 'http://jenkins.somewhere.com:8080'
                        secure true         // optional
                        username "testuser" // optional
                        password "testpass" // optional
                }
        }

        templates {
                build {
                        xml file('build-template.xml')
                }
        } 

        defaultServer servers.testing // optional 
        jobs {
                [ 'master', 'develop' ].each { branchName ->
                        "build_${branchName}" {
                        		server servers.testing // optional
                                definition {
                                        name "Build ${project.name} (${branchName})"
                                        xml templates.build.override { projectXml ->
                                                projectXml.disabled = 'true'
                                                projectXml.customWorkspace = "/build/${branchName}/${project.name}"
                                                projectXml.scm.userRemoteConfigs.'hudson.plugins.git.UserRemoteConfig'.url = "git@gitserver:${project.name}.git"
                                                projectXml.scm.branches.replaceNode { node ->
                                                        branches() {
                                                                'hudson.plugins.git.BranchSpec'() {
                                                                        name([:], "*/${branchName}")
                                                                }
                                                        }
                                                }
                                        }
                                }
                        }
                }
        }
}

The following example shows the use of server-specific configurations. Note that the closure added to the server directive is the same as added for job.definition.

apply plugin: 'jenkins'

jenkins {
	servers {
		test1 { url 'http://jenkins1.somewhere.com' }
		test2 { url 'http://jenkins2.somewhere.com' }
	}
	
	jobs {
		build {
			server servers.test1, {
				name 'Build for test1' // optional
				xml override { projectXml ->
					projectXml.description = 'This build is for test1'
				}
			}
			
			server servers.test2, {
				name 'Build for test2' // optional
				xml override { projectXml ->
					projectXml.description = 'This build is for test2'
				}
			}
			
			definition {
				name 'Build job'
				xml file('config.xml')
			}
		}
	}
}

Note that server-specific configurations are always applied last. For instance, if you had a job that used a template and then overrode the template, those overrides would be evaluated first (regardless of order in the script) and the server-specific overrides would occur last.

Configuration

The following conventions are added by this plugin:

  • jenkins - The main configuration closure contains servers, templates, jobs and (optionally) a defaultServer definition.
    • servers - Definitions of jenkins servers where jobs can be applied. Each named server can define several fields:

      • url - The url to the jenkins instance.
      • username - The username to use (must have admin privileges). (Optional)
      • password - The password to use. (Optional)
      • secure - Whether or not the server is secured (requiring username and password). If username and password are not defined, and secure is set to true, they will be prompted for on the console. If no console is available, an exception will be thrown. In the event that the secure field is set to false, empty values for username and password are allowed and no prompting will occur. (Optional, default = true)
    • defaultServer - The default server to use when a job does not specify a server. If a defaultServer is not defined, then each job must specify the server it should be applied to. (Optional)

    • templates - Definitions of jobs that can be used as templates for concrete jobs. Each named template defines one field:

      • xml - The config.xml to use as a template for defining jobs. This field can accept a String, a File, or a Groovy MarkupBuilder closure.
    • jobs - Definitions of concrete jobs. Each named job defines several fields:

      • server - The server the job should be applied to and the job definition. If the server is not configured it will use the server defined by defaultServer. Multiple "server" declarations causes the job to be applied to multiple servers. You can also add a closure argument to do server-specific configuration. The closure has the same configuration as the closure for definition below.(Optional)
      • definition - The definition of the actual job. This closure defines the following fields:
        • name - The name of the job on the Jenkins server. This defaults to the name of the job. (Optional)
        • xml - The config.xml that defines the job. This field accepts a String, a File, or a Groovy MarkupBuilder closure. Additionally, if a template is defined, you can use the override method on the template which accepts a closure to manipulate the content. The GPathResult from an XMLSlurper is passed to the closure for manipulation. See http://groovy.codehaus.org/Updating+XML+with+XmlSlurper for info on using XMLSlurper GPathResults to manipulate xml.

Tasks

The plugin applies four tasks to the project.

  • updateJenkinsJobs - Creates or updates jobs on the server(s). This task operates on whatever jobs are specified by the job or server filters. If no filters are applied, it operates on all jobs.
  • deleteJenkinsJobs - Deletes jobs from the server(s). This task operates on whatever jobs are specified by the job or server filters. If no filters are applied, it operates on all jobs.
  • dumpJenkinsJobs - Writes job config.xml files to the "jobs" directory under buildDir ("build" by default). This task operates on whatever jobs are specified by the job or server filters. If no filters are applied, it operates on all jobs. By default, this task dumps xml in a human-friendly form, but this format may not import well into jenkins due to line breaks and other white space (if you attempt to import it manually). Setting "prettyPrint = false" on this task causes the xml to be dumped in a form that should import well, but is decidedly not friendly to humans.
  • retireJenkinsJobs - Deletes jobs specified for retirement. This task only operates on the jobs specified.
  • validateJenkinsJobs - Validates that the jobs on the server match the jobs configured in the gradle configuration. Note that this an xml comparison that checks if the two documents have the same elements, attributes and content. There are a number of situations where the jobs may function exactly the same, but the xml may differ enough for this task to show a difference.

Updating Jobs

As of version 0.4.0, by default, the plugin will attempt to determine if a job is different from the job configuration that would be uploaded. If a job has not been edited in any way, subsequent runs of the update task will skip the update for that job. In general, though, any time a job has been edited in Jenkins after it has been uploaded, Jenkins tends to make a number of changes to the xml that will likely show up as different (even if it is functionally the same) and the plugin will update the job.

This up-to-date check can be overridden by setting the project property forceJenkinsJobsUpdate to 'true'. This can be done by setting the property on the command line (i.e. "-PforceJenkinsJobsUpdate=true") or by setting this as a project property in your build script (i.e. "ext.forceJenkinsJobsUpdate = 'true'"). With this property set, all jobs will be updated regardless of whether they have been changed or not (i.e. pre-0.4.0 behavior).

Validating Jobs

By default, the validateJenkinsJobs task causes a build failure whenever a difference is found. If, however, one wants to run this task for informational purposes, but not have it fail on differences, this behavior can be overridden:

validateJenkinsJobs.failOnDifference = false

At normal log levels, this task only outputs information about any jobs that do not match the configuration. For additional information, including details about each difference as well as jobs that do match the configuration, use the "info" log setting (i.e. the "--info" command line switch).

Retiring Jobs

As your Jenkins jobs evolve, you may find the need to retire large numbers of old jobs. A convenience task (retireJenkinsJobs) is provided to facilitate this programmatically. Example:

	retireJenkinsJobs {
		delete(jenkins.servers.server1, "Build job1")
		delete(jenkins.jobs.job2)
	}

The delete method has two forms. The first accepts a server definition and the name of the job in Jenkins as the arguments. The second form takes a job defined in the jenkins configuration. The first form is most likely to be used as you would probably remove your job from the configuration prior to running this task. Retirement of jobs can be easily hooked into updates by setting a dependency on updateJenkinsJobs.

Note that retireJenkinsJobs is only a convenience task and deleting individual jobs can be done in a more general purpose fashion if necessary by overriding the DeleteJenkinsJobsTask task. Example:

	import com.terrafolio.gradle.plugins.jenkins.DeleteJenkinsJobsTask
	...
	task deleteSomeJobs(type: DeleteJenkinsJobsTask) {
		jenkins.servers.each { server ->
			delete(server, "Some job")
		}
	}

Overriding job xml

The template example above shows how you can override templates to customize the configuration for a job. A job can also override its own xml in order to incrementally customize the xml. This can be useful for configuring jobs in a clean and modular fashion. Example:

	jenkins {
		...
		jobs {
			job1 {
				server servers.test1
				definition {
					name "Build job1"
					xml templates.build.override { projectXml ->
						projectXml.customWorkspace = "/build/${branchName}/${project.name}"
					}
				}
			}
		}
	}
	...
	jenkins.jobs.job1.definition.xml override { projectXml ->
		projectXml.scm.userRemoteConfigs.'hudson.plugins.git.UserRemoteConfig'.url = "git@gitserver:${project.name}.git"
	} 
	...
	jenkins.jobs.findAll { it.name.startsWith("Build" }.each { job ->
		job.definition.xml override { projectXml ->
			projectXml.disabled = 'true'
		}
	}

Overrides are processed in the order they are evaluated during the gradle configuration phase. This makes it possible to have one override operate on the xml generated from a previous override. It also makes it possible to mistakenly produce different xml by re-ordering overrides within the build script.

Filtering by job

You can filter which jobs are operated on by setting the "jenkinsJobFilter" property on the build to a regex matching the job name. The most likely case is that you will want to do this from the command line (e.g. -PjenkinsJobFilter="build.*"). The name the regex matches against is the name of the job in gradle, not the name of the project in Jenkins. For instance:

	jobs {
		build_lib_1 {
			server servers.testing
			definition {
				name "Build Library 1"
				xml file("lib1-config.xml")
			}
		}
		
		build_lib_2 {
			...
		}
		
		build_war_1 {
			...
		}
		
		deploy_war_1 {
			...
		}
	}

If you used -PjenkinsJobFilter='.war_1', it would match jobs build_war_1 and deploy_war_1 and only apply the changes to those jobs in Jenkins. On the other hand, if you specified the regex as 'build_.', it would match build_lib_1, build_lib_2, and build_war_1.

Filtering by Server

You can filter which servers are operated on by setting the "jenkinsServerFilter" property on the build to a regex matching the server name. The most likely case is that you will want to do this from the command line (e.g. -PjenkinsServerFilter="test.*"). For instance:

	servers {
		test_1 {
			...
		}
		
		test_2 {
			...
		}
		
		qa {
			...
		}
		
		prod {
			...
		}
	}

If you used -PjenkinsServerFilter='test.*', it would apply any jobs set up to run against test_1 or test_2.

Changelog

Roadmap

  • Add support for adding views to jenkins
  • Add convenience functions for common job manipulation tasks (e.g. setting parameter values, adding build steps, publishers, etc).

0.4.0 (01/10/14)

  • Issue #5: Check existing jobs for changes before update

0.3.4 (10/17/13)

  • Issue #18: Problem with creating jobs on Jenkins 1.535 (Note that this version is required if you're using 1.535)

0.4.0 (01/10/14)

  • Issue #5: Check jobs for changes before update

0.3.3 (08/19/13)

  • Pull Request #16: Update for jdk7 compatibility

0.3.2 (07/12/13)

  • Issue #15: Make job name convention for job definition name
  • Issue #14: DumpJenkinsJobs does not dump server-specific configurations

0.3.1 (03/22/13)

  • Issue #13: white space in dumped job files cannot be imported
  • Issue #12: config style closure on job definition overwrites existing definition
  • Issue #11: Password prompting occurs for dumpJenkinsJobs
  • Test refactoring and issue #10: adding support for composing update jobs

0.3.0 (03/10/13)

  • Issue 4: Add ability to dump job xml
  • Issue 6: Add support for retiring jobs
  • Issue 7: Move server credential prompting to beginning of task
  • Issue 8: Remove asm dependency from http-builder

0.2.0 (01/25/13)

  • Add support for filtering jobs to operate against (instead of all jobs all the time).
  • Add support for filtering servers to operate against (instead of all servers all the time).
  • Add support for per-server configuration such that slightly different configurations can be deployed to different servers for the same job.

Suggestions, contributions and/or bug reports are welcome. Please log any as a pull request or an issue in github (https://github.com/ghale/gradle-jenkins-plugin/issues).