I decided to have a go at automating the management of our Jenkins jobs. Basically most of our projects have similar builds, and I want to keep the job configs standard – they tend to diverge when we manage them via the UI. I’ve previously played with the Jenkins API using HttpBuilder to post XML config files. My DSL doesn’t do anything clever to generate the XML, but it makes the Jenkins config very readable:
jenkins { url = "http://jenkins.url/" username = "jenkins" apiKey = "asdfadsgdsfshgdsfg" buildGroup { name = "Maven Builds" xml { feature = "release/xml/maven-test-config.xml" develop = "release/xml/maven-install-config.xml" } repos { project1 = "ssh://git.repo/project1.git" project2 = "ssh://git.repo/project2.git" } } buildGroup { name = "Grails Builds" xml { feature = "release/xml/grails-test-config.xml" develop = "release/xml/grails-install-config.xml" } repos { project4 = "ssh://git.repo/project4.git" project5 = "ssh://git.repo/project5.git" } } }
I think this is so easy to read.
I’m not really sure if I’ve gone the right way about writing this, but here goes:
First, I have a main method, which parses the file. It binds the “jenkins” method to an instance of my JenkinsDsl class.
Binding binding = new Binding(); JenkinsDsl jenkinsDsl = new JenkinsDsl(); binding.setVariable("jenkins",jenkinsDsl); GroovyShell shell = new GroovyShell(binding); Script script = shell.parse(file); script.run();
The JenkinsDsl class overrides the `call` method, and uses `methodMissing` to define how we handle the buildGroups. The really cool bit is I didn’t have to write anything special to get the url, username and password from the jenkins config file – groovy is automatically calling a ‘setProperty’ method on the delegate.
class JenkinsDsl { def call(Closure cl) { log.info "Processing Main Configuration" cl.setDelegate(this); cl.setResolveStrategy(Closure.DELEGATE_ONLY) cl.call(); } def url def apiKey def username def jenkinsHttp def methodMissing(String methodName, args) { if (methodName.equals("buildGroup")) { Closure closure = args[0] closure.setDelegate(new JenkinsBuildGroup()) closure.setResolveStrategy(Closure.DELEGATE_ONLY) closure() closure.delegate.updateJenkins(getJenkinsHttp()) } else { log.error "Unsupported option: " + methodName } } def getJenkinsHttp() { if (!jenkinsHttp) { jenkinsHttp = new JenkinsHttp(url, username, apiKey) } jenkinsHttp } }
The JenkinsBuildGroup class is very similar – to save time I’ve used the ‘Expando’ class to collect the xml and repository details.
class JenkinsBuildGroup { def name def xml def repoList /** * Use methodMissing to load the xml and repos closures * @param methodName * @param args */ def methodMissing(String methodName, args) { if (methodName.equals("xml")) { xml = new Expando() Closure closure = args[0] closure.setDelegate(xml) closure.setResolveStrategy(Closure.DELEGATE_ONLY) closure() } else if (methodName.equals("repos")) { repoList = new Expando() Closure closure = args[0] closure.setDelegate(repoList) closure.setResolveStrategy(Closure.DELEGATE_ONLY) closure() } else { log.error "Unsupported option: " + methodName } } def updateJenkins(def jenkinsHttp) { xml.getProperties().each { String jobType, String filePath -> def configXml = new File(filePath).text repoList.getProperties().each { String jobName, String repo -> String jobXml = configXml.replaceAll("REPOSITORY_URL", repo) jenkinsHttp.createOrUpdate(jobName + "-" + jobType, jobXml) } } } }
To actually update jenkins, I substitute the correct repository in the template XML file and post it to the Jenkins API. The JenkinsHttp class is just a wrapper for HTTP Builder. It checks if a job exists so that it can correctly create or update.
I’m not sure if this is the right way to go about writing a groovy DSL, but it works! And once I’ve finalised the template XML files, Jenkins is going to be a vision of consistency.
You’re halfway to managing your jobs all in groovy… check out https://github.com/jenkinsci/job-dsl-plugin
We’ve used this to define our jenkins jobs for some time now, and it’s worked very well for us.
Did you consider using the Job DSL plugin for Jenkins (https://wiki.jenkins-ci.org/display/JENKINS/Job+DSL+Plugin)? It’s a groovy based DSL for configuring Jenkins jobs. It could solve the trouble of generating the XML files. There’s been some work on the forums about how externally uploading jobs, like you’re doing, we could use additional thoughts on it.
Thanks for sharing this.
Do you know the Job DSL Plugin for Jenkins?
Thanks for the comments! I’ll definitely take a look at the DSL plugin. I wonder if it supports all configuration options?
On a totally different note from the DSL plugin that others have commented on, your example finally got me to wrap my head around writing DSLs. Thank you very very much for posting this information. Very useful!