A collection of task oriented solutions in Puppet

 

Validate managed files

Challenge

You want to validate configuration files you are managing

Solution

class valid_config {

  file { '/etc/sudoers':
    ensure       => 'present',
    content      => "root    ALL=(ALL)       ALL",
    validate_cmd => '/sbin/visudo -cf %',
  }

}

# Notice: /Stage[main]/Main/Node[default]/File[/etc/sudoers]/ensure:
#   defined content as '{md5}a8f1c89c9a38b550bbb244c4e5053935'

Explanation

Puppet is a faithful tool that ensures all the file resources you tell it to manage are deployed. Unfortunately, by default, it's not very insightful one so if the provided file is invalid it will happily roll it to all the nodes, leaving a trail of failures in its wake. Luckily there's an attribute you can specify on your file resources, called validate_cmd, that can run a validation command against your files and will cause puppet to skip the resource, leaving the current one in place, and raise an error if the new file is invalid.

class invalid_sudo {

  file { '/etc/sudoers2':
    ensure       => 'present',
    content      => "root    ALL=(ALL)       ALL this should fail now",
    validate_cmd => '/sbin/visudo -cf %',
  }

}
Error: Execution of '/sbin/visudo -cf /etc/sudoers220171116-27989-25hozb'
  returned 1: /etc/sudoers220176-289-ozb: syntax error near line 1

parse error in /etc/sudoers220176-289-ozb: near line 1
-root    ALL=(ALL)       ALL
+root    ALL=(ALL)       ALL this should fail now

Error: /Stage[main]/Main/Node[default]/File[/etc/sudoers]/content: failed:
Execution of '/sbin/visudo -cf /etc/sudoers220176-289-ozb returned 1:
  /etc/sudoers220171116-27989-25hozb: syntax error near line 1
  parse error in /etc/sudoers220171116-27989-25hozb near line 1

If the validation command contains a literal % you can specify a second attribute to the resource, validate_replacement, and its value will be replaced in the command line instead. Here's an example of this in action:

class replaced_literal {

  file { '/etc/sudoers':
    ensure               => 'present',
    content              => "root    ALL=(ALL)       ALL",
    validate_cmd         => '/sbin/visudo -cf file_from_puppet',
    validate_replacement => 'file_from_puppet',
  }

}

It's worth noting that stdlib contains a function called validate_cmd which can be useful in certain circumstances but often does not work in the way people expect. The file resource validate_cmd we detail here is executed on each client and so has all of its environment available. The stdlib function based version runs on the server, as all functions do, and so gets all its values from there.

The last thing to consider is the validate command itself. You need to ensure, preferably in your puppet code, that any resources it needs, and how it gets installed, happen before the file resource is reached. In our case we need the sudo package to exist (which provides the visudo command) but if you're validating XML, JSON or similar then you are responsible for ensuring the tools you need are managed.

See also