Data Driven Continuous Delivery with XL Release’s Export Hooks

| July 28, 2015 | 0 Comments

One of things in XL Release 4.7.0 that I’m most excited about is the new “export hook” feature. Export hooks allow you to populate your reporting/Business Intelligence/Big Data stores with as much or as little information about release, phases and tasks as you need.

More importantly, you can “mix in” related data from external sources, such as retrieving information from process tools like JIRA or ServiceNow to allow you to link releases to service tickets, incidents, feature requests, epics, stories or the like. Best of all, implementing an export hook is extremely easy, certainly when compared to the effort required to get this kind of information out of most CI or release management tools.

XL Release already provides a bunch of useful out-of-the-box reports, of course. Export hooks, however, allow you to pre-process the data in whichever way you need it for your reports – and, by making it easy to pull in information from additional sources, enable you to quickly put together reports that go beyond what XL Release can provide, giving you insight into your full end-to-end pipeline from concept to cash, as well as your full technical stack from your virtual machines to your business processes.

The export hook documentation goes into more detail, but export hooks are essentially event hooks that are triggered whenever a release is completed or aborted. To implement an export hook, all you need to do is define any parameters that your export hook needs and provide a Jython script as the implementation. This Jython script is passed, amongst others, a release object containing all the information about the release that was just completed or aborted.

You can do whatever you wish with this information: print it to the console, write it to a file, send it to a web service or message queue, process it in any way you need to, link it to information from other sources etc.. If you’re planning to store the information in a database, there’s also a JDBC export hook, which takes care of all the JDBC connection management and makes a connection object available that you can use to write to your database. You’re not limited to one export hook, either: create as many as needed to distribute your XL Release information to all the relevant data stores.

There are a couple of sample export hooks that push data to ElasticSearch and MySQL, respectively. Here, just as a flavour of what’s possible, we look at a simple export hook that writes aggregated release information, and more detailed task information, to two text files in CSV format.

Four lines of XML is all it takes to define the export hook itself. Compare that to the extension APIs for some other tools!

<type type="acme.CsvExportHook" extends="xlrelease.ExportHook">
  <property name="releaseDetailsFile" />
  <property name="taskDetailsFile" />
</type>

Here’s an instance of the CSV export hook, configured via the Settings > Configuration menu in XL Release:

export-hook-configuration

The implementation of the hook is almost as simple. Here, for example, is the loop that writes information about each task to the task details file, one line per task:

task_details_file = open(exportHook.taskDetailsFile, 'a')
try:
    if isEmpty(exportHook.taskDetailsFile):
        task_details_file.write('"Release ID","Template ID","Release title",
"Release owner","Release start time","Release end time",
"Release duration","Planned release duration","Release delayed?",
"Release status","Release flag status","Release Tags","Phase title",
"Task type","Automated task?","Task title","Task owner","Task team",
"Task start time","Task end time","Task duration","Task status",
"Task failure count","Task flag status"\n')
    for phase in release.phases:
        for taskOrGroup in phase.tasks:
            for task in expandGroup(taskOrGroup):
                startTaskMillis = task.startDate.getTime()
                endTaskMillis = task.endDate.getTime()
                task_details_file.write('%s,%s,"%s","%s",%d,%d,%d,%s,%d,%s,%s,"%s","%s",%s,%d,"%s","%s","%s",%d,%d,%d,%s,%d,%s\n' % (
stripApplication(release.id), stripApplication(noneToEmpty(release.originTemplateId)), release.title, release.owner,
startReleaseMillis, endReleaseMillis, releaseDuration, noneToEmpty(release.plannedDuration), 
oneIfOverrun(release.plannedDuration, releaseDuration), release.status, release.flagStatus, noneToEmpty(' '.join(release.tags)),
phase.title, task.type, oneIfAutomated(task.type), task.title, noneToEmpty(task.owner), noneToEmpty(task.team), 
startTaskMillis, endTaskMillis, (endTaskMillis - startTaskMillis) / 1000, task.status, task.failuresCount, task.flagStatus))
finally:
    task_details_file.close()

Here, the expandGroup function simply extracts the list of tasks contained in a parallel group.

Writing information to the release details file is a little more interesting. Here, we are calculating some basic aggregated statistics for the release, such as the percentage of automated vs. non-automated tasks:

release_details_file = open(exportHook.releaseDetailsFile, 'a')
try:
    if isEmpty(exportHook.releaseDetailsFile):
# similar to task_details_file.write(...) above
        release_details_file.write(...)
    numTasks = 0
    numAutomatedTasks = 0
    numRetriedTasks = 0
    numCompletedTasks = 0
    numSkippedTasks = 0
    for phase in release.phases:
        for taskOrGroup in phase.tasks:
            for task in expandGroup(taskOrGroup):
                numTasks += 1
                if oneIfAutomated(task.type):
                    numAutomatedTasks += 1
                if task.status == 'COMPLETED':
                    numCompletedTasks += 1
                elif task.status == 'SKIPPED':
                    numSkippedTasks += 1
                if task.failuresCount:
                    numRetriedTasks += 1
# similar to task_details_file.write(...) above
    release_details_file.write(...)
finally:
    release_details_file.close()

The type definition and implementation of the example task are available as a Gist in the XebiaLabs community Gist repository. Note that the code linked to is sample code only that is not officially supported by XebiaLabs.

Here are some very simple reports I put together using the resulting CSV files:

Task result status by team

task-result-status-by-team

Avg. task duration (sec) by team, for automated and non-automated tasks

avg-task-duration

Failures by task, by team

failures-by-task

Level of automation by release template

level-of-automation-by-template

Task result status by release template

task-result-status-by-template

Task result status for automated and non-automated tasks

task-result-status-by-automated-and-non-automated

Delayed and on-time releases by release owner

delayed-and-on-time-by-release-owner

% of delayed releases (y-axis) by level of automation

delayed-by-level-of-automation

The underlying data set is just a sample – the idea here is more to demonstrate what’s possible, and to provide some inspiration and ideas for your own export hooks.

Data-driven Continuous Delivery, here we go!


About the Author ()

Andrew Phillips is the VP of DevOps Strategy at XebiaLabs. He is a DevOps thought-leader, speaker and developer.