don't worry, it's probably fine

Puppet Patterns for Personal Provisioning

08 Dec 2013

puppet

We use puppet to manage our infrastructure at work, and lately I’ve taken to using it to manage the state of my personal machines. Running masterless (without connecting to a puppetmaster, and processing resources locally) is ideally suited for a small set of devices, or a large set which have very little variance between them. In my own case, I’ve picked up a couple of effort-saving tricks and patterns to make personal puppet configurations easier and less complex.

Resource Defaults

Puppet allows you to set a default parameters on resources, which is handy. For example, as I am the only probable user of my configuration, I’d like files to default to being owned by me, in my own group, and only writable by me. With variables, all you would normally have to do is to define a class where they are initialised and include it in your other classes. Simple? Simple.

class defaults {
  $my_name = 'mrwilson'
}

class users {
  include defaults
  user { $my_name:
     # user definition here
  }
}

However, you can’t achieve the same for resources, due to puppet’s scoping rules. The documentation recommends defining the variables outside of the node declarations in the site manifest, which tackles the issue by defining the defaults in the top scope.

As I don’t like defining things outside of node definitions, we can get to the same result via inheritance instead.

node base {
  File {
    owner => 'mrwilson',
    group => 'mrwilson',
    mode  => '0644'
  }
  Package {
    ensure => latest
  }
}

We can now inherit from this base node.

import 'base.pp'
node mymachine inherits base {
  include someclass
  include someotherclass
}

These classes will now include these defaults, so I no longer have to declare these parameters on my resources unless I want them to be explicitly overridden, which can be done on a class-by-class basis.

As an added bonus, by explicitly declaring that we want files to be owned by someone, we avoid the accidental problem where the owner and group fall back to the person who runs the puppet command. Since this is often root, or at least run with elevated privileges, we are no longer troubled by the annoyance of suddenly finding files owned by root in our home directory. With this safety, we also gain terseness (and readability) in basic declarations, and remove duplication (keeping our manifests as DRY as possible).

Leveraging a Package Manager

Minimising the effort to set up a new system is a sensible goal of any serious attempt to formalise a machine’s basic state. A rephrasing of this is “how few commands do I have to enter to achieve what I want?” and almost resembles golf in this sense (the author does not recommend golfing anything that people may have to care about or maintain). In an ideal world, I’d like to be able to install my configuration, puppet, and run my setup in a single command. We can accomplish this with a shell script, but that’s nowhere near enough fun.

Since a puppet configuration is just a tree of static files, one can create an archive for your particular distribution of Linux (other operating systems are available). In this way, we can take advantage of the features already implemented in many of these applications, such as dependencies, versioning, source downloads, and many other things. This isn’t limited to masterless setups, these features can be utilised at scale and would tie in nicely with an automated build server coupled with an internal repository.

I use Debian-based distros, and creating a .deb of static files is very simple. The Debian handbook has an easy to follow guide to create archives. Coupled with a control file that declares puppet as a dependency and a post-install script to run the build, provisioning a new system becomes as trivial as running apt-get install puppet-config.

Source for the .deb of my puppet config is on github.