Applying a Puppet configuration to a machine or group of machines (called nodes) entails defining classes which are applied to said nodes. Classes are defined in manifest files and nodes are often defined as a collection of classes to be applied to a particular machine. These definitions are typically also contained in files.
Instead of classifying nodes in files I can use an external node classifier or ENC, basically a program that outputs a YAML document when passed a node name. (Puppet can also retrieve classification data directly from an LDAP directory, but even then I’d use the ENC method as it is more flexible.) This is good because I can obtain a list of classes for a machine from any source I desire, e.g. a database, and Puppet will, as long as the classes exist of course, happily apply the configuration I create on the fly to the node.
I’ll also shortly discuss the generate and extlookup functions because they also relate to obtaining information dynamically within a class.
Here’s the YAML document my ENC outputs. I’m applying two classes called “dyn” and
“ssh”. The former has three parameters (“ports”, “smtpmx”, and “users”), and the
latter none. Note that the values “parameters” in the element are passed into
the Puppet class as variables, and that these are not associated with a particular
class. (I trust this will become clear in a moment, when we see the class definition for
The ENC can, as shown in the above YAML example, provide classes with parameters (a.k.a. parametrized classes). The “dyn” class is passed the specified parameters. Class parameters defined in the ENC missing in the class definition result in an error when the agent attempts to apply the class. This error 400 (“Invalid parameter … on node …”) is very useful in tracking down errors.
Here is the class definition:
This is the template called
my_memoryvariables are from the
parameterssection of the YAML produced by the ENC.
- The rest are parameters from the parametrized class.
And here is the resulting file
d2 as produced by Puppet applying the template:
My intention in experimenting with parametized classes was to create a class
with which I can define a list of users as parameter. I don’t seem to be
able to use control structures (i.e. loops) within a class definition (or at least
I haven’t found a way to do that) which is why I have to resort to
and arrays to create an array of users and then manually process that array. This is
done by the defined
- With an
inline_template()I create a string containing a colon-separated list of values, with each “user” separated from the next with a semi-colon.
- I then
split()this into an array.
one_user()resource is invoked with this array (note the trailing colon on the variable name) which appears to call my
one_user()“function” once for each array element, setting
$titleto the content of the array element.
More data from server: generate
generate() function invokes an executable program on the server and
collects/returns that program’s output. Unfortunately,
accept arguments so it’s a bit limited. (I can’t say “generate for this user” or
“generate for this node”.) About the only thing the program can do is check its
environment to determine for which node it is being invoked
$SSL_CLIENT_S_DN_CN). Although I can’t pass parameters to the program in
I can use its result as a string I pass to
inline_template() as shown above.
jpgen program in this example is trivial:
One of files produced by the
file resource in
one_user() then contains:
External data: extlookup
External lookups from a database into a class are possible with extlookup, a function that extracts data from a CSV file. (There seem to be multiple iterations of extlookup floating around with differing capabilities including a pluggable version with JSON and YAML lookups.) I’m limiting this discussion to the extlookup function as I have access to in a 2.7 Puppet release. (Puppet’s quite idiotic release numbering (0.25, 0.26, 2.7) makes searching for solid information difficult at best. Tip: remove all leading zeroes and decimal points and read what’s left as the release number. But I digress.)
Because it wasn’t obvious to me when I first used this, I’d like to explicitly point
out that the CSV files
extlookup consults are on the Puppet master (i.e. the server) and not on the
client node. In my
site.pp I configure the order in which
for CSV files:
Puppet will first try the fully qualified domain name (i.e.
and will fall back to
/etc/db/common.csv. Suppose I have the following line
notice() in my class (see above) will print the contact and e-mail address. If
extlookup() doesn’t find
extlookup() will return the default
value I specified as “NONAME”.
And here, for good measure, is the small Python program I used to create above
ENC. Note how I read the content of the the node’s facts from a file populated
by Puppet before my ENC is invoked. The file contains
all the node’s facts, including special facts.
They have been collected and deposited on the puppet master in a file called
.yaml. The ENC classifier can read
that YAML to find facts about the node it is creating classes for. (The YAML
in this file contains a few Ruby objects which I remove with a text substitution instead
of doing it properly.)
All in all, these features make for very powerful combinations when designing a Puppet infrastructure. And as I’m a beginner, I hope I haven’t made any grave mistakes here.