Kris' Blog

RSS feed

Adding a Custom Publisher to CruiseControl.NET

Now you too can have a funny lookin’ bunny announce whether you broke the build with your last check-in.

The sample code is a Nabaztag publisher for CC.NET which sends the build status as a text to speech message via the Nabaztag API demonstrating a configurable CruiseControl.NET publisher plugin.

NabaztagPublisher.cs

using System;
using System.Net;
using System.Text;
using Exortech.NetReflector;
using ThoughtWorks.CruiseControl.Core;
using ThoughtWorks.CruiseControl.Core.Util;
using ThoughtWorks.CruiseControl.Remote;

namespace NabaztagCruiseControlPlugin
{
    [ReflectorType("nabaztag")]
    public class NabaztagPublisher: ITask
    {
        private const int SuccessEarPosition = 0;
        private const int FailureEarPosition = 9;
        private const string NabaztagUrlFormat =
@"http://api.nabaztag.com/vl/FR/api.jsp?key={0}&sn={1}&token={2}&posleft={3}&posright={3}&ears=ok&voice=graham22s&tts={4}";

        private readonly WebClient _client;

        private string _key;
        private string _token;
        private string _serialNumber;
        private string _pronounceableName;

        public NabaztagPublisher()
        {
            _client = new WebClient();
            _client.DownloadStringCompleted += new DownloadStringCompletedEventHandler(WebClientDownloadStringCompleted);
        }

        [ReflectorProperty("key")]
        public string Key
        {
            get { return _key; }
            set { _key = value; }
        }

        [ReflectorProperty("token")]
        public string Token
        {
            get { return _token; }
            set { _token = value; }
        }

        [ReflectorProperty("serialNumber")]
        public string SerialNumber
        {
            get { return _serialNumber; }
            set { _serialNumber = value; }
        }

        [ReflectorProperty("pronounceableName", Required=false)]
        public string PronounceableName
        {
            get { return _pronounceableName; }
            set { _pronounceableName = value; }
        }

        public void Run(IIntegrationResult result)
        {
            string name = PronounceableName ?? result.ProjectName;
            string message;
            int earPosition;
            switch (result.Status)
            {
                case IntegrationStatus.Success:
                    earPosition = SuccessEarPosition;
                    if (result.LastIntegrationStatus == IntegrationStatus.Success)
                        message = string.Format(
                            "Yet another successful build for {0}.", name);
                    else
                        message = string.Format(
                            "Recent check ins have fixed the build for {0}.", name);
                    break;
                case IntegrationStatus.Failure:
                    earPosition = FailureEarPosition;
                    if (result.LastIntegrationStatus == IntegrationStatus.Success)
                        message = string.Format(
                            "The build has failed for {0}. Oh dear.", name);
                    else
                        message = string.Format(
                            "The build is still broken for {0} people! Get your act together. Deary me.", name);
                    break;
                case IntegrationStatus.Exception:
                    earPosition = FailureEarPosition;
                    message = string.Format(
                        "Oh dear. The build has failed due to an exception for project {0}. This sucks.", name);
                    break;
                case IntegrationStatus.Unknown:
                default:
                    earPosition = FailureEarPosition;
                    message = string.Format(
                        "Oh dear. The build has failed due to an unknown reason for project {0}. This sucks.", name);
                    break;
            }

            Uri uri = new Uri(
                string.Format(NabaztagUrlFormat, Key, SerialNumber, Token, earPosition, Uri.EscapeDataString(message)));
            _client.DownloadStringAsync(uri, uri);
        }

        void WebClientDownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
        {
            Log.Debug(string.Format("Nabaztag API: {0}", e.UserState));
            if (e.Error != null)
                Log.Error(e.Error);
            else
                Log.Debug(e.Result);
        }
    }
}

I had trouble finding documentation on CC.NET plugins but I found documentation on adding a custom builder plugin and most the info applies to plugins in general.

I had to name the assembly ccnet.nabaztag.plugin.dll (CC.NET expects plugin assembly files to be in the form ccnet.*.plugin.dll) and mark it with NetReflector (part of the CC.NET distribution) attributes that correspond to the XML tags and attributes used to configure the plugin in the ccnet.config file.

<project>
...
<publishers>
...
<nabaztag key="111111111" serialNumber="111111111" token="111111111"
pronounceableName="My Project" />
</publishers>
</project>

Another thing to note, CC.NET’s static Log util is backed by log4net which is thread safe (in case you were wondering if it was ok for me to log on the async callback, the only drawback is in the log file it will use the thread id and not the name of the project).

Bunny
Edit:
I wanted to keep the example simple and focused on adding a custom publisher to CC.NET but it seems the bunny is more interesting so I wanted to point out this C# Nabaztag API wrapper which is also where I got the “oh deary me” messages from. And yet another library to control your bunny.

Also, for the original CruiseControl.