puppet tricks: debugging

Update: (2012/9/30) I came up with this around the time I was using 0.25.  Apparently now you can do similar utilizing the -debug switch on the client along with debug() calls. I thought the function was only part of PuppetLab's stdlib, but apparently its in base, at least in 2.7+. I'll probably do a part 2 to this with more info, although there isn't much more.

Update: (2012/12/20) So the debug() function from stdlib is lame. I spent a while troubleshooting my new environment not getting messages and realized that rolling back to notice() worked. Could have sworn I tested it when I posted that. I did also run into an issue that naming the fact debug is actually a bad idea and so have updated this blog accordingly.

Update: Found this bug that talks about the facts not returning as the appropriate types.

Disclaimer: I am not a ruby programmer... so there might be "easier" or "shorter" ways to do some of the things I do with ruby, but my aim is for readability, comprehensibility by non-programmers, and consistency.

In my time playing with puppet I have had to do a few things I was not pleased with.  Mainly I had to write several hundred lines of custom facts and functions.  Debugging was one of the biggest pains, until I found a wonderful blog post that helped me out with that.  Actually, when he helped me out with debugging I had already been to the site once because I ran into a bug related to the actual topic of his post, "calling custom functions from inside other custom functions".  Back to the matter at hand... when I first started working on custom functions I would leave exceptions all over my code and use them to step through the functions during debugging sessions.  While the code itself was short, this a tedious process as I would have to comment out each exception to move to the next one and then re-run the test.  It looked like this:

checkval = someaction(var)
#raise Puppet::ParseError, "checkval = #{checkval}"
result = anotheraction(checkval)
raise Puppet::ParseError, "result = #{result}"

Then I found function_notice, which got rid of the commenting of exceptions by allowing me to log debug statements.  So I replaced all of my exceptions with if wrapped function_notice calls, resulting with:

debug = true
checkval = someaction(var)
if debug
   function_notice(["checkval = #{checkval}"])
end
result = anotheraction(checkval)
if debug
   function_notice(["result = #{result}"])
end

An important thing to remember about function_notice in a custom function is that the variable you pass to function_notice must be a list.  I have not done anything other than send a single string inside a single list, so I could not speak to its other behaviors.  The length of the code increases greatly, and I do not actually do a debug for everything.  Overall this is a much better place to be.  However, now to enable debug I have to edit the custom functions on the puppet master which requires a restart the service (puppetmasterd, apache, etc), and logs are generated for every client.  That is still a pain.  This is when I had a "supposed to be sleeping" late at night revelation.  You can lookup facts and variables inside your custom functions!  So I created a very simple fact named debug.rb that looks like this:

Facter.add('puppet_debug') do
    debug = false
    if File.exists?('/etc/puppet/debug')
        debug = true
    end
    setcode do
        debug
    end
end

So what that means is that on any of my puppet clients I can enable debugging of my puppet setup by touching the file /etc/puppet/debug, and disable it by deleting that file.  To enable this in my custom function I change the definition of debug.

debug = false
if lookup('puppet_debug') == 'true'
    debug = true
end
checkval = someaction(var)
if debug
    function_notice(["checkval = #{checkval}"])
end
result = anotheraction(checkval)
if debug
   function_notice(["result = #{result}"])
end

Now, this may seem like a kinda odd way to go about setting the debug value, but while the code in the custom fact is working with the boolean value of true/false, when called as a fact it returns the string "true" or "false".  Since the string "false" is true from a boolean sense you could end up getting flooded with logs if you do a simply true/false check against the lookup() result.  Thus, we default to false as that should be our normal working mode, and if the fact returns the string "true", we set debug to true.  Now there is a custom fact providing debug, and a custom function utilizing it to log messages on the puppet server. Yay!  But wait, there is more!  Now that you have the custom fact defined, you can utilize it inside your puppet manifests in the same way!  Let take a look:

class resolver {
  $nameservers = $gateway ? {
    /^192.168.1./ = ['192.168.1.25', '192.168.2.25'],
    /^192.168.2./ = ['192.168.2.25', '192.168.1.25'],
  }
  define print() { notify { "The value is: '${name}'": } }
  if ${::puppet_debug} {
    # On the server
    notice("${::hostname} is in ${::gateway} network")
    # On the client
    print { ${nameservers}: }
  }
}

Wait, what? Sorry.. threw a few curve balls at you. The notify call, which is not a local function, logs on the client side. Then I wrapped it in a define called print, because I was going to pass an array to it. By wrapping it in the define it takes the array and performs the notify call on each object in the array. You can read more about this on this page, under the sections What is the value of a variable? and Whats in an array?.  The article has some nice explanations of a few other things as well.

Also, if you'd rather check for $::debug than $::puppet_debug then add the following to your site.pp:

$::debug = $::puppet_debug