Modules
Quest objectives
- Understand the purpose of Puppet modules
- Learn the module directory structure
- Write and test a simple module
Getting started
If you want to get things done efficiently in Puppet, the module will be your best friend. You got a little taste of module structure in the Manifests and Classes quest. In this quest, we'll take you deeper into the details.
In short, a Puppet module is a self-contained bundle of all the Puppet code and other data needed to manage some aspect of your configuration. In this quest, we'll go over the purpose and structure of Puppet modules, before showing you how to create your own.
When you're ready, type the following command:
quest begin modules
Why meddle with modules?
Creation always builds upon something else. There is no art that doesn't reuse.
-Lawrence Lessig
There's no hard-and-fast technical reason why you can't toss all the resource declarations for a node into one massive class. But this wouldn't be very Puppetish. Puppet's not just about bringing your nodes in line with a desired configuration state; it's about doing this in a way that's transparent, repeatable, and as painless as possible.
Modules allow you to organize your Puppet code into units that are testable, reusable, and portable, in short, modular. This means that instead of writing Puppet code from scratch for every configuration you need, you can mix and match solutions from a few well-written modules. And because these modules are separate and self-contained, they're much easier to test, maintain, and share than a collection of one-off solutions.
At their root, modules are little more than a structure of directories and files that follow Puppet's naming conventions. The module file structure gives Puppet a consistent way to locate whatever classes, files, templates, plugins, and binaries are required to fulfill the function of the module.
Modules and the module directory structure also provide an important way to manage scope within Puppet. Keeping everything nicely tucked away in its own module means you have to worry much less about name collisions and confusion.
Finally, because modules are standardized and self-contained, they're easy to share. Puppet Labs hosts a free service called the Forge where you can find a wide array of modules developed and maintained by others.
The modulepath
All modules accessible by your Puppet Master are located in the directories specified by the modulepath variable in Puppet's configuration file.
Task 1:
You can find the modulepath on your puppet master by running the
puppet master
command with the --configprint
flag and the
modulepath
argument:
puppet master --configprint modulepath
This will tell you that Puppet looks in the directories
/etc/puppetlabs/code/environments/production/modules
,
/etc/puppetlabs/code/modules
, and then in
/opt/puppetlabs/puppet/modules
to find available modules.
Throughout the quests in the Learning VM, you will work in the
/etc/puppetlabs/code/environments/production/modules
directory. This
is where you keep modules for your production environment. (Site specific
modules you need to be available for all environments are kept in
/etc/puppetlabs/code/modules
, and modules required by Puppet Enterprise
itself are kept in the /opt/puppetlabs/puppet/modules
directory.)
Module structure
Now that you have an idea of why modules are useful and where they're kept, it's time to delve a little deeper into the anatomy of a module.
A module consists of a pre-defined structure of directories that help Puppet reliably locate the module's contents.
Use the the puppet module list
command to see what modules are already
installed. You'll probably recognize some familiar names from previous quests.
To get a clear picture of the directory structure of the modules here, you can use
a couple of flags with the tree
command to limit the output to directories, and
limit the depth to two directories.
tree -L 2 -d /etc/puppetlabs/code/environments/production/modules/
You'll see a list of directories, something like this:
/etc/puppetlabs/code/environments/production/modules/
├── cowsayings
│ ├── manifests
│ └── examples
└── graphite
├── manifests
├── spec
└── templates
Each of the standardized subdirectory names you see tells Puppet users and Puppet itself where to find each of the various components that come together to make a complete module.
Now that you have an idea of what a module is and what it looks like, you're ready to make your own.
You've already had a chance to play with the user and package resources in previous quests, so this time we'll focus on the file resource type. The file resource type is also a nice example for this quest because Puppet uses some URI abstraction based on the module structure to locate the sources for files.
The module you'll make in this quest will manage some settings for Vim, the text editor you've been using to write your Puppet code. Because the settings for services and applications are often set in a configuration file, the file resource type can be very handy for managing these settings.
Change your working directory to the modulepath if you're not already there.
cd /etc/puppetlabs/code/environments/production/modules
Task 2:
The top directory will be the name you want for the module. In this case, let's
call it "vimrc." Use the mkdir
command to create your module directory:
mkdir vimrc
Task 3:
Now you need three more directories, one for manifests, one for examples, and one for files.
mkdir vimrc/{manifests,examples,files}
If you use the tree vimrc
command to take a look at your new module, you
should now see a structure like this:
vimrc
├── files
├── manifests
└── examples
3 directories, 0 files
Managing files
We've already set up the Learning VM with some custom settings for Vim. Instead
of starting from scratch, you can copy the existing .vimrc
file into the files
directory of your new module. Any file in the files
directory of a module in
the Puppet master's modulepath will be available to client nodes through
Puppet's built-in fileserver.
Task 4:
Copy the existing .vimrc
file to your module's files
directory:
cp ~/.vimrc vimrc/files/vimrc
Task 5:
Once you've copied the file, open so you can make an addition.
vim vimrc/files/vimrc
We'll keep things simple. By default, line numbering is disabled. Add the following line to the end of the file to tell Vim to turn on line numbering.
set number
Save and exit.
Task 6:
Now that your source file is ready, you need to write a manifest to tell puppet what to do with it.
Remember, the manifest that includes the main class for a module is always
called init.pp
. Create the init.pp
manifest in your module's manifests
directory.
vim vimrc/manifests/init.pp
The Puppet code you put in here will be pretty simple. You need to define a
class vimrc
, and within it, make a file resource declaration to tell Puppet
to take the vimrc/files/vimrc
file from your module and use Puppet's file
server to push it out to the specified location.
In this case, the .vimrc
file that defines your Vim settings lives in the
/root
directory. This is the file you want Puppet to manage, so its full path
(i.e. /root/.vimrc
) will be the title of the file resource you're declaring.
This resource declaration will then need two attribute value pairs.
First, as with the other resource types you've encountered, ensure =>
present,
tells Puppet to ensure that the entity described by the resource
exists on the system.
Second, the source
attribute tells Puppet what the managed file should
actually contain. The value for the source attribute should be the URI of the
source file.
All Puppet file server URIs are structured as follows:
puppet://{server hostname (optional)}/{mount point}/{remainder of path}
However, there's some URI abstraction magic built in to Puppet that makes these URIs more concise.
First, the optional server hostname is nearly always omitted, as it defaults to
the hostname of the Puppet master. Unless you need to specify a file server
other than the Puppet master, your file URIs should begin with a triple forward
slash, like so: puppet:///
.
Second, nearly all file serving in Puppet is done through modules. Puppet
provides a couple of shortcuts to make accessing files in modules simpler.
First, Puppet treats modules
as a special mount point that will point to the
Puppet master's modulepath. So the first part of the URI will generally look
like puppet:///modules/
Finally, because all files to be served from a module must be kept in the
module's files
directory, this directory is implicit and is left out of the
URI.
So while the full path to the vimrc source file is
/etc/puppetlabs/code/environments/production/modules/vimrc/files/vimrc
,
Puppet's URI abstraction shortens it to /modules/vimrc/vimrc
. Combined with
the implicit hostname, then, the attribute value pair for the source URI is:
source => 'puppet:///modules/vimrc/vimrc',
Putting this all together, your init.pp
manifest should contain the following:
class vimrc {
file { '/root/.vimrc':
ensure => present,
source => 'puppet:///modules/vimrc/vimrc',
}
}
Save the manifest, and use the puppet parser
tool to validate your syntax:
puppet parser validate vimrc/manifests/init.pp
Test your module
Remember, this manifest defines the vimrc
class, but you'll need to
declare it for it to have an effect. That is, we've described what the vimrc
class is, but you haven't told Puppet to actually do anything with it.
Task 7:
To test the vimrc
class, create a manifest called init.pp
in the
vimrc/examples
directory.
vim vimrc/examples/init.pp
All you'll do here is declare the vimrc
class with the include
directive.
include vimrc
Task 8:
Apply the new manifest with the --noop
flag. If everything looks good, drop
the --noop
and apply it for real.
You'll see something like the following:
Notice: /Stage[main]/Vimrc/File[/root/.vimrc]/content: content changed
'{md5}99430edcb284f9e83f4de1faa7ab85c8' to
'{md5}f685bf9bc0c197f148f06704373dfbe5'
When you tell Puppet to manage a file, it compares the md5 hash of the target file against that of the specified source file to check if any changes need to be made. Because the hashes did not match, Puppet knew that the target file did not match the desired state, and changed it to match the source file you had specified.
To see that your line numbering settings have been applied, open a file with Vim. You should see the number of each line listed to the left.
Review
In this quest, you learned about the structure and purpose of Puppet modules. You created a module directory structure, and wrote the class you need to manage a configuration file for Vim. You also saw how Puppet uses md5 hashes to determine whether a target file matches the specified source file.
In the quests that follow, you'll learn more about installing and deploying pre-made modules from the Puppet Forge.