
Modules
Prerequisites
- Welcome Quest
- Power of Puppet Quest
- Resources Quest
- Manifest Quest
- Variables Quest
- Conditions Quest
- Ordering Quest
- Classes Quest
Quest Objectives
- Understand the purpose of Puppet modules
- Learn the basic structure and layout of modules
- Write and test a simple module
Getting Started
So far we've seen some simple examples of Puppet code, but what makes Puppet such an adaptable tool? When it comes to getting things done, it's all about the module. A Puppet module is a self-contained bundle of code and data, organized around a particular purpose. The code would encompass manifests, the data would include all the source files, templates etc that you need to accomplish the purpose of the module. Everything we have learned until this point and more can come together as part of a module. When you're ready, type the following command:
quest --start modules
What's a Module?
If resources and classes are the atoms and molecules of Puppet, we might consider modules our amoebas: the first self-contained organisms of the Puppet world.
Thus far, in order to gain insight into Puppet's language features and syntax, we have been writing one-off manifests, perhaps using a source file for the contents of some configuration file (as we did for the SSH daemon configuration) etc. We have to remember, however that Puppet is designed to help you manage lots of systems (not just one system) from a central point - the master.
Although it is possible to list all the resources (users, files, package, services, cron tasks etc) that you need to manage on a system in one huge manifest, and then apply that manifest on the system, this is not scalable. Neither is it composable, or flexible enough to be reused. Classes were our first stop on the path to learning how to create 'blocks' - building blocks that we can use to describe the configuration of the systems we seek to manage.
There is still a missing part of the puzzle. When you ask Puppet to, say, include ssh
on a particular node, or, as we did in the Power of Puppet quest, classify a node (learn.localdomain) with a class (lvmguide), how does Puppet know where to find the definition for the class, in order to ensure that the specified configuration is realized?
The answer is, we agree to put the class definitions in a standard location on the file system, by placing the manifest containing the class definition in a specific directory in a module.
Simply put, a Module is a directory with a specific structure - a means for us to package everything needed to achieve a certain goal. Once we agree to stick to the standard way of doing this, a significant benefit is the ability to share our work, such that others who seek to achieve the same goal can re-use our work. The Forge is the central location where you can find modules that have been developed by others.
In our initial Quest, we were able to use an Apache module from the Forge. This let us easily install and configure an Apache webserver to host the website version of this Quest Guide. The vast majority of the necessary code had already been written, tested, documented, and published to the Puppet Forge.
Modules provide a structure to make these collections of pre-written Puppet code, well, modular. In order to enable Puppet to access the classes and types defined in a module's manifests, modules give us a standard pattern for the organization of and naming of Puppet manifests and other resources.
Module Path
All modules are located in a directory specified by the modulepath variable in Puppet's configuration file. On the Learning VM, Puppet's configuration file can be found at /etc/puppetlabs/puppet/puppet.conf
.
Task 1 :
Find the modulepath on the Learning VM.
If you're ever wondering where your modulepath is, you can find it by running the puppet agent
command with the --configprint
flag and specifying modulepath
:
puppet agent --configprint modulepath
What the returned value tells us is that Puppet will look in the directories /etc/puppetlabs/puppet/modules
and then in /opt/puppet/share/puppet/modules
to find the modules in use on the system. Classes and types defined by modules in these directories will be available to Puppet.
Module Structure
The skeleton of a module is a pre-defined structure of directories that Puppet already knows how to traverse to find the module's manifests, templates, configuration files, plugins, and anything else necessary for the module to function. Of these, we have encountered manifests and files that serve as the source for configuration files. We will learn about the rest in due course.
Remember, /etc/puppetlabs/puppet/modules
is in our modulepath. Use the ls
command to see what's in that directory:
ls /etc/puppetlabs/puppet/modules
There's the apache
module we installed before. There is also the lvmguide
module that was used to set up the quest guide website, that was already in place when we started. Use the tree
command to take a look at the basic directory structure of the module. (To get a nice picture, we can use a few flags with the tree command to limit our output to directories, and limit the depth to two directories.)
tree -L 2 -d /etc/puppetlabs/puppet/modules/
You should see a list of directories, like so:
/etc/puppetlabs/puppet/modules/
└── apache
├── files
├── lib
├── manifests
├── spec
├── templates
└── tests
Once you get down past this basic directory structure, however, the apache
module begins to look quite complex. To keep things simple, we can create our own first module to work with.
Task 2 :
First, be sure to change your working directory to the modulepath. We need our module to be in this directory if we want Puppet to be able to find it.
cd /etc/puppetlabs/puppet/modules
You have created some users in the Resources and Manifests quests, so this resource type should be fairly familiar. Let's make a users
module that will help us manage users on the Learning VM.
The top directory of any module will always be the name of that module. Use the mkdir
command to create your module directory:
mkdir users
Task 3 :
Now we need two more directories, one for our manifests, which must be called manifests
, and one for our tests, which must be called (you guessed it!) tests
. As you will see shortly, tests allow you to easily apply and test classes defined in your module without having to deal with higher level configuration tasks like node classification.
Go ahead and use the mkdir
command to create users/manifests
and users/tests
directories within your new module.
mkdir users/{manifests,tests}
If you use the tree users
command to take a look at your new module, you should now see a structure like this:
users
├── manifests
└── tests
2 directories, 0 files
Task 4 :
Create a manifest defining class users:
The manifests directory can contain any number of the .pp
manifest files that form the bread-and-butter of your module. If there is a class with the same name as the module, the definition for that class should be in a file name init.pp
. In our case, we need a class called users
in our module with the same name. The defintion for class users should be in a file called init.pp
in the manifests
directory. This is necessitated by the mechanism by which Puppet locates class definitions when you name a class. If for example, you had a file called settings.pp
in the manifests directory, you will have to refer to it as class users::settings
for Puppet to be able to find the class defintion contained in it. By placing the definition for class users
in init.pp
, we can refer to that class defintion by the name of the module - users
.
Go ahead and create the init.pp
manifest in the users/manifests
directory. (We're assuming you're still working from the /etc/puppetlabs/puppet/modules
. The full path would be /etc/puppetlabs/puppet/modules/users/manifests.init.pp
)
nano users/manifests/init.pp
Now in that file, add the following code:
class users {
user { 'alice':
ensure => present,
}
}
We have defined a class with just the one resource in it. A resource of type user
with title alice
.
Use the puppet parser
tool to validate your manifest:
puppet parser validate users/manifests/init.pp
For now, we're not going to apply anything. This manifest defines the users
class, but so far, we haven't declared it. That is, we've described what the users
class is, but we haven't told Puppet to actually do anything with it.
Declaring Classes from Modules
Remember when we talked about declaring classes in the Classes Quest? We said we would discuss more on the correct way to use classes in the Modules Quest. Once a class is defined in a module, there are actually several ways to declare it. As you've already seen, you can declare classes by putting include [class name]
in your main manifest, just as we did in the Classes Quest.
The include
function declares a class, if it hasn’t already been declared somewhere else. If a class has already been declared, include
will notice that and do nothing.
This lets you safely declare a class in several places. If some class depends on something in another class, it can declare that class without worrying whether it’s also being declared elsewhere.
Task 5 :
Write a test for our new class:
In order to test our users
class, we will create a new manifest in the tests
directory that declares it. Create a manifest called init.pp
in the users/tests
directory.
nano users/tests/init.pp
All we're going to do here is declare our users
class with the include
directive.
include users
Try applying the new manifest with the --noop
flag first. If everything looks good, apply the users/tests/init.pp
manifest without the --noop
flag to take your users
class for a test drive, and see how it works out when applied.
Use the puppet resource
tool to see if user alice
has been successfully created.
So what happened here? Even though the users
class was in a different manifest, we were able to declare our test manifest. Because our module is in Puppet's modulepath, Puppet was able to find the correct class and apply it.
You just created your first Puppet module!!
Classification
When you use a Node Classifier, such as the Puppet Enterprise Console, you can classify nodes - which is to say that you can state which classes apply to which nodes, using the node classifier. This is exactly what we did when we used the PE Console to classify our Learning VM node with the lvmguide
class in the Power of Puppet quest. In order to be able to classify a node thus, you must ensure all of the following:
- There is a module (a directory) with the same name as the class in the modulepath on the Puppet master
- The module has a file called
init.pp
in themanifests
directory contained within it - This
init.pp
file contains the definition of the class
With modules, you can create composable configurations for systems. For example, let's say that you have a module called 'ssh' which provides class ssh, another called 'apache' and a third called 'mysql'. Using these three modules, and the classes provided by them, you can now classify a node to be configured with any combination of the three classes. You can have a server that has mysql and ssh managed on it (a database server), another with apache and ssh managed on it (a webserver), and a server with only ssh configured on it. The possibilities are endless. With well-written, community-vetted, even Puppet Supported Modules from the Forge, you can be off composing and managing configuration for your systems in no time. You can also write your own modules that use classes from these Forge modules, as we did with the lvmguide
class, and reuse them too.
Review
- We identified what the features of a Puppet Module are, and understood how it is useful
- We wrote our first module!
- We learned how modules (and the classes within them) can be used to create composable configurations by using a node classifier such as the PE Console.