Updating Puppet classification with hiera to use the modern lookup command

One of the most important parts of using any configuration management software, including Puppet, is making sure that your nodes receive the correct classification. You can write all the code you want to describe the desired system state, but if you don’t attach it to a node, it doesn’t provide any value. In previous articles, I have described using hiera_include() for that classification. However, hiera functions have been deprecated since at least version 4.10.

The replacement for hiera functions is called Lookup. Automatic Parameter Lookup now directly uses lookup instead of the older hiera functions. There is an actual lookup() function. There is also a plugin for the puppet command line utility, puppet lookup. Here’s what we have now:

node default {
  hiera_include('classes')
}

What can we replace this with? If we simply swap out hiera_include with lookup, we don’t actually include the results. We can add .include at the end, which is an object-oriented function that can be called on any string or array of strings:

node default {
  lookup('classes').include
}

This works, but leaves some ugly edges. First, if there are no results for classes, it gives a static error, rather than one you can control:

C:\Windows\system32>puppet apply --noop -e 'lookup("classes").include'
Error: Function lookup() did not find a value for the name 'classes'

Second, it could return a null string, which also gives an error:

C:\Windows\system32>puppet apply --noop -e '"".include'
Error: Evaluation Error: Error while evaluating a Method call, Cannot use empty string as a class name (line: 1, column: 3) on node agent.example.com

A third issue is one of cleanliness, not an actual error: the result could have multiple instances of the same class. Inserting .unique between the lookup and the include would address that.

Fourth, we could have almost any type of data in the classes key, not just strings. If it returned a hash, some other type related error would be seen.

Finally, while rare, we could potentially want to iterate on the result, maybe for logging purposes, and as it stands now, the lookup would have to be performed again for that. We can store the result and operate on it.

With these concerns in mind, a more comprehensive result was obtained with the help of Nate McCurdy:

node default {
  # Find the first instance of `classes` in hiera data and includes unique values. Does not merge results.
  $classes = lookup('classes', Variant[String,Array[String]])
  case $classes {
    String[1]: {
      include $classes
    }
    Array[String[1],1]: {
      $classes.unique.include
    }
    default: {
      fail('This node did not receive any classification')
    }
  }
}

The lookup() call does two things different now. First, we specify a type definition. classes MUST be either a String or an Array of Strings. Any other result will trigger a type mismatch error (while I definitely encourage the “one node, one role” promise of the role/profile pattern, returning multiple classes can be useful during development – just be sure not to allow such tests to propagate to production). Second, the result is stored in $classes.

Next, we have a case statement for the result. The first case is a String with at least one character, to protect against a null string. In that case, the single class is included. The second case matches an Array of Strings with at least 1 element, and each element has at least one character, to protect against a null array or an array with null strings.

The final case matches any empty strings or arrays and throws a customized error about the lack of classification. Because this fails, instead of silently completing without applying anything, no catalog is compiled and the agent’s local state cache is not updated, both events your monitoring system can report on.

As is, this provides a flexible classification system using modern puppet language constructs. It can also be further customized for each case, if desired. I would recommend that anyone still using hiera_include() review this and implement something similar to protect against the eventual removal of the deprecated functions.

Thanks to Nate McCurdy for his assistance with the language constructs!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s