Manifests and classes
Quest objectives
- Understand the concept of a Puppet manifest
- Construct and apply manifests to manage resources
- Understand what a class means in Puppet's Language
- Learn how to use a class definition
- Understand the difference between defining and declaring a class
Getting started
In the Resources quest you learned about resources and the syntax used to
declare them. You used the puppet resource
, puppet describe
, and puppet apply
tools to inspect, learn about, and change resources on the system. In this quest,
we're going to cover two key Puppet concepts that will help you organize and
implement your resource declarations: classes and manifests. Proper use
of classes and manifests is the first step towards writing testable and reusable
Puppet code.
When you're ready to get started, enter the following command:
quest begin manifests_and_classes
Manifests
Imagination is a force that can actually manifest a reality.
-James Cameron
At its simplest, a manifest is nothing more than some puppet code
saved to a file with the .pp
extension. It's the same stuff you saw using
the puppet resource
tool and applied with the puppet apply
tool. Easy
enough, but it's where you put a manifest and what you put in it that really
matter.
Much of this organizational structure, both in terms of a manifest's content and its location on the puppet master's filesystem, is related to Puppet classes.
Classes
In Puppet's DSL a class is a named block of Puppet code. The class is the
next level of abstraction above a resource. A class declares a set of resources
related to a single system component. As you saw when you applied the graphite
class in the Power of Puppet quest, class parameters allow you to adapt a class
to suit your needs. We'll cover class parameters in depth in a later quest.
For now, we'll focus on how the abstraction provided by classes allows you
to manage complex sets of resources in terms of a single function they serve.
Using a Puppet class requires two steps. First, you must define it by writing a class definition and saving it to a manifest file. Puppet will parse this manifest and remember your class definition. The class can then be declared to apply the resource declarations it contains to a node in your infrastructure.
There are several ways to tell Puppet where and how to apply classes to nodes. You already saw the PE Console's node classifier in the Power of Puppet quest, and we'll discuss other methods of node classification in a later quest. For now we'll show you how to write class definitions and use test manifests to declare these classes locally.
One more note on the topic of classes: In Puppet, classes are singleton, which means that a class can only be declared once on a given node. In this sense, Puppet's classes are different than the kind of classes you may have encountered in object-oriented programming, which are often instantiated multiple times. Declaring a class multiple times could give Puppet conflicting instructions for how to manage resources on a system.
Cowsayings
You had a taste of how Puppet can manage users in the Resources quest. In this quest we'll use the package resource as our example.
First, you'll use Puppet to manage the cowsay package. Cowsay lets you print a message in the speech bubble of an ASCII cow. It may not be mission critical software (unless your mission involves lots of ASCII cows!), but it works well as a simple example. You'll also install the fortune package, which will give you and your cow access to a database of sayings and quotations.
We've already prepared a cowsayings
module directory in Puppet's modulepath,
and included two subdirectories: manifests
and examples
. Before getting started
writing manifests, change directories to save yourself some typing:
cd /etc/puppetlabs/code/environments/production/modules
Cowsay
Let's start with cowsay. To use the cowsay
command, you need to have the cowsay
package installed. You can use a package
resource to handle this installation,
but you don't want to put that resource declaration just anywhere.
Task 1:
To keep things tidy, we'll create a cowsay.pp
manifest, and within that manifest
we'll define a class that can manage the cowsay package.
Use vim to create a cowsay.pp
manifest:
vim cowsayings/manifests/cowsay.pp
Enter the following class definition, then save and exit (:wq
):
class cowsayings::cowsay {
package { 'cowsay':
ensure => present,
provider => 'gem',
}
}
Now that you're working with manifests, you can validate your code before you apply
it. Use the puppet parser
tool to check the syntax of your new manifest:
puppet parser validate cowsayings/manifests/cowsay.pp
The parser will return nothing if there are no errors. If it does detect a syntax error, open the file again and fix the problem before continuing.
If you try to directly apply your new manifest, nothing on the system will change. (Give it a shot if you like.) This is because you have defined a cowsay class, but haven't declared it anywhere. Puppet knows that the cowsay class contains a resource declaration for the cowsay package, but hasn't yet been told to do anything with it.
Task 2:
If you were going to apply this code to your production infrastructure, you would use
the console's node classifier to classify any nodes that needed cowsay installed with
the cowsay with your cowsay class. As you're working on a module, however, it's useful
to apply a class directly. By convention, these test manifests are kept in an examples
directory. (You may also sometimes see these manifests in the tests
directory.)
To actually declare the class, create a cowsay.pp
test in the examples directory.
vim cowsayings/examples/cowsay.pp
In this manifest, declare the cowsay class with the include
keyword.
include cowsayings::cowsay
Save and exit.
Before applying any changes to your system, it's always a good idea to use the
--noop
flag to do a 'dry run' of the Puppet agent. This will compile the
catalog and notify you of the changes that Puppet would have made without
actually applying any of those changes to your system.
puppet apply --noop cowsayings/examples/cowsay.pp
(If you're running offline or have restrictive firewall rules, you may need to
manually install the gems from the local cache on the VM. In a real
infrastructure, you might consider setting up a local rubygems mirror with a
tool such as Stickler.
gem install --local --no-rdoc --no-ri /var/cache/rubygems/gems/cowsay-*.gem
)
You should see an output like the following:
Notice: Compiled catalog for learn.localdomain in environment production in
0.62 seconds
Notice: /Stage[main]/Cowsayings::Cowsay/Package[cowsay]/ensure: current_value
absent, should be present (noop)
Notice: Class[Cowsayings::Cowsay]: Would have triggered 'refresh' from 1
events
Notice: Stage[main]: Would have triggered 'refresh' from 1 events
Notice: Finished catalog run in 1.08 seconds
Task 3:
If your dry run looks good, go ahead and run puppet apply
again without the
--noop
flag. If everything went according to plan, the cowsay package is now
installed on the Learning VM. Give it a try!
cowsay Puppet is awesome!
Your bovine friend clearly knows what's up.
____________________
< Puppet is awesome! >
--------------------
\ ^__^
\ (oo)\_______
(__)\ )\/\
||----w |
|| ||
Fortune
But this module isn't just about cowsay; it's about cow sayings. With the fortune package, you can provide your cow with a whole database of wisdom.
Task 4:
Create a new manifest for your fortune class definition:
vim cowsayings/manifests/fortune.pp
Write your class definition here:
class cowsayings::fortune {
package { 'fortune-mod':
ensure => present,
}
}
Task 5:
Again, you'll want to validate your new manifests syntax with the puppet parser
validate
command. When everything checks out, you're ready to make your test
manifest:
vim cowsayings/examples/fortune.pp
As before, use include
to declare your cowsayings::fortune
class.
Task 6:
Apply the cowsayings/examples/fortune.pp
manifest with the --noop
flag. If
everything looks good, apply again without the flag.
Now that you have both packages installed, you can use them together. Try piping
the output of the fortune
command to cowsay
:
fortune | cowsay
So you've installed two packages that can work together to do something more interesting than either would do on its own. This is a bit of a silly example, of course, but it's not so different than, say, installing packages for both Apache and PHP on a webserver.
Main class: init.pp
Often a module will gather several classes that work together into a single class to let you declare everything at once.
Before creating the main class for cowsayings, however, a note on scope. You
may have noticed that the classes you wrote for cowsay and fortune were both
prepended by cowsayings::
. When you declare a class, this scope syntax tells
Puppet where to find that class; in this case, it's in the cowsayings module.
For the main class of a module, things are a little different. The main
class shares the name of the module itself, but instead of following the pattern of
naming the manifest for the class it contains, Puppet recognizes the special
file name init.pp
for the manifest that will contain a module's
main class.
Task 7:
So to contain your main cowsayings
class, create an init.pp
manifest in the
cowsayings/manifests
directory:
vim cowsayings/manifests/init.pp
Here, you'll define the cowsayings
class. Within it, use the same
include
syntax you used in your tests to declare the cowsayings::cowsay
and
cowsayings::fortune
classes.
class cowsayings {
include cowsayings::cowsay
include cowsayings::fortune
}
Save the manifest, and check your syntax with the puppet parser
tool.
Task 8:
At this point, you already have both packages you want installed on the
Learning VM. Applying the changes again wouldn't actually do anything. For the
sake of testing, you can use the puppet resource
tool to delete them so
you can try out the functionality of your new cowsayings
class:
puppet resource package fortune-mod ensure=absent
puppet resource package cowsay ensure=absent provider=gem
Next, create a test for the init.pp
manifest in the examples directory.
vim cowsayings/examples/init.pp
Here, just declare the cowsayings
class:
include cowsayings
Task 9:
Good. Now that the packages are gone, do a --noop
first, then apply your
cowsayings/examples/init.pp
test.
Review
We covered a lot in this quest. We promised manifests and classes, but you got a little taste of how Puppet modules work as well.
A class is a collection of related resources and other classes which, once defined, can be declared as a single unit. Puppet classes are also singleton, which means that unlike classes in object oriented programming, a Puppet class can only be declared a single time on a given node.
A manifest is a file containing Puppet code, and appended with the .pp
extension. In this quest, we used manifests in the ./manifests
directory each
to define a single class, and used a corresponding test manifest in the
./examples
directory to declare each of those classes.
There are also a few details about classes and manifests we haven't gotten to just yet. As we mentioned in the Power of Puppet quest, for example, classes can also be declared with parameters to customize their functionality. Don't worry, we'll get there soon enough!