Pro Puppet (13 page)

Read Pro Puppet Online

Authors: Jeffrey McCune James Turnbull

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

Every time you add a new module or file you will need to add it to Git using the
git add
command and then commit it to store it in the repository. I recommend you add and commit changes regularly to ensure you have sufficiently granular revisions to allow you to easily roll back to an earlier state.

Tip
If you’re interested in Git, we strongly recommend Scott Chacon’s excellent book
Pro Git
– also published by Apress. The book is available in both dead tree form and online at
http://progit.org/book/
. Scott is also one of the lead developers of the Git hosting site, GitHub –
http://www.github.com
, where you can find a number of Puppet related modules.

Our simple
sudo
module is a good introduction to Puppet, but it only showcased a small number of Puppet’s capabilities. It’s now time to expand our Puppet knowledge and develop some new more advanced modules, starting with one to manage SSH on our hosts. We’ll then create a module to manage Postfix on
mail.example.com
, one to manage MySQL on our Solaris host,
db.example.com
, another to manage Apache and web sites, and finally one to manage Puppet with Puppet itself.

We’ll also introduce you to some best practices for structuring, writing and managing modules and configuration.

Creating a module to Manage SSH

We know that we first need to create an appropriate module structure. We’re going to do this under the
/etc/puppet/modules
directory on our Puppet master.

$ cd /etc/puppet/modules
$ mkdir –p ssh/{manifests,templates,files}
$ touch ssh/manifests/init.pp

Next, we create some classes inside the
init.pp
file and some initial resources, as shown in
Listing 2-2
.

Listing 2-2.
The ssh module

class ssh::install {
  package { "openssh":
    ensure => present,
  }
}
class ssh::config {
  file { "/etc/ssh/sshd_config":
    ensure = > present,
    owner => 'root',
    group => 'root',
    mode => 0600,
    source => "puppet:///modules/ssh/sshd_config",
    require => Class["ssh::install"],
    notify => Class["ssh::service"],
  }
}
class ssh::service {
  service { "sshd":
    ensure => running,
    hasstatus => true,
    hasrestart => true,
    enable => true,
    require => Class["ssh::config"],
  }
}
class ssh {
  include ssh::install, ssh::config, ssh::service
}

We’ve created three classes:
ssh
,
ssh::install
,
ssh::config
, and
ssh::service
. As we mentioned earlier, modules can be made up multiple classes. We use the
::
namespace syntax as a way to create structure and organization in our modules. The
ssh
prefix tells Puppet that each class belongs in the
ssh
module, and the class name is suffixed.

Note
We’d also want to create a
sshd_config
file in the
ssh/files/
directory so that our
File["/etc/ssh/sshd_config"]
resource can serve out that file. The easiest way to do this is to copy an existing functional
sshd_config
file and use that. Later we’ll show you how to create template files that allow you to configure per-host configuration in your files. Without this file Puppet will report an error for this resource.

In
Listing 2-2
, we created a functional structure by dividing the components of the service we’re managing into functional domains: things to be installed, things to be configured and things to be executed or run.

Lastly, we created a class called
ssh
(which we need to ensure the module is valid) and used the
include
function to add all the classes to the module.

Managing Our Classes

Lots of classes with lots of resources in our
init.pp
file means that the file is going to quickly get cluttered and hard to manage. Thankfully, Puppet has an elegant way to manage these classes rather than clutter the
init.pp
file. Each class, rather than being specified in the
init.pp
file, can be specified in an individual file in the manifests directory, for example in a
ssh/manifests/install.pp
file that would contain the
ssh::install
class:

class ssh::install {
  package { "openssh":
    ensure => present,
  }
}

When Puppet loads the
ssh
module, it will search the path for files suffixed with
.pp
, look inside them for namespaced classes and automatically import them. Let’s quickly put our
ssh::config
and
ssh::service
classes into separate files:

