Specialising validate_re with wrapper functions in Puppet

Once your puppet code base reaches a certain size you’ll often have a number of validate_ functions testing parameters and configuration values for compliance with local rules and requirements. These invocations often look like this:

validate_re($private_gpg_key_fingerprint, '^[[:alnum:]]{40}$', 'Must supply full GPG fingerprint')

Once you’ve spent a minute or two reading that you’ll probably be able to understand it; but wouldn’t it be nice to not have to care about the exact details and focus on what you’re actually testing? An approach I’ve been experimenting with on one larger code base is to specialise those kind of calls using a wrapper function. Instead of the detailed call above we can now do validate_gpg_key_fingerprint($finger_print). Which of those are easier to read? Which is simpler for people new to puppet?

Implementing this kind of wrapper is much easier than you’d expect. All you need is a small stub function that wraps an existing one, in our case validate_re, and supplies some sensible defaults and local validation. You can see an example below.

cat modules/wrapper_functions/lib/puppet/parser/functions/validate_gpg_key_fingerprint.rb
module Puppet::Parser::Functions
  newfunction(:validate_gpg_key_fingerprint, :doc => <<-'ENDHEREDOC') do |args|
    A simple wrapper function to show specialisation of 'validate_re' from the stdlib
    and how it can make manifests more domain specific and easier to read.

    unless (args.length >= 1 && args.length <= 2)
      message = 'wrong arguments - <GPG fingerprint> [error message]'
      raise ArgumentError, "validate_gpg_key_fingerprint(): #{message}"

    fingerprint = args[0]

    # here we set the local rules and sensible defaults
    message     = args[1] || 'Must supply full GPG fingerprint'
    regex       = '^[[:alnum:]]{40}$'

    # here we run the original function
    function_validate_re( [ fingerprint, regex, message ] )

This is easy to test in isolation, a nice place to encompass more complicated validations while presenting a simple usage case and requires only a small amount of shim coding. If this approach interests you it’s also quite easy to achieve a similar benefit with custom wrappers to the is_ functions in stdlib that have a greater understanding of what you’re testing than the basic, but common cases, provided in things like stdlib . For example you can wrap is_ip_address and only return true for addresses in your own valid ranges.

The most obvious downside to this approach, other than the custom coding required, is that if used often enough it’s easy to convince people that puppet has a number of basic validate_ and is_ functions that don’t actually exist anywhere outside of your repos.

Although these kinds of changes are not going to revolutionise your code base they are nice, gradual improvements. If you have a number of users that don’t work with puppet on a regular basis, changing the vocabulary of your functions to closer align with your local domain terms can be a nicer way to ease them into reading, and eventually contributing, to the code.