Deployment hook in XL Deploy

Now that we've migrated to TFS 2013 we now have Team Rooms available for all the projects. TFS provides a number of default events such as work item state changes and builds that have completed. Because we use XL Deploy to handle our automated deployments I wanted to include those events to the team room as well.

XL Deploy has a nice REST API but unfortunately there is no way to register a hook when a deployment completes. Fortunately XL Deploy has a way to add rules that can execute extra steps at various points in a deployment. I've used this to add a step to the end of a deployment plan that uses a little Jython script to call a remote REST API whenever the deployment completes.

Adding a rule

XL Deploy stores it's rules under the $XLDEPLOYPATH/server/ext folder in the xl-rules.xml file and this is where we'll add the necessary configuration to run our script.
The rule definition looks like

 <rule name="DeploymentSucceeded" scope="post-plan">
    <steps>
        <jython>
          <order>99999</order>
          <description>Execute application deployed hook</description>
          <script-path>hooks/application-deployed.py</script-path>
          <jython-context>
            <application expression="true">context.deployedApplication.name</application>
            <environment expression="true">context.deployedApplication.environment.name</environment>
            <version expression="true">context.deployedApplication.version.name</version>
            <hooksDict expression="true">context.repository.read("Environments/Hooks")</hooksDict>
          </jython-context>
        </jython>
    </steps>
</rule>  

I've set the rule scope to be post-plan to make sure that this step is only added after the validation and planning stages have completed. The rule adds a new jython step that instructs XL Deploy to call the script defined in script-path and is a relative path starting from $XLDEPLOYPATH/server/ext on the XL Deploy server.

To provide the script with some useful information you can define a number of parameters in the jython-context. These parameters will be made available as variables in the jython script. In this example I pass in the application, environment and version names. The hooksDict parameter is pointing to a dictionary under Environments/Hooks in the XL Deploy repository and it contains the URL of the REST API we want to call.

! Before you add this to your xl-rules.xml be sure to make a backup first!

Depending on the configuration of your XL Deploy instance you may need to restart the server before the rule is activated.

The Hooks dictionary

To be able to configure the URL that the script will be calling I've added a dictionary under the Environments folder in the XL Deploy repository. This dictionary contains the following settings:

KeyValue
hostlocalhost:8000
deployment-complete-url/$application/$environment/$version/$username

The $application, $environment, $version and $username placeholders will be replaced by the Jython script later on. If you leave host empty the script won't run which makes it an easy way to disable the hook alltogether.

Creating the script

Now that we have the rule in place we need to act whenever the step is triggered. The script is actually fairly simple, it collects the variables, figures out the current username running the deployment and calls a REST service:

import httplib  
from string import Template

if hooksDict != null and "host" in hooksDict.entries and "deployment-complete-url" in hooksDict.entries:  
    username = context.task.username

    context.logOutput("Calling deployment-completed hook with: " + username + " deployed application " + application + " (" + version + ") to " + environment)

    s = Template(hooksDict.entries["deployment-complete-url"])
    url = s.substitute(application=application, version=version, environment=environment, username=username)

    conn = httplib.HTTPConnection(hooksDict.entries["host"])
    conn.request("GET", url)
    r1 = conn.getresponse()
    conn.close()
! Note that Jython is a Python derived language and is white-space sensitive.

In the script you can see that the deployment-complete-url is retrieved from the dictionary and the placeholders are replaced with the actual values. This allows a bit of flexibility when defining a REST service endpoint.

Creating the REST service

To demonstrate that this all works I put together a simple NodeJS app (this is pushing the term app) to receive the request that the Jython script makes. It will simply log the request URL to a file and return "OK":

var http = require("http");  
var fs = require("fs");

var server = http.createServer(function(req, res) {  
    fs.appendFile("/tmp/completed.log", req.url + "\n", function(err) {
        if(err) {
          res.writeHead(500, { "Content-Type": "text/plain" });
          res.end(err);
        }
        else {
            res.writeHead(200, { "Content-Type": "text/plain" });
            res.end("OK\r\n");
        }
    });

});

server.listen(8000, '127.0.0.1');  

This service is started by running:

sauron@localhost> node restservice.js  

Testing the deployment complete hook

To test the rule and the actual script I created the following in a local XL Deploy instance:

  • Application: TestApp
  • Package: TestPackage (without anything in it)
  • Environment: Production

I also added the Hooks dictionary with the settings as described above.

On the deployment tab simply drag the package TestPackage in the left box and the environment Production in the right box and click Execute. Because there isn't actually anything in the package or the environment the deployment will start immediately and you should see sometthing like this:
XL Deploy, before deployment And when the execution is complete you should see this:
XL Deploy, deployment completed

If everything went OK then the file /tmp/completed.log (or wherever you've put it) should show:

/TestApp/Production/TestPackage/admin

Wrap up

While extending XL Deploy this way works and provides a nice and simple way to achieve what I wanted I'm not entirely convinced this is the best way to do this. It may be that a plugin is actually better suited and could be more powerfull but that is something I need to look at in the future.

In the meantime, this hook now allows me to post messages to the TFS team room whenever someone deploys an application with XL Deploy!