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.


10 Comments »

  1. Thanks for the fun builds!

  2. Maybe the failure messages could be even more offending :P

  3. Nabaztag Publisher for CruiseControl.NET…

  4. Thanks for the info. I couldn’t get my plugin to be noticed until I saw “CC.NET expects plugin assembly files to be in the form ccnet.*.plugin.dll”.

    Thanks for sharing your experience.

  5. [...] ran across this post earlier today about integrating Nabaztag with [...]

  6. [...] for idea to The Pragmatic Programmers and their ridiculous lava lamps. My co-worker Kris wrote a custom publisher to integrate CruiseControl with the nabaztag [...]

  7. [...] The Cruise Control Interface [...]

  8. Hi.
    Good design, who make it?

  9. Hi,

    we plan to buy a Nabaztag for our java based project. Unfortunately, I can’t find the original Nabaztag plugin for the Java flavoured CruiseControl (project seems dead).
    Did you use it for your C# version? If so, did you keep the original source code?

    If you could send it to me, or point me where I can find it (google didn’t help on this), that’d be cool. Otherwise I might port your C# back to java.

    Thx,

    Fred.

  10. The code above is more specific to a CruiseControl.NET publisher and not a general Nabaztag API which is why I provided the links. The most API like thing in the code is formatting a URL with parameters. The Nabaztag API is pretty simple.

    I would just take the most basic plugin example for the Java version of CruiseControl and port the Run method from the code above.

    I’ve done it for CruiseControl.rb as well. You don’t need to use the Java version of CruiseControl to build Java projects. I much prefer Rake over Ant or Nant.

Leave a comment