Configuring laptops and servers

In the past, walking the uneasy borderline of academia, I used to managed compute servers for my research group. New hardware was a rare godsend, configuring it by hand was natural. At 99designs, I've been privy to the opposite approach: machines should be disposable. This requires the entire setup of a new machine to be automated.

At first I amassed shell scripts of complicated install routines, and whilst these worked they weren't that composable, say when you wanted multiple services on the same machine. Then from Babushka we learned a better way: test if something you need's there, install it if it's not, then test again to see if you succeeded. This is not hugely different from using make, just more flexible and more fault-tolerant.

Still, Babushka made me uneasy: all this ceremony and complex templating, just to describe a few facts and simple rules?

Enter a little logic

Logic programming's heyday feels well passed, making it ripe for rediscovery. Today several mature Prolog interpreters are packaged for all the major Linux distributions, and the declarative paradigm it belongs to still has serious advantages:

  1. Predicates describe the relationships between entities more flexibly than functions do in other languages
  2. Depth-first search through the space of available solutions is built-in!

If you haven't used Prolog before, both of these are a little weird at first. Here's the simplest example. Let's define a couple of package names:

pkg(python).
pkg(pip).
pkg(supervisor).

We can now query it multiple ways, automatically!

?- pkg(python).  % is python a package?
true.

?- pkg(jython).  % is jython a package?
false.

?- pkg(P).       % what packages exist?
P = python ;
P = pip ;
P = supervisor.

All we did is define the predicate pkg, but it's as if we'd simultaneously written methods is_pkg?(), get_any_package() and get_all_packages() in another language. This is a trivial example of a powerful principle, one which can seriously simplify your code. What's more, this isn't some special DSL, it's built into the core language.

Marelle

Using these principles, I put together a functioning, cross-platform system for configuring machines called Marelle. Though experimental, it's happily powering projects like the Great Language Game, and already works well for achieving a consistent environment between OS X and Linux machines.

A full target description has four parts:

% 1. register the name 'supervisor' as a package
pkg(supervisor).

% 2. explain how to tell if it's been installed
%    (if supervisorctl is in PATH)
met(supervisor, _) :- which('supervisorctl').

% 3. explain how to install it
%    (using pip)
meet(supervisor, _) :- bash('sudo pip install -y supervisor').

% 4. (optional) register any dependencies
depends(supervisor, _, [pip]).

Along with a description of our dependency pip, we can now ask marelle to install it on the command-line:

$ marelle meet supervisor
supervisor {
  pip ✓
Downloading/unpacking supervisor
  Downloading supervisor-3.0.tar.gz (459kB): 459kB downloaded
  Storing download in cache at ./Downloads/pip-cache/https%3A%2F%2Fpypi.python.org%2Fpackages%2Fsource%2Fs%2Fsupervisor%2Fsupervisor-3.0.tar.gz
  Running setup.py egg_info for package supervisor

Requirement already satisfied (use --upgrade to upgrade): setuptools in /usr/local/lib/python2.7/site-packages/setuptools-1.1.6-py2.7.egg (from supervisor)
Requirement already satisfied (use --upgrade to upgrade): meld3>=0.6.5 in /usr/local/lib/python2.7/site-packages (from supervisor)
Installing collected packages: supervisor
  Running setup.py install for supervisor

    Skipping installation of /usr/local/lib/python2.7/site-packages/supervisor/__init__.py (namespace package)
    Installing /usr/local/lib/python2.7/site-packages/supervisor-3.0-py2.7-nspkg.pth
    Installing echo_supervisord_conf script to /usr/local/bin
    Installing pidproxy script to /usr/local/bin
    Installing supervisorctl script to /usr/local/bin
    Installing supervisord script to /usr/local/bin
Successfully installed supervisor
Cleaning up...
} ok ✓

Since these operations are idempotent, we can always try again:

$ marelle meet supervisor
supervisor ✓

It was already there, no change, no fuss.

But... Prolog?

Compared to languages like Ruby or Python, Prolog's standard library is a little non-intuitive. Language usability has certainly improved since SWI-Prolog was designed. This means that in Marelle, you'll end up shelling out more often than you would with other tools. In terms of community, Prolog's is also much smaller, and without the momentum of major languages. So using Marelle means being a bit more self-service than you'd need to be with tools like Babushka, Puppet or Chef.

Caveats aside, there's one burning question: is there a use for non-determinism in system configuration? If so, Prolog's built-in machinery is a killer feature. Provide two ways to meet a target, and failover from one to the next is automatic. If you had to run on a huge variety of different systems, this could be immensely helpful. In typical cloud provisioning though, companies just use a standard base operating system, so Marelle's strength here lies dormant.

Try it

Despite these concerns, Marelle remains an elegant and simple way to configure servers, thanks to the powerful paradigm of logic programming. Why not try it for your next hobby project, or even for tasks as simple as managing the packages on your everyday computer.

https://github.com/larsyencken/marelle