Module of the Week: puppetlabs/stdlib – Puppet Labs Standard Library Part 2

Purpose Standard library for creating Puppet modules
Module puppetlabs/stdlib
Puppet Version 2.6+
Platforms Redhat, Debian, Solaris, Mac OS X, Windows

Welcome back to the module of the week! Last week we began our coverage of the puppetlabs/stdlib module by examining the file_line resource, which allows you to manage individual lines in a file.

We’ll pick up where we left off by covering the data validation functions provided by the puppetlabs/stdlib module and how to use them to make our Puppet manifests more resilient to runtime failures.

There are five validation functions in total. The following four functions: validate_array, validate_bool, validate_hash and validate_string allow you to validate the type of data used in your manifests. These are useful when you want to be sure the input value is an Array, Hash, Boolean, or a String.

Sometimes you need to go beyond type validation and ensure something more specific like an ipaddress or hostname. The validate_re function helps here by validating input using regular expressions. With access to the full power of Ruby’s regular expressions, you can validate just about anything.

Resource Overview - validation functions

Function validate_array
validate_bool
validate_hash
validate_re
validate_string

Example usage

In the following example we use the validate_array and validate_bool functions to validate the input types used by the ntp module.
$ cat /etc/puppet/modules/ntp/manifests/init.pp

# Example Usage
# 
# class { "ntp":
#   servers    => ['0.debian.pool.ntp.org'],
#   autoupdate => false,
# }
#
class ntp ($servers="UNSET",
           $ensure="running",
           $autoupdate=false,
) {
  validate_array($servers)
  validate_bool($autoupdate)
}
In the above example we are using the validate_array function to ensure the $severs class parameter is an array: ['0.debian.pool.ntp.org'], and not a String: '0.debian.pool.ntp.org'. We’re also using the validate_bool function to ensure $autoupdate is a Boolean value: true or false, and not: 'true', 'false', or 'string'. Lets see what happens if I use a String value for the $server class parameter:
$ cat /etc/puppet/manifests/site.pp

node 'pmotw.puppetlabs.com' {
  class { "ntp":
    servers => '0.debian.pool.ntp.org'
  }
}

$ puppet agent -t
…
err: Could not retrieve catalog from remote server: Error 400 on SERVER: "0.debian.pool.ntp.org" is not an Array.  It looks to be a String at /etc/puppet/modules/ntp/manifests/init.pp:5 on node pmotw.puppetlabs.com
warning: Not using cache on failed catalog
err: Could not retrieve catalog; skipping run

Yikes! Looks like we’ve managed to piss off our Puppetmaster. It’s basically yelling at us for using a String and not an Array. It’s Puppet’s way of saying, “You’re doing it wrong!”, and indeed we are. The fix is pretty simple:

node 'pmotw.puppetlabs.com' {
  class { "ntp":
    servers => ['0.debian.pool.ntp.org']
  }
}

When the concern is not the type, but the actual value, then the validate_re function is the right tool for the job. From the docs:

