Authors: Jeffrey McCune James Turnbull
By requiring the whole class, it doesn’t matter how many packages we add to thessh::install
class – Puppet knows to install packages before managing configuration files, and we don’t have to update a lot of resources every time we make a change.
Tip
In our current example we could make use of arrays to extend the variables in thessh::params
class. For example, by changing$ssh_package_name
to an array, we could specify multiple packages to be installed without needing to create another Package resource in thessh::install
class. Puppet is smart enough to know that if you specify a variable with a value of an array then it should expand the array, so changing the value of the$ssh_package_name
variable to[ openssh, package2, package3 ]
would result in thessh::install
class installing all three packages. This greatly simplifies the maintenance of ourssh
module, as we only need to change values in one place to manage multiple configuration items.
Let’s look at our last class,ssh::service
, and update it to reflect our new practice:
class ssh::service {
service { $ssh::params::ssh_service_name:
ensure => running,
hasstatus => true,
hasresstart => true,
enable => true,
require => Class["ssh::config"],
}
}
We’ve added our new variable,$ssh_service_name
, to thessh:params
class too:
class ssh::params {
case $operatingsystem {
Solaris {
$ssh_package_name = 'openssh'
$ssh_service_config = '/etc/ssh/sshd_config'
$ssh_service_name = 'sshd'
}
…
}
Let’s also look at ourService[$ssh::params::ssh_service_name]
resource (at the start of this section), as this is the first service we’ve seen managed. You’ll notice two important attributes,ensure
andenable
, which specify the state and status of the resource respectively. The state of the resource specifies whether the service is running or stopped. The status of the resource specifies whether it is to be started at boot, for example as controlled by thechkconfig
orenable-rc.d
commands.
Puppet understands how to manage a variety of service frameworks, like SMF and init scripts, and can start, stop and restart services. It does this by attempting to identify the service framework your platform uses and executing the appropriate commands. For example, on Red Hat it might execute:
$ service sshd restart
If Puppet can’t recognize your service framework, it will revert to simple parsing of the process table for processes with the same name as the service it’s trying to manage. This obviously isn’t ideal, so it helps to tell Puppet a bit more about your services to ensure it manages them appropriately. Thehasstatus
andhasrestart
attributes we specified in thessh::service
class is one of the ways we tell Puppet useful things about our services. If we specifyhasstatus
as true, then Puppet knows that our service framework supports status commands of some kind. For example, on Red Hat it knows it can execute the following:
$ service sshd status
This enables it to determine accurately whether the service is started or stopped. The same principle applies to thehasrestart
attribute, which specifies that the service has a restart command.
Now we can see Puppet managing a full service, if we include our newssh
module in our Puppet nodes, as shown in
Listing 2-5
.
Listing 2-5.
Adding thessh
Module
class base {
include sudo, ssh
}
node 'puppet.example.com' {
include base
}
node 'web.example.com' {
include base
}
node 'db.example.com' {
include base
}
node 'mail.example.com' {
include base
}
Here we’ve created a class calledbase
, in which we’re going to place the modules that will be base or generic to all our nodes. Thus far, these are oursudo
andssh
modules. We theninclude
this class in each node statement.
Note
We talked earlier about node inheritance and some of its scoping issues. As we explained there, using a class instead of node inheritance helps avoids these issues. You can read about it athttp://projects.puppetlabs.com/projects/puppet/wiki/Frequently_Asked_Questions#Common+Misconceptions
.
With a basic SSH module in place, and we can now manage the SSH daemon and its configuration.
Let’s now create a module to manage Postfix onmail.example.com
. We start with a similar structure to our SSH module. In this case, we know which platform we’re going to install our mail server on so we don’t need to include any conditional logic. However, if we had multiple mail servers on different platforms, it would be easy to adjust our module using the example we’ve just shown to cater for disparate operations systems.
postfix
postfix/files/master.cf
postifx/manifests/init.pp
postfix/manifests/install.pp
postfix/manifests/config.pp
postfix/manifests/service.pp
postfix/templates/main.cf.erb
We also have some similar resources present in our Postfix module that we saw in our SSH module, for example in thepostfix::install
class we install two packages,postfix
andmailx
:
class postfix::install {
package { [ "postfix", "mailx" ]:
ensure => present,
}
}
Note that we’ve used an array to specify both packages in a single resource statement this is a useful shortcut that allows you specify multiple items in a single resource.
Next, we have thepostfix::config
class, which we will use to configure our Postfix server.
class postfix::config {
File {
owner => "postfix",
group => "postfix",
mode => 0644,
}
file { "/etc/postfix/master.cf":
ensure = > present,
source => "puppet:///modules/postfix/master.cf",
require => Class["postfix::install"],
notify => Class["postfix::service"],
}
file { "/etc/postfix/main.cf":
ensure = > present,
content => template("postfix/main.cf.erb"),
require => Class["postfix::install"],
notify => Class["postfix::service"],
}
}
You may have noticed some new syntax: We specified the File resource type capitalized and without a title. This syntax is called a resource default, and it allows us to specify defaults for a particular resource type. In this case, all File resources within thepostfix::config
class will be owned by the userpostfix
, the grouppostfix
and with a mode of0644
. Resource defaults only apply to the current scope, but you can apply global defaults by specifying them in yoursite.pp
file.
A common use for global defaults is to define a global “filebucket” for backing up the files Puppet changes. You can see the filebucket type and an example of how to use it globally athttp://docs.puppetlabs.com/references/stable/type.html#filebucket
.
Tip
A common use for global defaults is to define a global “filebucket” for backing up the files Puppet changes. You can see the filebucket type and an example of how to use it globally athttp://docs.puppetlabs.com/references/stable/type.html#filebucket
.
METAPARAMETER DEFAULTS
Like resource defaults, you can also set defaults for metaparameters, such asrequire
, using Puppet variable syntax. For example:
class postfix::config {
$require = Class["postfix::install"]
…
}
This would set a default for therequire
metaparameter in thepostfix::config
class and means we could remove all therequire => Class["postfix::install"]
statements from our resources in that class.
We’ve also introduced a new attribute in ourFile["/etc/postfix/main.cf"]
resource –content
. We’ve already seen thesource
attribute, which allows Puppet to serve out files, and we’ve used it in one of our File resources,File["/etc/postfix/master.cf"]
. Thecontent
attribute allows us to specify the content of the file resources as a string. But it also allows us to specify a template for our file. The template is specified using a function calledtemplate
.
As previously mentioned, functions are commands that run on the Puppet master and return values or results. In this case, thetemplate
function allows us to specify a Ruby ERB template (http://ruby-doc.org/stdlib/libdoc/erb/rdoc/
), from which we can create the templated content for our configuration file. We specify the template like this:
content => template("postfix/main.cf.erb"),
We’ve specified the name of the function, “template,” and inside brackets the name of the module that contains the template and the name of the template file. Puppet knows when we specify the name of the module to look inside thepostfix/templates
directory for the requisite file – here,main.cf.erb
.
THE REQUIRE FUNCTION
In addition to theinclude
function, Puppet also has a function calledrequire
. Therequire
function works just like theinclude
function except that it introduces some order to the inclusion of resources. With theinclude
function, resources are not included in any sequence. The only exception is individual resources, which have relationships (using metaparameters, for example) that mandate some ordering. Therequire
function tells Puppet that all resources being required must be processed first. For example, if we specified:
class ssh {
require ssh::params
include ssh::install, ssh::config, ssh::service
}
then the contents ofssh::params
would be processed before any other includes or resources in thessh
class. This is useful as a simple way to specify some less granular ordering to your manifests than metaparameter relationships, but it’s not
recommended as a regular approach. The reason it is not recommended is that Puppet does this by creating relationships between all the resources in the required class and the current class. This can lead to cyclical dependencies between resources. It’s cleaner, more elegant and simpler to debug if you use metaparameters to specify the relationships between resources that need order.