Authors: Jeffrey McCune James Turnbull
We can also specify a value calleddefault
.
default => "ssh",
This value is used if no other listed selection matches. If we don’t specify adefault
value and no selection matches then thename
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 classmodule::params
, so in our current case it would be namedssh::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 ourssh::params
class we’ve created another type of conditional, thecase
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 athttp://docs.puppetlabs.com/guides/language_tutorial.html#case_statement
. Also available is an if/else syntax that you can read about athttp://docs.puppetlabs.com/guides/language_tutorial.html#ifelse_statement
.
And finally, we need to include our new class in thessh
class:\
class ssh {
include ssh::params, ssh::install, ssh::config, ssh::service
}
These includes tell Puppet that when you include thessh
module, you’re getting all of these classes.
FUNCTIONS
Theinclude
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 thegenerate
function that calls external commands and returns the result, and thenotice
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 athttp://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 athttp://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 thessh::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 ourPackage["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, heressh::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]
.
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 ourssh::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 thessh::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
andnotify
. 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, therequire
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.
Thenotify
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 thessh::service
class. In our current case, a “notification” will cause the service resources in thessh::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 asrequire
andnotify
. You can read about chaining athttp://docs.puppetlabs.com/guides/language_tutorial.html#chaining_resources
.
So why specify the wholessh::service
class rather than just theService["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 athttp://docs.puppetlabs.com/guides/language_tutorial.html#hashe
s.
We’d need to do that for every resource that required our packages, making ourrequire
statements cumbersome, potentially error prone, and most importantly requiring that every resource that requires packages be updated with any new package requirements.