Pro Puppet (14 page)

Read Pro Puppet Online

Authors: Jeffrey McCune James Turnbull

BOOK: Pro Puppet
6.95Mb size Format: txt, pdf, ePub

We can also specify a value called
default
.

default => "ssh",

This value is used if no other listed selection matches. If we don’t specify a
default
value and no selection matches then the
name
attribute would be set to a nil value.

As can you imagine, this requirement to select the appropriate value for a particular platform happens a lot. This means we could end up scattering a lot of very similar conditional statements across our Puppet code. That’s pretty messy; a best practice we recommend is to make this look a lot neater and more elegant by moving all your conditional checks to a separate class.

We usually call that class
module::params
, so in our current case it would be named
ssh::params
. Like before, we’re going to store that class in a separate file. Let’s create that file:

$ touch ssh/manifests/params.pp

We can see that class in
Listing 2-3
.

Listing 2-3.
The ssh::params class

class ssh::params {
  case $operatingsystem {
    Solaris: {
      $ssh_package_name = 'openssh'
    }
    /(Ubuntu|Debian)/: {
      $ssh_package_name = 'openssh-server'
    }
    /(RedHat|CentOS|Fedora)/: {
      $ssh_package_name = 'openssh-server'
    }
  }
}

You can see that inside our
ssh::params
class we’ve created another type of conditional, the
case
statement. Much like a selector, the case statement iterates over the value of a variable, here
$operatingsystem
. Unlike a selector, case statements allow us to specify a block of things to do if the value of the variable matches one of the cases. In our case we’re setting the value of a new variable we’ve created, called
$ssh_package_name
. You could do other things here, such as include a class or a resource, or perform some other function.

Note
You can read more about case statements at
http://docs.puppetlabs.com/guides/language_tutorial.html#case_statement
. Also available is an if/else syntax that you can read about at
http://docs.puppetlabs.com/guides/language_tutorial.html#ifelse_statement
.

And finally, we need to include our new class in the
ssh
class:\

class ssh {
  include ssh::params, ssh::install, ssh::config, ssh::service
}

These includes tell Puppet that when you include the
ssh
module, you’re getting all of these classes.

FUNCTIONS

The
include
directive we use to include our classes and modules is called a function. Functions are commands that run on the Puppet master to perform actions. Puppet has a number of other functions, including the
generate
function that calls external commands and returns the result, and the
notice
function that logs messages on the master and is useful for testing a configuration. For example:

notice("This is a notice message including the value of the $ssh_package variable")

Functions only run on the Puppet master and cannot be run on the client, and thus can only work with the resources available on the master.

You can see a full list of functions at
http://docs.puppetlabs.com/references/stable/function.html
and we’ll introduce you to a variety of other functions in subsequent chapters. You can also find some documentation on how to write your own functions at
http://projects.puppetlabs.com/projects/puppet/wiki/Writing_Your_Own_Functions
,
and we’ll talk about developing functions in
Chapter 10
.

We’re going to come back to the
ssh::params
class and add more variables as we discover other elements of our OpenSSH configuration that are unique to particular platforms, but for the moment how does including this new class change our
Package["ssh"]
resource?

package { $ssh::params::ssh_package_name:
  ensure => installed,
}

You can see our namespacing is useful for other things, here using variables from other classes. We can refer to a variable in another class by prefixing the variable name with the class it’s contained in, here
ssh::params
. In this case, rather than our messy conditional, the package name to be installed will use the value of the
$ssh::params::ssh_package_name
parameter. Our resource is now much neater, simpler and easier to read.

Tip
So how do we refer to namespaced resources? Just like other resources,
Package[$ssh::params::ssh_package_name]
.

The ssh::config Class

Now let’s move onto our next class,
ssh::config
, which we can see in
Listing 2-4
.

Listing 2-4.
The ssh::config class

class ssh::config {
  file { "/etc/ssh/sshd_config":
    ensure = > present,
    owner => 'root',
    group => 'root',
    mode => 0440,
    source => "puppet:///modules/ssh/sshd_config",
    require => Class["ssh::install"],
    notify => Class["ssh::service"],
  }
}

We know that the location of the sshd_config files will vary across different operating systems. Therefore, we’re going to have to add another conditional for the name and location of that file. Let’s go back to our
ssh::params
class from Example 2-3 and add a new variable:

class ssh::params {
  case $operatingsystem {
    Solaris {
      $ssh_package_name = 'openssh'
      $ssh_service_config = '/etc/ssh/sshd_config'
  }

}

We add the
$ssh_service_config
variable to each of the cases in our conditional and then update our file resource in the
ssh::config
class:

file { $ssh::params::ssh_service_config:
  ensure = > present,
  …
}

Again, we have no need for a messy conditional in the resource, we can simply reference the
$ssh::params::ssh_service_config
variable.

We can also see that the file resource contains two metaparameters,
require
and
notify
. These metaparameters both specify relationships between resources and classes. You’ll notice here that both metaparameters reference classes rather than individual resources. They tell Puppet that it should create a relationship between this file resource and every resource in the referenced classes.

Tip
It is a best practice to establish relationships with an entire class, rather than with a resource contained within another class, because this allows the internal structure of the class to change without refactoring the resource declarations related to the class.

For example, the
require
metaparameter tells Puppet that all the resources in the specified class must be processed prior to the current resource. In our example, the OpenSSH package must be installed before Puppet tries to manage the service’s configuration file.

The
notify
metaparameter creates a notification relationship. If the current resource (the service’s configuration file) is changed, then Puppet should notify all the resources contained in the
ssh::service
class. In our current case, a “notification” will cause the service resources in the
ssh::service
class restart, ensuring that if we change a configuration file that the service will be restarted and running with the correct, updated configuration.

Tip
In Puppet 2.6.0, a shorthand method called “chaining” was introduced for specifying metaparameter relationships, such as
require
and
notify
. You can read about chaining at
http://docs.puppetlabs.com/guides/language_tutorial.html#chaining_resources
.

So why specify the whole
ssh::service
class rather than just the
Service["sshd"]
resource? This is another piece of simple best practice that allows us to simplify maintaining our classes and the relationships between them. Imagine that, instead of a single package, we had twenty packages. If we didn’t require the class then we’d need to specify each individual package in our require statement, like this:

require => [ Package["package1"], Package["package2"], Package["package3"] ],

Note
Adding [ ]s around a list creates a Puppet array. You can specify arrays as the values of variables and many attributes; for example, you can specify many items in a single resource:
package { [ "package1", "package2", "package3" ]: ensure => installed }
. In addition to arrays, Puppet also supports a hash syntax, which you can see at
http://docs.puppetlabs.com/guides/language_tutorial.html#hashe
s.

We’d need to do that for every resource that required our packages, making our
require
statements cumbersome, potentially error prone, and most importantly requiring that every resource that requires packages be updated with any new package requirements.

Other books

Silent Court by M. J. Trow
Bringing Elizabeth Home by Ed Smart, Lois Smart
Centralia by Mike Dellosso
Gentleman Takes a Chance by Sarah A. Hoyt
Blood Donors by Steve Tasane
Trial & Error by Paul Levine
Desiring the Enemy by Lavelle, Niecy
Blood Vengeance by L.E. Wilson
Borderline by Mishell Baker