don't worry, it's probably fine

Enforcing State with Puppetlets

18 Jan 2015

puppet

How to use small, idempotent, self-contained executable puppet scripts to enforce state, rather than a full solution.

Update: I tweaked the pup script to use exec rather than just running puppet apply, replacing the current process rather than forking another. It’s better to use "$@" than $* so quoted arguments are preserved (cheers to lamby for pointing these out)

You can make nearly any file executable by using a shebang or hash-bang at the top of the file - e.g. #!/usr/bin/env ruby. This tells the shell in which the script is being executed to run the contents of the file with the shebang command - in this example, /usr/bin/ruby some_file.rb.

With some work, we can do the same with puppet.

#!/usr/bin/env puppet
file { '/var/tmp/test.txt': content => 'Hello, world!' }

With a little chmod to make it executable, we get … a problem

Error: Could not parse application options: invalid option: ./test.pp

So the shell (in my case zsh) is interpreting that we want to run puppet ./test.pp, and puppet needs to take an additional argument to tell it to run agentless. Here’s the snag: the shebang parsing only cares about the first command as an argument (and after that only short-form flags).

You can get around that with a custom wrapper that runs puppet apply - let’s call it pup - in your path.

#!/bin/bash
exec puppet apply "$@"

Now our original file becomes

#!/usr/bin/env pup
file { '/var/tmp/test.txt': content => 'Hello, world!' }

Note: we use /usr/bin/env rather than hard-coded paths to enable executable overriding. From this we get:

Notice: Compiled catalog for hostname in environment production in 0.39 seconds
Notice: /Stage[main]/Main/File[/var/tmp/test.txt]/ensure:
  defined content as '{md5}6cd3556deb0da54bca060b4c39479839'
Notice: Finished catalog run in 0.03 seconds

Mission accomplished. The pup script is now fully editable, and can enforce other things like puppet’s new parser/type system.