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.