Those of you who use the excellent Buildbot Continuous Integration system probably spend a fair amount of time looking at it’s Waterfall display. Which shows the status of your builders, and probably looks something like this.

One of the problems with an OOTB Buildbot install however, is that while the Waterfall is pretty awesome (when it’s green), the Home Page tends to be a little… let’s say dry. Which is a shame for what should be valuable URL real-estate.

All of the Web pages served by Buildbot are customizable however, although (at the time of writing) there seems to be little documentation on how to customize the home page in an interesting way – say by passing through extra context variables that can be used in the template.

Redressing that is the purpose of this post!



Basic Customisation

Buildbot will look for a directory called “templates” adjacent to your master.cfg for any custom templates you want to define. These are then rendered using the Jinja2 templating engine.

The template for the homepage is called root.html. You may like to use the current Buildbot default root.html template as a basis to work from – you can find the source here.

If you simply add

<div>
Hello Beautiful World
</div>

before the final closing endblock tag, well you can probably guess what shows up towards the bottom of your Buildbot’s home page…



Adding Variables

In order to pass extra variables through to the template, we’ll have to override a couple classes.

The frist thing we’ll want to do is provide a class that will render the homepage. This is normally buildbot.status.web.root.RootPage. We want an instance variable that will hold our extra variables, so we subclass and override __init__

from buildbot.status.web import root
 
class ContextWebRoot(root.RootPage):
 
    def __init__(self, context, *args, **kwargs):
        self.context = context
        root.RootPage.__init__(self, *args, **kwargs)

The rendering actually gets done by the content() method of our page class, which has a signature that helpfully includes the request and the template context as a dictionary. This means that we can simply update the dictionary and continue as we otherwise would…

    def content(self, request, cxt):
        """
        Let's update the context with our extra vars
        """
        cxt.update(self.context)
        return root.RootPage.content(self, request, cxt)

Which is all well and good, but now we need to have this class be the one that gets called when we visit the “/” url.

Now, in order to do this, we’ll want to subclass the buildbot.status.web.baseweb.WebStatus class that provides er… the Web status functionality. You can do that by adding the following to your master.cfg

from buildbot.status.web import baseweb
 
class ContextWebStatus(baseweb.WebStatus):
 
    def __init__(self, context={}, *args, **kwargs):
        self.context = context
        baseweb.WebStatus.__init__(self, *args, **kwargs)
 
    def setupSite(self):
        """
        Use the existing setup machinery, then replace the page that
        the "/" url points at.
        """
        baseweb.WebStatus.setupSite(self)
        our_root = ContextWebRoot(projects=self.projects)
        self.site.resource.delEntity("")
        self.site.resource.putChild("", our_root)

Now all that remains is to set the custom WebStatus class in the master.cfg.

tpl_context= {
    'foo': 'bar',
    'goo': 'car',
    'hoo': 'dar'
}
 
BuildmasterConfig['status'] = [ContextWebStatus(
    context=tpl_context,
    http_port=80,
    allowForce=True
)]

You can now access the context variables from the template with e.g.

{{ foo }} <br/>
{{ goo }} <br/>
{{ hoo }} <br/>



Putting it all together

So, the status section of your master.cfg should now look something like this:

from buildbot.status.web import baseweb, root
 
class ContextWebRoot(root.RootPage):
 
    def __init__(self, context, *args, **kwargs):
        self.context = context
        root.RootPage.__init__(self, *args, **kwargs)
 
    def content(self, request, cxt):
        """
        Let's update the context with our extra vars
        """
        cxt.update(self.context)
        return root.RootPage.content(self, request, cxt)
 
class ContextWebStatus(baseweb.WebStatus):
 
    def __init__(self, context={}, *args, **kwargs):
        self.context = context
        baseweb.WebStatus.__init__(self, *args, **kwargs)
 
    def setupSite(self):
        """
        Use the existing setup machinery, then replace the page that
        the "/" url points at.
        """
        baseweb.WebStatus.setupSite(self)
        our_root = ContextWebRoot(projects=self.projects)
        self.site.resource.delEntity("")
        self.site.resource.putChild("", our_root)
 
tpl_context= {
    'foo': 'bar',
    'goo': 'car',
    'hoo': 'dar'
}
 
BuildmasterConfig['status'] = [ContextWebStatus(
    context=tpl_context,
    http_port=80,
    allowForce=True
)]

Announcing Dizzee

Dizzee is an Emacs library for managing subprocesses.
This is it’s initial release!

From the docs:

Dizzee is a pleasant way to manage your project’s subprocesses in Emacs.
You have a project.
In order to get an instance running and start working, you have to manually launch say… 4 processes in 4 different shells.
This is Not Fun.
Thankfully it is also a definable, repeatable process – which means that we can Use Programming.
At worst, this is More Fun than doing it yourself every time.

This all came about when I started my current job – of the three product stacks I work on, none can be run from source without launching multiple processes in separate shells. Terminator helps (and is generally awesome) but being able to start work on a bug all with one command, and build in code reloading without having to build it into the source & go through the process of getting the relevant buy-in for that, was a massive win.

Bugs/feature requests & docs via github.

Enjoy.

Love regards etc

For those moments when you want fast access to “~/.something” – what nicer than M-x .thatsomething

(defmacro dotfile (filename)
  "Define the function `filename' to edit the dotfile in question"
  (let ((filestr (symbol-name filename)))
    `(progn
       (defun ,(intern filestr) ()
         ,(format "Open %s for editing" filestr)
         (interactive)
         (find-file ,(concat "~/" filestr))))))

With which we can define a M-x .emacs function with:

(dotfile .emacs)

Love regards etc

Doing a lot of work with interpreters that need semi-colons to tell them when a line ends recently – and making flymake be quiet about it involves a whole sequence of C-e … ; … what was I thinking about again?

So thanks to this, C-M-; now colonizes the line and leaves me happily where I was

 
(defun colonize ()
  "For languages that insist on putting a colon at the end of a line,
do that. But stay where you're thinking is at."
  (interactive)
  (save-excursion
    (move-end-of-line nil)
    (insert ";")))
 
(global-set-key (kbd "C-M-;") 'colonize)

Love regards etc