jmtd → log → puppet
As a first step on the road to evaluating different configuration management systems, I set up two VMs to test out puppet.
Puppet is conveniently packaged within Debian so client and server
installation was very simple. By default, puppet clients will look for a host
named puppet on the local network, so by naming the server sensibly the two
started talking to each other. puppetca --sign [client hostname] on the
server started the ball rolling.
My first test configuration was to add a user to the sudoers file. With
CF Engine 2, I would have achieved this by using editfiles to check for a
regular expression and insert a line if necessary. I stumbled across some
CF Engine-style definitions for puppet in a Debian Administration
article (taken originally
from puppet's own wiki, which have since been moved/deleted) and achieved it
using
append_if_no_such_line { sudo:
file => "/etc/sudoers",
line => "jon ALL=(ALL) ALL"
}
I'm a little concerned that this approach to problem solving is no better
than CF Engine 2, and in fact worse than how I'd do it with CF Engine 2: if the
host had a line with a different quantity of whitespace, such as jon ALL=(ALL) ALL, it won't match exactly and a
duplicate line will be appended, whereas with CF Engine 2 I would have checked
for something like ^jon \+ALL=(ALL) \+ALL$.
I set up my work laptop as another test node, and used puppet to install some packages that I commonly use:
class jon-desktop {
package { ['icedove', 'git-core', 'vim-gnome', 'vinagre', 'build-essential',
'devscripts', 'subversion', 'git-buildpackage', 'mutt',
'offlineimap', 'ascii', 'gitk', 'chromium-browser'
]: ensure => installed }
}
This worked fine, but is a little verbose and the package list is awkward to maintain buried inside the puppet syntax. I spent some time trying to see whether I could populate a puppet list based on the contents of a file: then I could list the packages one-per-line in a separate file. I haven't found a way of doing that yet.
Using my laptop as a second node also exposed some interesting puppet client
behaviour. Once I went off-site, the laptop tried to look up puppet on
foreign networks (such as my home network). I would have assumed that, once
the strong SSL association had been made, the client would try to connect to
the FQDN of the server. This probably isn't a serious problem in practice,
as we probably won't need to rely on nodes off-site talking to the
configuration server, but I hope that the client doesn't trust any host that
happens to answer to puppet...
I fixed this by hacking my hosts file to include an entry for puppet. I
could maintain this hack using Puppet itself, which would protect me against
stale definitions if any renumbering takes place. I could also have added a
line
server = puppet.fqdn.example.org
in /etc/puppet/puppet.conf, which is a more robust way of solving the
problem.
Comments
Hi,
About I haven't found a way of doing that yet. sure it will be more elegant in Perl, Python, Ruby, whatever, but can be done using just bash:
inigo@crono:~/tmp $ cat packages.txt # a comment icedove git-core vim-gnome # empty lines vinagre build-essential devscripts subversion # end inigo@crono:~/tmp $ ( IFS=$'\n' arr=( $(while read -r pkg; do echo \ \'$pkg\'; done<<< "$(grep -vE '(#|^$)' packages.txt)" ) ); ( IFS=, ; printf "class jon-desktop {\n package { [%s]: ensure => installed }\n}\n" \ "${arr[*]}" ; ) ; ) class jon-desktop { package { [ 'icedove', 'git-core', 'vim-gnome', 'vinagre', 'build-essential', 'devscripts', 'subversion']: ensure => installed } } inigo@crono:~/tmp $Greetings, poisonbit (Iñigo).
Ick, I'd not realised that you could do cfengine in puppet -- how revolting.
Anyway, moving swiftly on, you should look at Augeas, and puppet-augeas, which lets you do stuff like this:
augeas { "sudoer-jon": context => "/files/etc/sudoers/spec[user = 'jon']", changes => [ "set user jon", "set host_group/host ALL", "set host_group/command ALL", "set host_group/command/runas_user ALL" ], }I've only written a few of these rules, so there are probably bugs in that, but the point is that Augeas reads config files into a tree structure, and lets you search that tree, and set new bits in it -- if your changes section actually changes anything, it then gets translated back into the format of the relevant config file.
An example I have tested, that adds an extra file to one's logrotate setup if not already present, is as follows:
augeas { "dhcp-logrotate": context => "/files/etc/logrotate.d/rsyslog/rule[schedule = 'daily']", changes => [ "ins file after file[last()]", "set file[last()] /var/log/dhcpd.log", ], onlyif => "match file[. = '/var/log/dhcpd.log'] size == 0", }This adds the dhcpd.log file entry after the last file entry in the daily set, but only if it's not already findable.