September 12, 2017

Russell, revisited

3 years ago I wrote about Russell, a static site/blog generator I wrote. Since then, I've had a major rewrite of the project to make it easier to extend and configure.

My sentiments towards other static site generators and CMSes are still the same, though at least by now the most popular ones aren't all written in Ruby.

I realized quickly though that I wanted more control over how my site was to be generated. I didn't want to be limited to what could be expressed in a YAML file - it basically meant that I would have to think ahead of anything that the user of Russell would want to do, and add support for that in the code that reads the YAML config and acts upon it.

The solution to this was simple: Use Python to run and configure Russell instead. When you run russell setup to create a new Russell site, the main entrypoint will be

Furthermore, I now recommend that you install Russell into a virtualenv which you can bring in other dependencies to as well. For example, in the source code for the website you're reading now, I bring in libsass to compile Sass files into CSS.

blog.write_file('assets/style.css', sass.compile(
    filename=os.path.join(ROOT_DIR, 'sass', 'main.sass')

If you're looking for a static site generator, especially for a blog or similar, and you like Python, I recommend now more than ever to check out Russell!

September 2, 2017

Proper logging in Django

Setting up logging in a sane way in Django has been surprisingly difficult due to some confusing setting names and the annoying way Django's default logging setup looks like. Here I'll go through some simple steps you can take to gain full control of your logging setup, without too many changes to a standard Django setup.

First of all, set LOGGING_CONFIG = None to prevent Django from setting up logging for you at all. You want this because in addition to the LOGGING dict that you define, Django has some defaults settings it will use, which you may not want.

Because we've set this, we need to call logging.dictConfig(LOGGING) ourselves. This can happen at the end of your settings file.

Make sure that LOGGING['disable_existing_loggers'] = False. If this is set to true, any loggers defined or invoked before logging.dictConfig is called will silently discard all its messages. You definitely don't want that.

Finally, I like to define LOGGING['root'] to have one log instance that controls everything, but sometimes log messages don't get sent to it. I found that setting the "" (empty string) logger can fix this:

LOGGING['loggers'][''] = { 'propagate': True }
August 23, 2017

Different SSH keys per github organisation

If you're like me, you prefer seting up different SSH keys for personal and professional use. Maybe you even work for multiple organisations at the same time and don't want to risk 1 compromised private key to have a wide-spread effect.

First, we need to set up our SSH config. We'll use a fake hostname for our github organisation. Put this in your ~/.ssh/config:

host hostname identityfile ~/.ssh/id_example_rsa

This will make sure that when we do any SSH (and indirectly, git) operations against the domain "", the correct SSH key will be used.

We could just remember to replace with every time we git clone or add a remote URL, but that's tedious. Instead, we can set up git in a way that does this automatically for us. This is what you want in your ~/.gitconfig:

[url ""] insteadOf = insteadOf =

This will dynamically replace any URLs that start with "" or "" with "". This has the added benefit of letting you git clone the URL you would put in your browser to visit a repository on github, but git will automatically use SSH instead of trying HTTP authentication.

Everything should work as before, except git will use the correct SSH key. The only caveat here is that if you have keys in your ssh-agent, but not the one needed to work with the github organisation, your SSH client may not be smart enough to figure that out.

July 12, 2017

Using a puppet-control repo in Vagrant

Whether you use Puppet Enterprise or r10k, using a "control repo" with a branch for every environment is the way you want to set up Puppet these days. Finding a way to make this work well with Vagrant for local development was surprisingly difficult - most guides out there focus on a very simple puppet setup with no modules, or maybe assuming that puppet is installed on the host operating system. I wanted to write a bit about the things I discovered while experimenting trying to get a proper setup up and running.

This is not meant as an introduction to puppet or vagrant - you might want to read up on how to use these tools before starting this article, as I won't go into detail on how puppet or vagrant configuration works..

I'll assume you already have a puppet-control repository. If you don't, have a look at this template repo.

Modifications to the control repo

First of all, you probably want to add .gitignore rules in your control repo for node-specific hieradata for vagrant files, so that you can modify these files as much as you want. If you use the default hierarchy and make sure that all your vagrant hostnames end with .vagrant it would look like this:


Also make sure to add some sort of generic vagrant hiera file which applies to all vagrant machines. We set a provider custom fact which is set to "vagrant" for vagrant machines, and then load the hiera file providers/%{facts.provider}.yaml, but if you can think of another way of setting generic hiera data for vagrant machines, you can do it however you want.

How we'll run Puppet

By default, r10k creates one environments for every git branch. This is rather nice for deploying things remotely, but for developing locally, this means we'd have to commit and run a deploy command before any change we make becomes "public" to the Vagrant machines. This is too slow for us, so we will be using r10k sparingly - mostly just to install modules. We could actually use puppet- librarian instead and get module dependency management, but we'll stick with r10k to stay consistent with our production environment.

We'll create a "fake" environment called "vagrant", which all of our VMs will use (configured through puppet.conf). This environment will be a plain directory on the VM's filesystem, and we'll simply invoke puppet using puppet apply.

Creating the Vagrant repo

We'll create a new git repo which contains the Vagrant configuration:

  • A Vagrantfile
  • Provisioning scripts
  • Puppet configuration
  • r10k configuration

The control repo can exist inside of this vagrant repo (make sure to .gitignore it!) or outside. The important thing here is to share the correct directories in the Vagrantfile:

config.vm.share './control', '/etc/puppetlabs/code/environments/vagrant'
config.vm.share './puppet', '/etc/puppetlabs/puppet'
config.vm.share './r10k', '/etc/puppetlabs/r10k'

Configuration files

We do not need a lot of configuration to make this work. I'll refer to configuration file paths relative to the directory where your Vagrantfile is.

puppet/puppet.conf only needs to contain "environment = vagrant". You might want to add various configuration to stay consistent with your production environment, of course.

puppet/hiera.yaml does need to be present, but does not need any actual configuration. We need to put "version: 5" in there to prevent Puppet warnings.

r10k/r10k.yaml should contain "cachedir: /var/cache/r10k".


While Vagrant comes with a Puppet provisioner, it does not work that well with our workflow, so we just write a custom shell script that does the necessary things to get everything set up. Here's an example for CentOS/RHEL:

rhv=$(cat /etc/redhat-release | grep -Po '\d' | head -1)
rpm -Uvh${rhv}.noarch.rpm
yum -y install puppet-agent
/opt/puppetlabs/puppet/bin/gem install r10k

Add it to our Vagrantfile:

config.vm.provision 'install_puppet', type: 'shell', path: ''

Let's make sure it works by running this command:

$ vagrant up && vagrant ssh

Our first puppet run

We're almost ready to run puppet - only one thing is missing: Installing modules and their dependencies. We'll do this manually with r10k, inside the virtual machine:

$ cd /etc/puppetlabs/code/environments/vagrant
$ sudo /opt/puppetlabs/puppet/bin/r10k puppetfile install

You may also want to check for missing dependencies which need to be added to your puppetfile:

$ sudo /opt/puppetlabs/bin/puppet module list --tree

Once this is done, we can try executing a class:

$ sudo /opt/puppetlabs/bin/puppet apply -e "include profile::base"

At this point, you can start editing and testing your code changes in puppet- control.