Leveraging the power of Puppet and using only about 35 lines of puppet code, we can install Err directly from Git(Hub), into it’s own isolated virtualenv and have it automatically update whenever the Git repository changes. How awesome is that?

Prerequisites

Before we get started, you’ll need to have a machine capable of running puppet. In this post I will show you the use of Puppet in combination with Vagrant, but you are free to run Puppet any way you like of course.

You’ll also need a few puppet modules, but these are easy to obtain.

Getting down to business

First, we should grab all of the necessary modules. As we’re going to pull straight from Git, the first module we’ll need is puppetlabs/vcsrepo. Since this module is available in the Forge, a simple

$ puppet module install puppetlabs/vcsrepo

will pull down the module and install it for us. As a side-benefit, it’ll also create (assuming you’re doing this on a workstation) ~/.puppet/modules/ for us if it doesn’t exist yet.

Next, we’re also going to need a way to set up the Python virtualenv so we can install Err seperate from all the system-wide Python packages. While there’s at least three Puppet modules capable of managing virtualenvs, I chose stankevich/puppet-python for this myself.

As the current version of puppet-python contains a few bugs, we’ll use my own fork of it until my pull requests get merged. Additionally, since it isn’t available as a Forge module at this time, we’ll check out the code from Git directly:

$ git clone git://github.com/zoni/puppet-python.git ~/.puppet/modules/python

2013-03-14 update: My pull requests were accepted today, so you can now clone from the original project instead:

$ git clone git://github.com/stankevich/puppet-python.git ~/.puppet/modules/python

Stitching it all together

Now that we have all the necessary tools, we can really get started. If you’re following along with Vagrant, then create a modules/base.pp next to your Vagrantfile. If you’re not using vagrant, you can create a base.pp anywhere you like for use with puppet apply, or you can add some code to your node definition when using a puppet master (but then I probably wouldn’t need to tell you any of this in the first place, right? :) ).

In this base.pp we’ll start off by declaring any necessary dependencies. This will ensure stuff like Git and Python get installed if they haven’t yet.

# You might need to uncomment this later on so binaries like
# virtualenv get found.
#Exec { path => '/usr/bin:/bin:/usr/sbin:/sbin' }

# Ensure Git is installed
package { "git": ensure => present }

# Ensure Python and virtualenv are installed
class { 'python':
  version    => 'system',
  virtualenv => true,
}

Next, we define where we’re going to pull Err’s code from, and where the repository should come to live on the system. You can change the revision argument here to the name of a branch, a tag or the SHA-1 hash of a specific commit.

$checkout = "/path/to/where/you/want/your/checkout/"
vcsrepo { $checkout:
  ensure   => latest,
  provider => git,
  source   => "git://github.com/gbin/err.git",
  revision => "master",
  require  => Package['git'],
}

Running the manifest as it is now would ensure we have Git, Python and virtualenv installed, as well as a checked-out version of the latest commit on master of the Err repository in the directory you specified. This means only one last step remains, namely the actual install of Err into a virtualenv so we can run it. Again, a few lines of puppet code in base.pp will take care of this.

# Install a virtualenv for Err into /usr/share/err
$virtualenv = "/usr/share/err/"
python::virtualenv { $virtualenv:
  ensure       => present,
  requirements => "${checkout}/requirements.txt",
  notify       => Exec['setup'],
}

exec { 'setup':
  command     => "python setup.py install",
  cwd         => $checkout,
  path        => "${virtualenv}/bin",
  refreshonly => true,
}

Kicking off Puppet automatically with vagrant

If you’ve been using vagrant, a bare-minimum Vagrantfile that will kick off Puppet like this after your VM boots would look something like the following:

Vagrant::Config.run do |config|
  config.vm.box = "lucid32"
  config.vm.network :hostonly, "10.0.2.15"

  config.vm.provision :puppet do |puppet|
    puppet.manifests_path = "manifests"
    puppet.manifest_file  = "base.pp"
    puppet.module_path    = "#{ENV['HOME']}/.puppet/modules"
  end
end

And there we have it. Whenever the manifest is run, it will checkout or update the Git repository when required, setup a virtualenv with the requirements Err needs, and finally run setup.py install for Err itself. Additionally, whenever requirements.txt changes, it’ll run pip install again to make sure the virtualenv again has all the needed packages.

In closing

If you hate copy and pasting partial code snippets, you can find the above base.pp manifest as a gist on GitHub here. It also includes an example to install Err side by side under both Python 2 and Python 3.

I hope this will save someone else some time and effort in setting up Err. If you have any questions, please don’t hesitate to ping me, I’d be happy to help you out.