Monitoring Codeship CI pipelines with BitBar

Most of the tails.com tech stack is deployed through CI/CD pipelines powered by Codeship. Deploying code to production is as simple as merging into a specific branch, and this coupled with our propensity for shipping early and often means we often have a lot of builds in flight throughout the day.

Although Codeship has the inevitable Slack integration and a Chrome extension that'll show you the state of your projects in your browser, I spend a lot of my day with my head buried in a text editor. What I really wanted was an application-independent way to tell the status of our builds with a simple glance.

Enter BitBar.

BitBar is a simple Mac OS X app that lets you take textual output from a script or command and decorate your menu bar with it. There are a myriad of uses for something like BitBar, and the website has a fair number of simple scripts you can download, but it's also very easy to write your own.

To get started, download BitBar from the releases page on GitHub. For now just get the stock BitBar zip file - we'll briefly touch on BitBarDistro a bit later. Unzip the file and drop the BitBar.app file into your Applications folder and double click to start. At this point you'll be prompted to choose a plugin folder; either create a new folder or choose an existing one.

Hint: I keep mine in ~/bitbar, where there the scripts are symlinked from individual git repositories, but do whatever works for you.

Writing your first plugin

The BitBar app works by periodically running the scripts in your plugin folder you picked above.

Each script will create its own menu bar item and associated menu. How frequently the script runs is determined by the filename, which has the structure name.<period>.ext, where <period> could be, for example, 30s for a 30-second refresh interval or 5m to refresh every 5 minutes.

To get up and running with a really simple example, let's create a (poor) replica of the system clock menu item using BitBar.

Create a file called time.1s.sh, put a call to the date system command in it and make it executable:

$ cd ~/bitbar
$ cat >time.1s.sh
#!/bin/sh
date "+%a %H:%M"
$ chmod +x time.1s.sh

Since it's just a shell script, you can run it in the terminal to make sure it's working as intended:

$ ./time.1s.sh
Tue 21:27

Now, assuming you've got BitBar running, click on the BitBar menu item and select Refresh All from the dropdown menu. Assuming everything's gone well the BitBar text in your menu bar will be replaced by our shiny clock, and the 1s refresh time will keep the time updated.

Screenshot: Time menu bar item

BitBar has a tonne more features to explore, but rather than extending our poor system clock replacement let's use them to build something entirely more useful

Codeship build monitor

Codeship have a very simple HTTP API that you can use to get the latest status for your builds. (Aside: The API doesn't currently support projects that use their shiny new Docker-based Jet platform, but apparently that's coming soon.)

You'll need your API key, so head to your account settings page and copy it into your clipboard.

Unfortunately Codeship don't have an official Python wrapper, so I've knocked up a very crude Codeship API wrapper you can copy/paste into your script.

Create a new file called codeship.30s.py in your BitBar plugins folder and create the basic skeleton below and paste the API wrapper code from above as indicated:

#!/usr/bin/env python

API_KEY = "<insert your api key here>"

def main():
    print ":anchor:"

# Paste API wrapper code here

if __name__ == "__main__":
    main()

You'll want to make this file executable too:

$ chmod +x codeship.30s.py

...and then to get the new menu bar item to show up you'll need to ask BitBar to refresh its plugins list using the Preferences > Refresh All option from the menu. If all went well, you should see an anchor emoji in your menu bar.

Screenshot: Codeship anchor

(BitBar supports Github-style emoji replacement)

Okay, first things first, let's update our main() function to output a list of the projects. We wouldn't want all of these in the menu bar, but with BitBar we can output ---, and everything after this line will be displayed in the dropdown menu rather than in the menu bar itself:

def main():
    print ":anchor:"
    print "---"

    cs = Codeship(API_KEY)
    
    # Fetch a list of projects with builds
    active_projects = [
        p for p in cs.get_projects()
        if p.builds
    ]

    for project in active_projects:
        print project.repository_name

Once BitBar refreshes out plugin you should be able to click on the anchor and see a list of your projects in the menu.

Screenshot: Codeship project list

We can display an emoji next to each project indicating whether the most recent build was successful by checking project.latest_build.status. Let's add a simple dict mapping project statuses to emoji at the top of our file:

#!/usr/bin/env python

API_KEY = "<insert your api key here>"

STATUS_EMOJI = {
    'success': 'smiley',
    'waiting': 'clock4',
    'testing': 'hourglass',
    'failed':  'poop',
    'stopped': 'no_entry_sign',
    'unknown': 'question'
}
        
...

...and then use this in our main() function to prefix the project name with the right emoji:

def main():
    ...
    
    for project in active_projects:
        status = project.latest_build.status
        print ":{emoji}: {name}".format(
            name=project.repository_name,
            emoji=STATUS_EMOJI.get(status, 'question')
        )

When BitBar refreshes your plugin, it should look something like this:

Screenshot: Codeship project list with status

We can also get BitBar to open the URL for your project when you click a menu item by adding a href parameter. Parameters are in key=value format and are separated from the text of a menu item using a pipe character:

def main():
    ...
    
    for project in active_projects:
        status = project.latest_build.status
        print ":{emoji}: {name} | href={url}".format(
            name=project.repository_name,
            emoji=STATUS_EMOJI.get(status, 'question'),
            url=project.url
        )

So that's great, but wouldn't it be better if we could see the details of each build? Our API wrapper has a builds property, and BitBar supports submenus by prefixing lines with -- so we have all the building blocks we need:

def main():
    ...
    
    for project in active_projects:
        status = project.latest_build.status
        print ":{emoji}: {name} | href={url}".format(
            name=project.repository_name,
            emoji=STATUS_EMOJI.get(status, 'question'),
            url=project.url
        )
        
        for build in project.builds:
            print "--:{emoji}: [{branch}] {message} | href={url}".format(
                emoji=STATUS_EMOJI.get(build.status, 'question'),
                branch=build.branch,
                message=build.short_message,
                url=build.url
            ) 

You'll now be able to see the status for each recent build in a project in a submenu, and you can click on the build to go to the build output on codeship.com.

Screenshot: Codeship project and build list

You can download the finished code for this plugin here.

That's it!

There are many more BitBar features that we haven't covered here - colours, fonts, external script execution, image support and more - so feel free to explore the plugin docs and extend the monitor to suit your own needs. And because each plugin is a shell script, it can also do anything else a shell script can do (such as use say for text to speech, or display desktop notifications, for example).

You can also package up your plugin as a standalone menu bar app using BitBarDistro, which is useful to sharing with a team.