A Tale of Two Build Tools: Integrating XL Release with Jenkins and Bamboo

| March 24, 2015 | 0 Comments

I discovered that XL Release does not have built-in support to integrate with the Bamboo build tool as it does for Jenkins. But I also discovered that XL Release’s extensibility makes it easy to configure a type definition and a script to enable an interface with Bamboo.

Let’s look at the support for Jenkins. XL Release provides a task definition:

xlr-jenkins-task

and an entry under the configuration tab:

xlr-jenkins-server

With these we can define one or more Jenkins servers and use them to execute the build jobs defined on them. Nice.

Bamboo doesn’t have such out-of-the-box support, so let’s take a look at how we could configure the objects we need.  Since Bamboo has a REST API, we can extend the HttpConnection object to provide us a Bamboo Server object by defining a type in xl-release-server/ext/synthetic.xml:

<type type="bamboo.Server" extends="configuration.HttpConnection" />

Now we need a task to call out to the API; let’s extend the PythonScript object for this so we can take advantage of Python tremendous versatility. The script will actually run under Jython, so we can utilize Java classes too if needed. Our input will be the project-plan key to identify the Bamboo plan, and let’s code a few output fields to return some information about the build after it completes.

<type type="bamboo.RunPlan" extends="xlrelease.PythonScript">
 <property name="bambooServer" category="input" label="Server" referenced-type="bamboo.Server" kind="ci" />
<property name="projPlanKey" category="input" />
<property name="buildNumber" category="output" />
<property name="buildState" category="output" />
<property name="state" category="output" />
</type>

Our next task is to write the Python script to call out to Bamboo. The namespace of the type, “bamboo” in this case, determines the script directory, and the type name determines the script name. So our script will be called RunPlan.py and will live in xl-release-server/ext/bamboo.

The script starts with some typical Python imports. We’ll use com.xhaus.jyson.JysonCodec for json since that’s included in the XL Release libraries. Next we set some variables for contentType and headers for all of our HTTP calls, and define some boolean and text fields from the build result.

Finally the main body of the code make a post request to Bamboo’s url using the built-in HttpRequest object. We supply the url, content type and headers. We could have added authentication here to override what’s defined on the Bamboo Server object, but let’s leave that for later. An empty set of curly braces is required for the JSON content body.

Then we use the JSON library module to parse two items out of the results: the build number and the build result key.

We store the latter in the variable brkey to keep it handy to pass to our helper methods. A simple while loop polls for a finished result every five seconds. That interval would be better as a configurable value; this is something else we’ll save for later.

When the loop ends, we query the job’s results (note the change in the query string from “queue” to “result”), print some messages and set the two state output variables so XL Release can use them to control future actions.  See the xlr-bamboo-plugin repo for future updates to the code.

import sys
import time
import com.xhaus.jyson.JysonCodec as json
print "Executing RunPlan.py\n"
if bambooServer is None:
print "No server provided."
sys.exit(1)
contentType = 'application/json'
headers = {'accept' : 'application/json'}
def finished(brkey):
request = HttpRequest(bambooServer)
response = request.get('/rest/api/latest/result/' + brkey, contentType=contentType, headers=headers)
return json.loads(response.response)['finished']
def successful(brkey):
request = HttpRequest(bambooServer)
response = request.get('/rest/api/latest/result/' + brkey, contentType=contentType, headers=headers)
return json.loads(response.response)['successful']
def getState(brkey):
request = HttpRequest(bambooServer)
response = request.get('/rest/api/latest/result/' + brkey, contentType=contentType, headers=headers)
return json.loads(response.response)['state']
def getBuildState(brkey):
request = HttpRequest(bambooServer)
response = request.get('/rest/api/latest/result/' + brkey, contentType=contentType, headers=headers)
return json.loads(response.response)['buildState']
def getPrettyBuildStartedTime(brkey):
request = HttpRequest(bambooServer)
response = request.get('/rest/api/latest/result/' + brkey, contentType=contentType, headers=headers)
return json.loads(response.response)['prettyBuildStartedTime']
def getPrettyBuildCompletedTime(brkey):
request = HttpRequest(bambooServer)
response = request.get('/rest/api/latest/result/' + brkey, contentType=contentType, headers=headers)
return json.loads(response.response)['prettyBuildCompletedTime']
request = HttpRequest(bambooServer)
response = request.post('/rest/api/latest/queue/' + projPlanKey, '{}', contentType=contentType, headers=headers)
result = json.loads(response.response)
buildNumber = result['buildNumber']
print 'Build number is ' + str(buildNumber) + '\n'
brkey = result['buildResultKey']
while (not finished(brkey)):
time.sleep(5)
print "Build job started at " + getPrettyBuildStartedTime(brkey) + "\n"
prettyBuildCompletedTime = getPrettyBuildCompletedTime(brkey)
if successful(brkey):
print "Build job completed successfully at " + prettyBuildCompletedTime + "\n"
else:
print "Build job failed at " + prettyBuildCompletedTime + "\n"
buildState = getBuildState(brkey)
state = getState(brkey)

Dave Roberts

About the Author ()

Dave is a Sales Engineer for XebiaLabs based in Boston, MA. He has worked on both sides of DevOps, as a web software engineer and as a WAS/DB2 administrator.