scribbles // Jos de Weger

Humane and Solid Software Development

Alarm! Alarm! You broke the build!

2018-12-09 Jos

Delcom Signal Light Build Status

Beautiful isn’t it? A screamy, flashy light to remind you that you broke or fixed the build?

Erm… no, it’s bloody horrific and annoying, that’s what it is! But that’s also why it works. It helps you deal with the reality that software development isn’t all about finding new tech to fumble with and applying cool patterns on obscure places in your solution. It is there to keep you and your codebase sane. Fix it, and the light turns green again. Conditioning at it’s greatest, you little dev-lab-rat you!

Ok, on to the fun stuff: what did I build, how does it work, and where is the friggin’ code!?

TL;SMTC (Too Lazy, Show Me The Code)

On Github: click click, you know you want to Details on how to set things up can be found in the readme.

Prerequisites

  • Delcom Signal Light, I found mine on eBay
  • DevOps (formerly known as ‘vsts’) instance, team collection name, project name(s) and DevOps security token
  • A machine running windows/linux (works on Raspberry PI too)

LightHouse

If you are lazy, and went straight to the code, you probably noticed that I called it ‘LightHouse’, which is my way of trying to find a shabby symbol that says something about what is does: steer you away from the merciless rocks of failing build/tests into the save haven of properly validated code… But enough with the poetics.

So a couple of years ago, when working for a The Hague based company, we used to have something very similar for our TeamCity builds. This was a little project called Beacon. Unfortunately, Beacon doesn’t have DevOps support, and building something for yourself is much more fun, so I decided to go onto Ebay and get me one of those signal lights.

What does it do?

As soon as you start LightHouse, it will try and connect to the Delcom Signal Light. Once it is connected, it will loop through all the DevOps projects you entered and start fetching the latest builds via the DevOps api. It then returns one of the following results:

Color Result
Green All good
Orange One or more builds are partially succeeded
Red One or more builds have failed
Green/Orange/Red flashing Last build was red/orange/green, currently a build is running

It will check the latest builds at an interval, which is currently defaulted at 30 seconds, but can also be passed as a command line argument.

And that’s all there is to it.

Under the hood

DevOps API

I started by looking at the DevOps api documentation, which is quite comprehensive. To get the latest builds I discovered there is a query param called ‘includeLatestBuilds’ for the Definitions resource, calling an url like so: https://dev.azure.com/{organization}/{project}/_apis/build/definitions. This includes all the information needed in one call. How convenient!

Flurl

After reading Scott Hanselman’s blog post about Flurl, I thought this would be a good opportunity to try this library out. I’m impressed, this library makes api calls as easy as can be. Composing a call to the Definitions resource looks beautiful:

return vstsUrl
    .WithBasicAuth(username: _accessToken, password: string.Empty)
    .AppendPathSegment("build")
    .AppendPathSegment("Definitions")
    .SetQueryParam("includeLatestBuilds", true)
    .GetJsonAsync<BuildDefinitionsResponse>();
CommandLine

For parsing the command line arguments I used a package called CommandLine, which makes it real easy to parse commandline arguments

Parser
    .Default
    .ParseArguments<DevOpsOptions>(args)
    .WithParsed(async options =>
    {
    	//execute logic using strongly typed DevOpsOptions here
    });

based on the following DevOpsOptions:

public class Options
{
    [Option('s', "service", Required = true)]
    public string Service { get; set; }
}

public class DevOpsOptions : Options
{
    [Option('i', "instance", Required = true)]
    public string Instance { get; set; }

    [Option('c', "collection", Required = true)]
    public string Collection { get; set; }

    [Option('p', "team-projects", Separator = ',', Required = false)]
    public IEnumerable<string> TeamProjects { get; set; }

    [Option('t', "token", Required = true]
    public string PersonalToken { get; set; }

    [Option('r', "refresh-interval", Required = false, Default = 30)]
    public double RefreshInterval { get; set; }
}

You can even specify a helptext per property, which is displayed in the console when you passed the wrong arguments. Sweet!

HidSharp

Last but not least, for being able to access the usb device cross platform, meet HidSharp. Connecting to a usb device made real easy, all you need is the Vendor ID and Product ID of your usb device:

if (DeviceList.Local.TryGetHidDevice(out var myDevice, VendorId, ProductId))
{
	using(var stream = myDevice.Open()) 
	{
		//use for instance the read/write/GetFeature/SetFeature methods 
		//available on the stream to communicate with the device
	}
}

Extensibility

Working as a consultant, I switch customer now and then, so I get the pleasure of working with different kinds of CI systems. Therefor I tried to keep extensibility in mind. It is probably far from perfect (e.g. I haven’t tried to find a good abstraction for the DevOps settings that need to be passed to LightHouse), but I tried to at least work with a seperate ‘BuildProvider’, so other providers can later be added to the mix. What was that acronym? You Are Gonna Need It, right?

Cross Platform

This is the part I struggled with most. Delcom provides a DLL that you can use within your projects as a way to interact with the signal light, but unfortunately this DLL is only targeted at Windows 8 or above. Eventually I figured out how to send a byte stream to the device using cross platform and open source library HidSharp mentioned above for interacting with usb devices, and learned what the different byte combinations are for turning on/off colors, flashing the lights and buzzing the buzzer.

I tested the code on a Raspberry Pi 3 running Ubuntu Core 16. The code needs to scan the available usb ports for the device, so it needs to be run as ‘sudo’ (someone with actual Linux skills will probably puke in his mouth a little when reading this…).

Where to go from here

It was fun doing this little side project, learning a bit about usb interaction, cross platform development and running .net stuff on different devices. I’m planning on actually using the build light in the near future. When moving to a new product/team with a different CI setup, I might add support for that service to the codebase as well.