$ touch ssh/manifests/{config.pp,service.pp}

This leaves our
init.pp
file containing just the
ssh
class:

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

Our
ssh
module directory structure will now look like:

ssh
ssh/files/sshd_config
ssh/manifests/init.pp
ssh/manifests/install.pp
ssh/manifests/config.pp
ssh/manifests/service.pp
ssh/templates

Neat and simple.

Tip
You can nest classes another layer, like
ssh::config::client
, and our auto-importing magic will still work by placing this class in the
ssh/manifests/config/client.pp
file.

The ssh::install Class

Now that we’ve created our structure, let’s look at the classes and resources we’ve created. Let’s start with the
ssh::install
class containing the
Package["openssh"]
resource, which installs the OpenSSH package.

It looks simple enough, but we’ve already hit a stumbling block – we want to manage SSH on all of Example.com’s hosts, and across these platforms the OpenSSH package has different names:

  • Red Hat: openssh-server
  • Ubuntu: openssh-server
  • Solaris: openssh

How are we going to ensure Puppet installs the correctly-named package for each platform? The answer lies with Facter, Puppet’s system inventory tool. During each Puppet run, Facter queries data about the host and sends it to the Puppet master. This data includes the operating system of the host, which is made available in our Puppet manifests as a variable called
$operatingsystem
. We can now use this variable to select the appropriate package name for each platform. Let’s rewrite our
Package["openssh"]
resource:

package { "ssh":
  name => $operatingsystem ?
    /(Red Hat|CentOS|Fedora|Ubuntu|Debian)/ => "openssh-server",
    Solaris => "openssh",
    },
  ensure => installed,
}

You can see we’ve changed the title of our resource to
ssh
and specified a new attribute called
name
. As we explained in
Chapter 1
, each resource is made up of a type, title and a series of attributes. Each resource’s attributes includes its “name variable,” or ”namevar,” and the value of this attribute is used to determine the name of the resource. For example, the Package and Service resources use the
name
attribute as their namevar while the File type uses the
path
attribute as its namevar. Most of the time we wouldn’t specify the namevar, as it is synonymous with the title, for example in this resource:

file { "/etc/passwd":
  …
}

We don’t need to specify the namevar because the value will be taken from the title,
"/etc/passwd"
. But often we’re referring to resources in many places and we might want a simple alias, so we can give the resource a title and specify its namevar this way:

file { "passwd":
  path => "/etc/passwd",
  …
}

We can now refer to this resource as
File["passwd"]
as an aliased short-hand.

Note
You should also read about the
alias
metaparameter, which provides a similar capability, at
http://docs.puppetlabs.com/references/latest/metaparameter.html#alias
.

In our current example, the name of the package we’re managing varies on different hosts. Therefore, we want to specify a generic name for the resource and a platform-selected value for the actual package to be installed.

You can see that inside this new
name
attribute we’ve specified the value of the attribute as
$operatingsystem
followed by a conditional syntax that Puppet calls a “selector.” To construct a selector, we specify the a variable containing the value we want to select on as the value of our attribute, here
$operatingsystem
, and follow this with a question mark (?). We then list on new lines a series of selections, for example if the value of
$operatingsystem
is Solaris, then the value of the
name
attribute will be set to
openssh,
and so on. Notice that we can specify multiple values in the form of simple regular expressions, like /(
Solaris|Ubuntu|Debian)/
.

Note
Selector matching is case-insensitive. You can also see some other examples of regular expressions in selectors at
http://docs.puppetlabs.com/guides/language_tutorial.html#selectors
.

Other books

El Avispero by Patricia Cornwell
Surreptitious (London) by Breeze, Danielle
Moominland Midwinter by Tove Jansson
Michaelmas by Algis Budrys
The Best I Could by R. K. Ryals
Tom All-Alone's by Lynn Shepherd
The Heiress by Jude Deveraux
Treasured by Sherryl Woods