Perform simple validation of a string against one or more regular expressions. The first argument of this function should be a string to test, and the second argument should be a stringified regular expression (without the // delimiters) or an array of regular expressions. If none of the regular expressions match the string passed in, compilation will abort with a parse error.

A “real” world example

You just got a job at the Federation, an organization committed to space exploration. You’re a rising star! You’ve been put in charge of the Intergalactic Compute Bay (ICB), aka the datacenter. Your job is to take things to the next level, so you start by creating a Puppet module to manage space probes. Yeah, space probes.

Your manifest looks like this:

$ cat /etc/puppet/modules/voyager/manifests/init.pp

# Class: voyager
#
#   This module manages billion dollar space probes, use with caution.
#   Please do not send probes to non-planets, avoid things like black holes!
#
#   Tested probes:
#    - Voyager 1
#    - Voyager 2
#    - Voyager 3
#
# Parameters:
#  
#   $destination
#
# Actions:
#  
#  Configures and manages space probes.
#
# Example Usage:
# 
# class { "voyager":
#    destination => 'Mars'
# }
#
class voyager ($destination) {
  define space_mission ($destination) {
    # Top secret stuff we cannot show
    $message = "Sending probe to ${destination}"
    notify { $message: }
  }

  space_mission { 'voyager3':
    destination => $destination
  }
}

Wow, that’s awesome! You have a module that makes it easy to configure a space probe to go anywhere. Your well documented module even warns people that probes should not be sent into Black Holes. After a few rounds of QA, the voyager module is ready for production.

Egor Homakov, the new security lead, has reviewed your module and pointed out some possible bugs. You ignore him—your module is badass and passed QA. Plus, you give clear instructions on how to use it, right there in the module.

It’s show time. It’s time to launch 1000 new Voyager 3 space probes, and instead of using the old manual system of strategic button pushing, the Voyager Puppet module has been called to action.

Egor has volunteered to help out, his part of the configuration looks like this:

# lots of node definitions omitted
node 'voyager3.federation.org' {
  class { "voyager":
    destination => 'Sun'
  }
}
…

With little time to review each entry you spot check things and give the green light.

During the launch you notice this scroll by the screen:

notice: Sending probe to Sun
notice: /Stage[main]/Voyager/Voyager::Space_mission[voyager3]/Notify[Sending probe to Sun]/message: defined 'message' as 'Sending probe to Sun'
notice: Finished catalog run in 0.03 seconds

Yes my friend, you are indeed sending one of those billion dollar probes to the Sun. In case it’s not entirely obvious, the Sun is not a planet. Your attempt to play it off is short-lived; you just witnessed a space probe burst into flames!

You made two mistakes here. First you trusted Egor with your Puppet configurations, and second you did not validate the input for the $destination class parameter. At this point your only option is to resign. Yep, not even Puppet can help you recover from this.

Even though it’s to late for you, lets see how you could have saved your job.

class voyager ($destination) {
  $approved_planets = [
    'Mercury',
    'Venus',
    'Earth',
    'Mars',
    'Jupiter',
    'Saturn',
    'Uranus',
    'Neptune'
  ]

  validate_re($destination, $approved_planets)

  define space_mission ($destination) {
    # Top secret stuff we cannot show
    $message = "Sending probe to ${destination}"
    notify { $message: }
  }

  space_mission { 'voyager3':
    destination => $destination
  }
}

This time we catch the error before destroying a billion-dollar space probe: $ puppet agent -t … Error 400 on SERVER: validate_re(): "Sun" does not match ["Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune"] at /etc/puppet/modules/voyager/manifests/init.pp:37 on node voyager3.federation.org

More validate_re examples

The regular expressions can be more complex than we have shown so far. For example, if you wanted to validate a mac address you could do the following:

validate_re($macaddress, '^([0-9a-f]{2}[:]){5}([0-9a-f]{2})$')

You can also use the validate_re function in combination with other validation functions such as the validate_hash function like this:

$network_device = { macaddress => '00:0c:29:0d:1d:970' }
validate_hash($network_device)
validate_re($network_device['macaddress'], '^([0-9a-f]{2}[:]){5}([0-9a-f]{2})$')

The above validates that the macaddress key of the $network_device hash contains a valid mac address string value.

Reading and writing regular expressions can be a challenge, one tool that I use to help with the process is Rubular, an online Ruby regular expression editor:

Rubular allows me to play around with an expression until I get it right.

Conclusion

Using the validation functions provided by puppetlabs/stdlib can help catch issues quickly and prevent disaster from striking. Try not to get carried away by validating everything under the Sun; start with things that are critical and be sure to document what valid values should look like.

Join us next week as we continue our coverage of the puppetlabs/stdlib module with a quick look at facts-dot-d and discover how we can get Facts from external sources and cache them in a central location so Facter can find them.

Additional Resources:
Puppet sites use proprietary and third-party cookies. By using our sites, you agree to our cookie policy.