Schildwächter – Ideas and Solutions by Carsten Thiel

Playing with Puppet 4 - Iteration and EPP templates

We finally had a chance to play with a few Puppet 4 features, such as iteration and EPP templates and learned a bit along the way.

The module we want to build will consits of a single class that calls a defined type to set instances of the software mwe we manage. The instances need a few configuration options, so we will load all instances and their options from Hiera.

For the example, let’s define a hash directly:

class mwe {
  $myhash = {
    one      => {
      first  => 'valueA',
      second => 'valueB',
      third  => 'valueC',
    },
    two      => {
      first  => 'valueD',
      third  => 'valueE',
    },
  }

As you see, we have two instances with options, where instace ‘two’ does not have a value for the ‘second’ parameter.

Now we want to pass them on to a defined type mwe::instance, so let’s loop over the entries of our hash.

$myhash.each |$key, $value| {
  mwe::instance { $key:  * =>  $value }
  if $value['first'] == 'ValueD' {
    include 'mwe::specialcase'
  }
}

In case any mwe instance has ‘ValueD’ for the key ‘first’, we need to do some additional setup not directly tied to any instance. Notice, that we pass * => $value as argument to the type, effectively sending the hash as arguments to the type. This is Puppet 4’s solution to make create_resources obsolete, see also roidelapluie’s blog post on the matter.

Now within our defined type mwe::instance we take the arguments and pass them on to the configuration of the instance.

define mwe::instance (
  String           $first  = undef,
  Optional[String] $second = 'valueF',
  Optional[String] $third  = undef,
){
  file {"/tmp/mwe_${title}":

While we are at it, lets use the new EPP templates.

One of the differences in Puppet 4’s scoping has implications for using templates from defined types as well. With Ruby ERB templates, the defined type’s local variables are accessible from within the template, since the template inherits everything from the scope in which it is evaluated.

This is no longer the case with EPP templates, which have their own scope inheriting from node scope. Thus you can access class variables through their full name, but not the local variables of the defined type.

Thus in our case, we have to pass the arguments to the template explicitly.

Our defined type thus looks like

define mwe::instance (
  String           $first  = undef,
  Optional[String] $second = 'valueF',
  Optional[String] $third  = undef,
){
  file {"/tmp/mwe_${title}":
    content => epp('mwe/mwe.epp',{
      'first'  => $first,
      'second' => $second,
      'third'  => $third,
      })
  }
}

and the EPP template, taking the arguments, is

<%- | $first, $second, $third | -%>
First: <%= $first %> 
Second: <%= $second %> 
<% unless $third == undef { -%>
Third: <%= $third %>
<% } -%>

As we already validate the types and values of the parameters within our defined type, we don’t in the template. Using Puppet inside the template has the definite advantage of a more coherent code base.

Tags: code puppet