Create Jenkins-Job with Ant and Groovy through authenticated remote API

Friedrich Krause Ufer Berlin, own photo (by Christoph Burmeister)

Friedrich Krause Ufer Berlin, own photo (by Christoph Burmeister)

At work, we drive a build-system based on Apache Ant and Jenkins. At the moment, we create new jobs in Jenkins by automatically putting a predefined config.xml into a new created directory and restarting Jenkins. That’s OK if we have enough time. But Jenkins offers a nice remote API, so I wrote a simple ant-target, calling a groovy-script for job-creation. But why using Groovy? Because Ant simply doesn’t provide the HTTP-Post-based preemtive Authentication, that we need. But Groovy does 🙂

Environment:

  • Apache Ant: 1.8.2
  • Groovy: 2.1.5
  • JRE: Hotspot 1.7.0_17
  • Jenkins: 1.522

So here comes the create-job.xml that is finally called by Ant :

<project name="create-jenkins-job" basedir="." default="create">

	<property name="jenkins.url" value="http://localhost:10000/" />
	<property name="jenkins.job.name" value="testjob" />
	<property name="jenkins.job.config.template" value="./config.xml.template" />
	<property name="jenkins.job.config.file" value="./tmp/config.xml" />
	<property name="jenkins.job.scm.url" value="http://localhost:9000/svn/testrepo/projectxyz/trunk/" />
	<property name="jenkins.admin.username" value="admin" />
	<property name="jenkins.admin.token" value="93ef7473b9619bc0c755k5k3h339aa" />
	<property name="jenkins.security.realm" value="Benutzer Jenkins" />
	
	<taskdef name="groovy" classname="org.codehaus.groovy.ant.Groovy">
		<classpath>
			<pathelement location="./libs/groovy-all-2.1.5.jar"/>
		</classpath>
	</taskdef>

	<target name="prepare" description="copy the config-template and replace the scm-url in the copied config-file">
		<mkdir dir="./tmp" />
		<copy file="${jenkins.job.config.template}" tofile="${jenkins.job.config.file}"/>
		<replace file="${jenkins.job.config.file}" token="@@@scm-url@@@" value="${jenkins.job.scm.url}"/>
	</target>
	
	<target name="create" depends="prepare" description="call the groovy-script with all params">
		<groovy src="./create-job.groovy">
			<classpath>
				<pathelement location="./libs/commons-httpclient-3.1.jar"/>
				<pathelement location="./libs/commons-logging-1.1.1.jar" />
				<pathelement location="./libs/commons-codec-1.8.jar" />
			</classpath>
			<arg line="${jenkins.url} ${jenkins.job.name} ${jenkins.job.config.file} ${jenkins.admin.username} ${jenkins.admin.token} ${jenkins.security.realm}" />
		</groovy>
	</target>

</project>

Here comes the Groovy-file create-job.groovy:

import org.apache.commons.httpclient.*

import org.apache.commons.httpclient.auth.*
import org.apache.commons.httpclient.methods.*
 

def jenkinsUrl = args[0]
def projectName = args[1]
def configurationFile = args[2]
def username = args[3]
def apiToken = args[4]
def realm = args[5]

// get parts of the given string-url
def url = new URL(jenkinsUrl)
def server = url.getHost()
def port = url.getPort()

// create the client
def client = new HttpClient()
client.state.setCredentials(
	new AuthScope( server, port, realm),
    new UsernamePasswordCredentials( username, apiToken )
)

client.params.authenticationPreemptive = true

def post = new PostMethod( "${jenkinsUrl}/createItem?name=${projectName}" )
post.doAuthentication = true

File input = new File(configurationFile);
RequestEntity entity = new FileRequestEntity(input, "text/xml; charset=UTF-8");
post.setRequestEntity(entity);
try {
    int result = client.executeMethod(post)
	if (result != 200) {
		// not nice, but the easiest way
		throw new Exception("http-result-code:" + result);
	}
    println "Return code: ${result}"
    post.responseHeaders.each{ println it.toString().trim() }
	new File("response.html").withWriter{ it << post.getResponseBodyAsString() } 
} finally {
    post.releaseConnection()
}

When running the Ant-file, I put in a simple mechanism to change the scm-url of an existing config.xml.template:

<?xml version='1.0' encoding='UTF-8'?>
<project>
  <actions/>
  <description></description>
  <keepDependencies>false</keepDependencies>
  <properties/>
  <scm class="hudson.scm.SubversionSCM" plugin="subversion@1.45">
    <locations>
      <hudson.scm.SubversionSCM_-ModuleLocation>
        <remote>@@@scm-url@@@</remote>
        <local>.</local>
        <depthOption>infinity</depthOption>
        <ignoreExternalsOption>false</ignoreExternalsOption>
      </hudson.scm.SubversionSCM_-ModuleLocation>
    </locations>
    <excludedRegions></excludedRegions>
    <includedRegions></includedRegions>
    <excludedUsers></excludedUsers>
    <excludedRevprop></excludedRevprop>
    <excludedCommitMessages></excludedCommitMessages>
    <workspaceUpdater class="hudson.scm.subversion.UpdateUpdater"/>
    <ignoreDirPropChanges>false</ignoreDirPropChanges>
    <filterChangelog>false</filterChangelog>
  </scm>
  <canRoam>true</canRoam>
  <disabled>false</disabled>
  <blockBuildWhenDownstreamBuilding>false</blockBuildWhenDownstreamBuilding>
  <blockBuildWhenUpstreamBuilding>false</blockBuildWhenUpstreamBuilding>
  <triggers class="vector"/>
  <concurrentBuild>false</concurrentBuild>
  <builders>
    <hudson.tasks.BatchFile>
      <command>echo &quot;jenkins rocks&quot;</command>
    </hudson.tasks.BatchFile>
  </builders>
  <publishers/>
  <buildWrappers/>
</project>

following JARs are needed unter „./libs/“ for the Groovy-target itself, so you don’t need a fully Groovy-installation:

  • groovy-all-2.1.5.jar

and following JARs are needed unter „./libs/“ for the Groovy-file-execution:

  • commons-codec-1.8.jar
  • commons-httpclient-3.1.jar
  • commons-logging-1.1.1.jar

So calling „ant -f ./create-job.xml“ will output an empty response.html in case of HTTP 200 (Yeah, all right!) or an filled response.html with the specific HTTP-Errorcode.

The most tricky thing was to find out the handling of passwords of Jenkins remote API with the user-based api-token which is located in the webgui directly in the user-configuration for each user and is only applicable for the remote-api.

By the way, a simple jenkins.bat (next to jenkins.war) looks like the following:

set JENKINS_HOME=./
call java -jar jenkins.war --argumentsRealm.passwd.admin=topfsecret --argumentsRealm.roles.admin=admin --httpPort=10000 --logfile=./logs/jenkins.log

References: