Pro Puppet (12 page)

Read Pro Puppet Online

Authors: Jeffrey McCune James Turnbull

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

You can see that we’ve tried to define the
$package
variable twice. If we were to try to compile and apply this configuration, the Puppet agent would return the following error:

err: Cannot reassign variable package at /etc/puppet/modules/ssh/manifests/init.pp:5

Note
The error helpfully also tells us the file, and line number in the file, where we’ve tried to redefine the variable.

So what’s a scope? Each class, definition, or node introduces a new scope, and there is also a top scope for everything defined outside of those structures. Scopes are created hierarchically and the important thing you need to remember about scope hierarchy is that it is created when Puppet code is evaluated, rather than when it is defined, for example:

$package = "openssh"
class ssh {
  package { $package:
    ensure => installed,
  }
}
class ssh_server
  include ssh
  $package = "openssh-server"
}
include ssh_server

Here a top level scope, in which
$package
is defined, is present. Then there’s a scope for the
ssh_server
class and a scope below that for the
ssh
class. When Puppet runs the
$package
variable will have a value of
"openssh-server"
because this is what the variable was when evaluation occurred.

Naturally, in these different scopes, you can reassign the value of a variable:

class apache {
      $apache = 1
}
class passenger {
      $apache = 2
}

The same variable can be used and defined in both the
apache
and
passenger
classes without generating an error because they represent different scopes.

Going back to node inheritance, you can probably see how this dynamic scoping is going to be potentially confusing, for example:

class apache {
  $apacheversion = "2.0.33"
  package { "apache2":
    ensure => $apacheversion,
  }
}
node 'web.example.com' {
  include  apache
}
node 'web2.example.com' inherits 'web.example.com' {
  $apacheversion = "2.0.42"
}

Here we’ve created a class called
apache
and a package resource for the
apache2
package. We’ve also created a variable called
$apacheversion
and used that as the value of the
ensure
attribute of the package resource. This tells Puppet that we want to install version 2.0.33 of Apache. We’ve then included our
apache
class in a node,
web.example.com
.

But we’ve also decided to create another node,
web2.example.com
, which inherits the contents of the
web.example.com
node. In this case, however, we’ve decided to install a different Apache version and therefore we specified a new value for the
$apacheversion
variable. But instead of using this new value, Puppet will continue to install the 2.0.33 version of Apache because the
$apacheversion
variable is maintained in its original scope of the
web.example.com
node and the new variable value is ignored.

There is a work-around for this issue that you can see here:

class apache {
  $apacheversion = "2.0.33"
  package { "apache2":
    ensure => $apacheversion,
  }
}
class base {
  include  apache
}
node 'web.example.com' {
  $apacheversion = "2.0.42"
  include base
}

Instead of defining a
base
node we’ve defined a
base
class that includes the
apache
class. When we created our node, we specified the
$apacheversion
we want and then included the
base
class, ensuring we’re in the right scope. We could put other like items in our
base
class and specify any required variables.

Note
You can learn more about variable scoping, workarounds and related issues at
http://projects.puppetlabs.com/projects/puppet/wiki/Frequently_Asked_Questions#Common+Misconceptions
.

With Puppet installed and node definitions in place, we can now move on to creating our modules for the various hosts. But first, let’s do a quick refresher on modules in general.

Making (More) Magic With Modules

In
Chapter 1
, we learned about modules: self-contained collections of resources, classes, files that can be served, and templates for configuration files. We’re going to use several modules to define the various facets of each host’s configuration. For example, we will have a module for managing Apache on our Web server and another for managing Postfix on our mail server.

Recall that modules are structured collections of Puppet manifests. By default Puppet will search the module path, which is by default
/etc/puppet/modules/
and
/var/lib/puppet/modules
, for modules and load them. These paths are controlled by the
modulepath
configuration option. This means we don’t need to import any of these files into Puppet – it all happens automatically.

It’s very important that modules are structured properly. For example, our
sudo
module contains the following:

sudo/
sudo/manifests
sudo/manifests/init.pp
sudo/files
sudo/templates

Inside our
init.pp
we create a class with the name of our module:

class sudo {
configuration…
}

Lastly, we also discovered we can apply a module, like the
sudo
module we created in
Chapter 1
, to a node by using the
include
function like so:

node 'puppet.example.com' {
  include sudo
}

The included function adds the resources contained in a class or module, for example adding all the resources contained in the
sudo
module here to the node
puppet.example.com
.

Let’s now see how to manage the contents of our modules using version control tools as we recommended in
Chapter 1
.

Note
You don’t have to always create your own modules. The Puppet Forge at
http://forge.puppetlabs.com
contains a large collection of pre-existing modules that you can either use immediately or modify to suit your environment. This can make getting started with Puppet extremely simple and fast.

Version Controlling Your Modules

Because modules present self-contained collections of configuration, we also want to appropriately manage the contents of these modules, allowing us to perform change control. To manage your content, we recommend that you use a Version Control System or VCS.

Version control is the method most developers use to track changes in their application source code. Version control records the state of a series of files or objects and allows you to periodically capture that state in the form of a revision. This allows you to track the history of changes in files and objects and potentially revert to an earlier revision should you make a mistake. This makes management of our configuration much easier and saves us from issues like undoing inappropriate changes or accidently deleting configuration data.

In this case, we’re going to show you an example of managing your Puppet manifests with a tool called Git, which is a distributed version control system (DVCS). Distributed version control allows the tracking of changes across multiple hosts, making it easier to allow multiple people to work on our modules. Git is used by a lot of large development projects, such as the Linux kernel, and was originally developed by Linux Torvalds for that purpose. It’s a powerful tool but it’sd easy to learn the basic steps. You can obviously easily use whatever version control system suits your environment, for example many people use Subversion or CVS for the same purpose.

First, we need to install Git. On most platforms we install the
git
package. For example, on Red Hat and Ubuntu:

$ sudo yum install git

or,

$ sudo apt-get install git

Once Git is installed, let’s identify ourselves to Git so it can track who we are and associate some details with actions we take.

$ git config --global user.name "Your Name"
$ git config --global user.email [email protected]

Now let’s version control the path containing our modules, in our case
/etc/puppet/modules
. We change to that directory and then execute the
git
binary to initialize our new Git repository.

$ cd /etc/puppet/modules
$ git init

This creates a directory called
.git
in the
/etc/puppet/modules
directory that will hold all the details and tracking data for our Git repository.

We can now add files to this repository using the
git
binary with the
add
option.

$ git add *

This adds everything currently in our path to Git. You can also use
git
and the
rm
option to remove items you don’t want to be in the repository.

$ git rm filename

This doesn’t mean, however, that our modules are already fully tracked by our Git repository. Like Subversion and other version control systems, we need to “commit” the objects we’d like to track. The commit process captures the state of the objects we’d like to track and manage, and it creates a revision to mark that state. You can also create a file called
.gitignore
in the directory. Every file or directory specified in this file will be ignored by Git and never added.

Before we commit though, we can see what Git is about by using the
git status
command:

$ git status

This tells us that when we commit that Git will add the contents to the repository and create a revision based on that state.

Now let’s commit our revision to the repository.

$ git commit –a –m "This is our initial commit"

The
–m
option specifies a commit message that allows us to document the revision we’re about to commit. It’s useful to be verbose here and explain what you have changed and why, so it’s easier to find out what’s in each revision and make it easier to find an appropriate point to return to if required. If you need more space for your commit message you can omit the
–m
option and Git will open your default editor and allow you to type a more comprehensive message.

The changes are now committed to the repository and we can use the
git log
command to see our recent commit.

$ git log

We can see some information here about our commit. First, Git uses SHA1 hashes to track revisions; Subversion, for example, uses numeric numbers – 1, 2, 3, etc. Each commit has a unique hash assigned to it. We will also see some details about who created the commit and our commit message telling us what the commit is all about.

Other books

Home To India by Jacquelin Singh
Fragmentos de honor by Lois McMaster Bujold
The Seduction of Lady X by London, Julia
Shopaholic on Honeymoon by Sophie Kinsella
Catching Raven by Smith, Lauren
Hens and Chickens by Jennifer Wixson
Frameshift by Robert J Sawyer