Exploring the standard library

One of the oldest-established Puppet Forge modules is puppetlabs/stdlib, the official Puppet standard library. We looked at this briefly earlier in the chapter when we used it as an example of installing a module with r10k, but let's look more closely now and see what the standard library provides and where you might use it.

Rather than managing some specific software or file format, the standard library aims to provide a set of functions and resources which can be useful in any piece of Puppet code. Consequently, well-written Forge modules use the facilities of the standard library rather than implementing their own utility functions which do the same thing. You should do the same in your own Puppet code—when you need a particular piece of functionality, check the standard library first to see if it solves your problem, rather than implementing it yourself.

Safely installing packages with ensure_packages

As you know, you can install a package using the package resource, like this (package.pp):

package { 'cowsay':
  ensure => installed,
}

But what happens if you also install the same package in another class in a different part of your manifest? Puppet will refuse to run, with an error like this:

Error: Evaluation Error: Error while evaluating a Resource Statement, Duplicate declaration: Package[cowsay] is already declared in file /vagrant/examples/package.pp:1; cannot redeclare at /vagrant/examples/package.pp:4 at /vagrant/examples/package.pp:4:1 on node localhost

If both of your classes really require the package, then you have a problem. You could create a class which simply declares the package, and then include that in both classes, but that is a lot of overhead for a single package. Worse, if the duplicate declaration is in a third-party module, it may not be possible, or advisable, to change that code.

What we need is a way to declare a package which will not cause a conflict if that package is also declared somewhere else. The standard library provides this facility in the ensure_packages() function. Call ensure_packages() with an array of package names, and they will be installed if they are not already declared elsewhere (package_ensure.pp):

ensure_packages(['cowsay'])

If you need to pass additional attributes to the package resource, you can supply them in a hash as the second argument to ensure_packages(), like this (package_ensure_params.pp):

ensure_packages(['rake'],
  { 'provider' => 'gem' })

Why is this better than using the package resource directly? When you declare the same package resource in more than one place, Puppet will give an error message and refuse to run. If the package is declared by ensure_packages(), however, Puppet will run successfully.

Since it provides a safe way to install packages without resource conflicts, you should always use ensure_packages() instead of the built-in package resource. It is certainly essential if you're writing modules for public release, but I recommend you use it in all your code. We'll use it to manage packages throughout the rest of this book.

Modifying files in place with file_line

Often when managing configuration with Puppet, we would like to change or add a particular line to a file, without incurring the overhead of managing the whole file with Puppet. Sometimes it may not be possible to manage the whole file in any case, as another Puppet class or another application may be managing it. We could write an exec resource to modify the file for us, but the standard library provides a resource type for exactly this purpose: file_line.

Here's an example of using the file_line resource to add a single line to a system config file (file_line.pp):

file_line { 'set ulimits':
  path => '/etc/security/limits.conf',
  line => 'www-data         -       nofile          32768',
}

If there is a possibility that some other Puppet class or application may need to modify the target file, use file_line instead of managing the file directly. This ensures that your class won't conflict with any other attempts to control the file.

You can also use file_line to find and modify an existing line, using the match attribute (file_line_match.pp):

file_line { 'set root email alias':
  path  => '/etc/aliases',
  line  => 'root: [email protected]',
  match => '^root: ',
}

The value of match is a regular expression, and if Puppet finds a line in the file which matches this expression, it will replace it with the value of line. (If you need to potentially change multiple lines, set the multiple attribute to true, or Puppet will complain when more than one line matches the expression.)

You can also use file_line to delete a line in a file if it is present (file_line_absent.pp):

file_line { 'remove csh from valid shells':
  ensure            => absent,
  path              => '/etc/shells',
  match             => '^/bin/csh',
  match_for_absence => true,
}

Note

When using ensure => absent, you also need to set the match_for_absence attribute to true if you want Puppet to actually delete matching lines.

Introducing some other useful functions

The grep() function will search an array for a regular expression and return all matching elements (grep.pp):

$values = ['foo', 'bar', 'baz']
notice(grep($values, 'ba.*'))

# Result: ['bar', 'baz']

The member() and has_key() functions return true if a given value is in the specified array or hash, respectively (member_has_key.pp):

$values = ['foo', 'bar', 'baz']
notice(member($values, 'foo'))

# Result: true

$valuehash = { 'a' => 1, 'b' => 2, 'c' => 3 }
notice(has_key($valuehash, 'b'))

# Result: true

The empty() function returns true if its argument is an empty string, array, or hash (empty.pp):

notice(empty(''))

# Result: true

notice(empty([]))

# Result: true

notice(empty({}))

# Result: true

The join() function joins together the elements of a supplied array into a string, using a given separator character or string (join.pp):

$values = ['1', '2', '3']
notice(join($values, '... '))

# Result: '1... 2... 3'

The pick() function is a neat way to provide a default value when a variable happens to be empty. It takes any number of arguments, and returns the first argument which is not undefined or empty (pick.pp):

$remote_host = ''
notice(pick($remote_host, 'localhost'))

# Result: 'localhost'

Sometimes you need to parse structured data in your Puppet code which comes from an outside source. If that data is in YAML format, you can use the loadyaml() function to read and parse it into a native Puppet data structure (loadyaml.pp):

$db_config = loadyaml('/vagrant/examples/files/database.yml')
notice($db_config['development']['database'])

# Result: 'dev_db'

The dirname() function is very useful if you have a string path to a file or directory and you want to reference its parent directory; for example, to declare it as a Puppet resource (dirname.pp):

$file = '/var/www/vhosts/mysite'
notice(dirname($file))

# Result: '/var/www/vhosts'

The pry debugger

When a Puppet manifest doesn't do quite what you expect, troubleshooting the problem can be difficult. Printing out the values of variables and data structures with notice() can help, as can running puppet apply -d to see detailed debug output, but if all else fails, you can use the standard library's pry() method to enter an interactive debugger session (pry.pp):

pry()

With the pry gem installed in Puppet's context, you can call pry() at any point in your code. When you apply the manifest, Puppet will start an interactive Pry shell at the point where the pry() function is called. You can then run the catalog command to inspect Puppet's catalog, which contains all the resources currently declared in your manifest:

sudo puppet apply /vagrant/examples/pry_install.pp
sudo puppet apply /vagrant/examples/pry.pp
...
[1] pry(#<Puppet::Parser::Scope>)> catalog
=> #<Puppet::Resource::Catalog:0x00000001bbcf78
...
 @resource_table={["Stage", "main"]=>Stage[main]{}, ["Class", "Settings"]=>Class[Settings]{}, ["Class", "main"]=>Class[main]{}},
 @resources=[["Stage", "main"], ["Class", "Settings"], ["Class", "main"]],
...

Once you've finished inspecting the catalog, type exit to quit the debugger and continue applying your Puppet manifest.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.14.69.119