Bodhisattva in Training

March 16, 2012 Continuous Integration

Switching to Jenkins–Download and Install Artifact Script for Tester

One of the things that I really liked about the version of CCNet that I was using was the ability to publish artifacts for download on the build report web page.  Here is a snapshot of the featured artifacts section of the summary report:


Beyond the nice GUI you could count on the url for the artifacts to conform to a pattern.  For example the weblogic release installer exe can be found at this address:


This enabled me to write a script, Chapter33.Deploy.Release.For.Tester.bat, that would download the http content, database installer, and weblogic installer and install the Chapter33 application in a tester’s private(local) workspace.  Well I am getting a little ahead of myself, the script also read from a CCNet REST interface.  It would gather up the build numbers for the last 6 successful Release Builds and present a GUI to the tester asking them which version of the application they would like to install.

Jenkins offers the analogous features, both artifacts for download and a REST interface, that will enable an analogous script to be written.  First lets look at the REST interface.  You can quickly see the xml from a REST request in you browser by adding /api/xml to any Jenkins page you are on.  So if you were at the following URL(a project dashboard):



Just adding /api/xml to the URL will call the REST interface:



If you want some more info on options for calling the REST API just /api to the URL…  Now the Jenkins xml REST interface is pretty cool in that you can specify the depth of information as well as tune it with an xpath query.  We will need a deeper depth of information as the current depth does not show the result, success or failure, for each build, nor does it give us the build display name.  By adding the query param depth=1 we get:



Finally we just need the build number for the last 6 successful build.  Note on my projects where we set the build display name to the SVN revision number we select the fullDisplayName as opposed to the build number here.  By adding the this xpath query xpath=(/*/build[result=’SUCCESS’])[position()<=6]/number we get the following results: 




Only 5 results are returned here because there are only 5 successful builds retained on this job.  With this REST query we have enough to get started on a Groovy script to download the artifacts.

def protocol = ‘http://’
def serverName = ‘ci.jruby.org’
def port = ”
def jobName = ‘jruby-dist-release’
def resultSetSize = 6

def restEndPointBuildList = "${protocol}${serverName}${port}/job/${jobName}/api/xml?depth=1&xpath=(/*/build[result=’SUCCESS’])[position()<=${resultSetSize}]/number&wrapper=builds"
def slurper = new XmlSlurper()
def buildList = slurper.parse(restEndPointBuildList)

def goodBuilds = new ArrayList<String>()

buildList.number.each {

This code will create an array of the build numbers returned from the REST query.  It uses the XmlSlurper to accomplish this, both downloading the REST result and parsing it so that we can loop over it creating the array of good build numbers.  Next we need to present the user with a UI so that they may choose a build that they wish to download and install.  I did not care about making the UI pretty so I opted to use a basic antforms UI.


def goodBuildsFlat = ”

goodBuilds.sort().each {
    if (goodBuildsFlat == ”){
        goodBuildsFlat = it
        goodBuildsFlat = it + "," + goodBuildsFlat

def ant = new AntBuilder()
    classname: "com.sardak.antform.AntForm",
    classpath: System.getenv(‘ANT_HOME’) + "/lib/antform.jar"

ant.antform(title: "Choose Which Build to Deploy"){
   ant.label("Choose a build")
    ant.radioSelectionProperty(label: "Builds: ", property: "buildChosen", values: "${goodBuildsFlat}")
def buildNumber = ant.project.properties.buildChosen;

The antforms task will accept a comma separated value string of options to make radio buttons out of.  I wanted them to be sorted, that is first bit of code here.  Next we need to load the antforms task.  You can see here that the jar is loaded from the ant lib dir.  You could load it from anywhere…  After the ant task has been loaded we call to display the form passing in several options like a title and labels.  When the user clicks the OK button the ant property buildChosen will contain the value they chose.  This is placed in the local variable buildNumber.

def restEndPointBuild = "${protocol}${serverName}${port}/job/${jobName}/api/xml?depth=1&xpath=/*/build[number=’${buildNumber}’][1]"

def build = slurper.parse(restEndPointBuild)

build.artifact.each {
    def artifactFileName = it.fileName.text()
    def artifactRelativePath = it.relativePath.text()
    def artifactUrl = "${protocol}${serverName}${port}/job/${jobName}/${buildNumber}/artifact/${artifactRelativePath}"
    download(artifactUrl, "C:\\Temp\\${artifactFileName}")

Next the script makes a REST query getting the build details for the build chosen.  It then loops over the artifacts published in that build downloading each.  This is the part of the script that you will need to start tailoring to your needs.  Where do you want to download to?  Do you want to download all the artifacts or just a specified few?  What are you going to do after downloading the artifacts?  On the project that I am working on at the moment we publish full on headless installers.  After downloading these installers we execute them.  This provides a push button script for the testers or anyone on the project to deploy a personal instance of the application any time they wish.  They even get to choose the version they want to install.


Ooh before I forget here is the last bit of the script, the download method.  I used Curl so that if the download fails it will pick back up where it left off we you retry.  This assumes that Curl is in your PATH.

def download(url, destination){
    String command = "-0 -m 86400 -S -C – -o \"${destination}\" -k -L –retry 5 ${url}"

    def ant = new AntBuilder()
    ant.exec(executable: "curl"){
        arg(line: "${command}")

Leave a comment