PSA regarding Puppet template variables and default values

Inside of a Puppet template, you would think that one of these would work to set a default value for something not explicitly defined in your manifest:

It doesn’t. This will clobber the variable, every time.

To understand why this happens, you need to consider two critical pieces of information:

  1. The architecture of Puppet is stupidly complicated, which leads to unexpected behaviors all over the place.
  2. Because Puppet variables are lazy-loaded, they need to reinvent how variables are accessed in the ERb templating system. The Puppet developers, in their infinite wisdom, decided to do this by using method_missing and dumping leaky abstractions all over the place.

To summarize, the reason this doesn’t work is because there really isn’t a variable named my_var at all. ERb tries to find a symbol called my_var and can’t, because that thing that looks like a variable is really syntactic sugar over something completely different happening under the covers.

The correct way to do this is to force a lookup through the scope object as follows:

The more vested I get in Puppet, the more I want to try out Chef.

Calling custom functions from other custom functions in Puppet

This post probably describes a bug, but I haven’t had the time yet to determine if this still exists in Puppet 2.6.x. Instead, here’s a post that will hopefully help out someone else having the same problem.

The other day, I was writing some custom parser functions for our Puppet 0.25.x install. In the interest of reusability, the idea was to keep the functions small and composable, and have them call each other in a nice, maintainable way in order to prevent code duplication.

As per the Puppet documentation, Puppet functions are called from Ruby by prefixing the function name with function_:

This worked great for calling built-in functions, but when I tried to call one of my own functions from another of my own functions, Puppet would just hang:



After a bunch of debugging, I found that the Puppet autoloader seemed to be spinning itself into an infinite loop trying to locate and load my_other_function when it was called from my_function.rb. The solution was to manually require the file containing that function:

The above assumes, of course, that the functions are in the same directory as one another.

With the dependency loaded, the custom function should work the same as any other parser function.

Sharing code between Facter facts

Spurred on by Jordan Sissel’s post about nodeless Puppet configurations, which are purely¬† fact-driven, I’ve started writing a lot of custom facts for our environment. One thing that’s been driving me nuts has been how Facter doesn’t have a real, supported mechanism for code reuse between facts. I can understand this decision — facts are supposed to be orthogonal. At the same time, there’s a lot of flexibility to be found in custom facts, and let’s face it, there’s only so much you can do without real code reuse.

However, it is pretty easy to stick shared code into a library to be pulled by Puppet along with the Facter facts themselves, because Puppet happens to synchronize all Facter .rb files before it starts to actually run any of them. For this example, we’re going to write a fact called httpd_running that checks the process table for httpd processes, and returns true if it finds any. (Since it crawls /proc, it only runs on Linux. Sorry, cross-platform Puppet guys.)

When Puppet resynchronizes facts from the server, it dumps them all into the folder /var/lib/puppet/lib/facter (or equivalent). /var/lib/puppet/lib is part of the RUBYLIB for any Puppet invocation. This means that any .rb file synchronized in this way is loadable using

First, we’re going to need a module path for our libraries. You can really put it anywhere, but for the sake of convenience I put these all in my Facter class under modules/facter/lib/facter.

Next, we’ll create modules/facter/lib/facter/lib_process_running.rb:

We stick this into a module so our functions don’t pollute the global namespace. I picked FacterShared because, well, it was completely arbitrary.

Next, we need a custom fact to actually call this, so let’s create modules/httpd/lib/facter/httpd_running.rb:

I’d love to hear about an official way to do this, but here’s something for those of you who need a way to do this here and now.

© 2019 @jgoldschrafe

Theme by Anders NorenUp ↑