Part 5 - Using templates to create complex files at run time
Contents
In Part 4 we copied static files from the master to the clients, we still relied on a simple static file but we learned how to have several of these files and select them based on various criteria such as hostname, network or arbitrary variables.
Often this becomes unwieldy, if you have /etc/hosts with slight differences in many environments and wish to make a big change you'll be editing a lot of files increasing the possibility of user error.
Templates
Puppet supports using ERB templates to create files based on variables available to your manifests, such as those provided by facter.
ERB templates allows you to mix ruby code with plain text, in it's simplest form you can just print a known variable, for example:
This hostname: <%= fqdn %>
Which would result in a single line that something like:
This hostname: dev1.devco.net
Apart from this simple example it also supports looping over Arrays and simple logic like if - then - else style constructs, lets see how to use this in our /etc/hosts example.
The simplest ''/etc/hosts''
Lets assume you wish to create a /etc/hosts file that only has localhost and it's own hostname, ip address combo, it could look something like this:
127.0.0.1 localhost.localdomain localhost 192.168.1.10 dev1.devco.net dev1
Clearly here a one-size-fits-all solution just won't work, the file can only ever be built using information specific to each host. Here is a simple template to achieve this:
127.0.0.1 localhost.localdomain localhost <%= ipaddress %> <%= fqdn %> <%= hostname %>
The variables ipaddress, fqdn and hostname come from facter, as discussed in Part 4.
This host file will possibly not work 100% for you, remember we added the host entry puppet to it, lets make a slightly more complex template that includes a superfluous if statement to add the puppet entry only on machines in the devco.net domain:
127.0.0.1 localhost.localdomain localhost <%= ipaddress %> <%= fqdn %> <%= hostname %> <% if domain == "devco.net" -%> 192.168.1.5 puppet <% end -%>
You can see we now have an if statement in there, but take special note of the <% instead of <%= that starts these new lines, also note the -%> at the end, this means that the new line that will usually be created if you just used %> will be suppressed.
Templates are read and parsed using the template function, we'll see how to use it later.
Setting up the puppetmaster to handle templates
By default puppet stores templates in /var/lib/puppet/templates, create a file called /var/lib/puppet/templates/hosts.erb and place the template in the previous section there.
You can also override the location of this template directory using the /etc/puppet/puppet.conf file on the master:
[puppetmasterd] templatedir = /path/to/templates
We will use the default in this guide.
Adjusting the hosts class
Lets edit hosts.pp file to look something like this:
class hosts {
file {"/etc/hosts":
owner => root,
group => root,
mode => 644,
content => template("hosts.erb")
}
}
If you now run your client puppet you should something along these lines:
dev1# /usr/sbin/puppetd --test
notice: Ignoring cache
info: Caching catalog at /var/lib/puppet/localconfig.yaml
notice: Starting catalog run
.
.
.
notice: //Node[dev2.pinetecltd.net]/hosts/File[/etc/hosts]/checksum: checksum changed '{md5}fcb38ae62df638e5a0565dd1074d8c64' to '{md5}c4e1573ef975181ca6cbe2ecbf64992f'
.
.
.
info: Filebucket[/var/lib/puppet/clientbucket]: Adding /etc/hosts(c4e1573ef975181ca6cbe2ecbf64992f)
info: //Node[dev2.pinetecltd.net]/hosts/File[/etc/hosts]: Filebucketed to with sum c4e1573ef975181ca6cbe2ecbf64992f
notice: //Node[dev2.pinetecltd.net]/hosts/File[/etc/hosts]/content: changed file contents from {md5}c4e1573ef975181ca6cbe2ecbf64992f to {md5}cd3174690dc1365fe208b9687f107587
notice: Finished catalog run in 0.04 seconds
If you look at the /etc/hosts file you should see it has the correct content with your IP address as shown in the initial requirement.
The important thing to note in the hosts class above is that we are using the content option and the template function, you can also store the outcome of the template in a variable:
$myvar = template("hosts.erb")
It's worth noting that unlike source you cannot use the trick with an array to select the template based on multiple selectors, you could though do something like:
$myvar = template("hosts-${domain}.erb")
Which in our case will load a file called hosts-devco.net.erb
Further Reading
You should now be able to understand the documentation at the reductive labs site detailing how to use templates, it has an example of iterating over arrays in a template and also using conditionals.
Conclusion
You might be thinking that you could use a multidimensional array or perhaps a hash to store hostname/ip address pairs and loop over it in a template, unfortunately Puppet does not support such complex data types, only single dimensional number indexed arrays.
In this part we have learned how to use templates, but we have pretty much hit a barrier in flexibility, it would be unwieldy in almost all cases discussed so far to add extra hosts to the /etc/hosts file in a arbitrary manner - especially in the absence of complex data types - in the next part we will see how to use the host type to achieve this instead of simply copying files around.
Once you start exploring other types instead of just file you will really unlock the full potential of puppet.
Move on to Part 6.
