In this chapter, we will review the process of creating a custom module. We will cover each step from the beginning, including:
Let’s get started.
A module can have any name that begins with a letter and contains only lowercase letters, numbers, underscores. A hyphen is not allowed within a module name. Here are some valid and invalid module names:
mymodule |
Valid |
3files |
Invalid |
busy_people |
Valid |
busy-people |
Invalid |
It is important to choose your module name carefully to avoid conflicts with other modules. As each module creates its own namespace, only one module may exist within a Puppet catalog with a given name at the same time. For most intents and purposes, this means that you can never use two modules with the same name.
When naming modules, I try to avoid naming the module anything that conflicts with a module name available in the Puppet Forge. I never know when another team might utilize that module for its project, or when a module I download might use the Puppet Forge module as a dependency.
Some namespaces are internal to and used by Puppet itself. For this reason, you can never create a module with these names:
main |
This class contains any resources not contained by any other class. |
facts |
This namespace contains facts provided by the node’s Puppet agent. |
server_facts |
This namespace contains server-side facts supplied by the Puppet server. |
settings |
This namespace contains the Puppet configuration settings. |
trusted |
This namespace contains facts taken from the client’s certificate, as signed by the Puppet certificate authority. |
Puppet will generate an empty skeleton for you with the puppet module generate
command. As mentioned at the start of the book, your first module will manage the Puppet agent itself. Let’s create that now. Use your own name or organization name instead of myorg
.
When you generate a module, it will ask you a number of questions. Default values used if you just press Enter are included in square brackets after each question:
$ puppet module generate myorg-puppet We need to create a metadata.json file for this module. Please answer the following questions; if the question is not applicable to this module, feel free to leave it blank. Puppet uses Semantic Versioning (semver.org) to version modules. What version is this module? [0.1.0] -->
After you have answered all of the questions, it will generate the module in your current directory:
Notice: Generating module at /home/vagrant/puppet...
Notice: Populating templates...
Finished;
module generated in puppet.
puppet/Gemfile
puppet/Rakefile
puppet/examples
puppet/examples/init.pp
puppet/manifests
puppet/manifests/init.pp
puppet/spec
puppet/spec/classes
puppet/spec/classes/init_spec.rb
puppet/spec/spec_helper.rb
puppet/tests
puppet/tests/init.pp
puppet/README.md
puppet/metadata.json
If you prefer to skip the questions and edit the file yourself, add --skip-interview
to the command line.
After you’ve been creating modules for a while, you may want to tune the default module skeleton to add things you want in your modules. You do so by placing your revised skeleton in the ~/.puppetlabs/opt/puppet/cache/puppet-module/skeleton directory.
Previous versions of Puppet used the ~/.puppet/var/puppet-module/skeleton directory. You’ll need to copy any existing skeleton to the new directory:
$
mkdir
-p
~/.puppetlabs/opt/puppet/cache/puppet-module/
$
cd
~/.puppetlabs/opt/puppet/cache/puppet-module/
$
cp
-r
~/.puppet/var/puppet-module/skeleton
./
You can install multiple skeletons in a directory of your choice, and select one for the module you will be building like so:
$ puppet module --module_skeleton_dir=~/skels/rails-app generate myorg-railsapp
You can also find enhanced module skeletons that others have created. There are skeletons that include better test suites, and skeletons that include common examples for different application environments. You can find Puppet module skeletons by searching for “puppet skeleton” on GitHub.
Let’s review the files and directories created in your new module. All of the following files are fixed, unchangeable paths built into Puppet’s expectations and utilization of modules:
manifests/ | Directory where Puppet language manifests (classes) are read. |
files/ | Directory to place files served intact by your module. |
examples/ | Directory to place functional examples used for testing the module. |
templates/ | Directory to place templates parsed to create custom files. |
lib/ | Directory for optional module plugins, such as Ruby facts, functions, etc. |
spec/ | Directory containing unit tests to validate the manifests. |
tests/ | Directory containing system tests to validate the manifests. |
facts.d/ | Directory containing non-Ruby external facts to be distributed. |
metadata.json | File documenting the module version and dependencies. |
To test the module, we’ll have to place it somewhere that Puppet can find it. While developing a new module, it is easiest to simply move the directory into a test environment’s $modulepath
:
[
vagrant@client
~
]
$
cd
/etc/puppetlabs/code/environments/test/modules
[
vagrant@client
modules
]
$
mv
~/puppet
./
Now that the module is in place, add it to classes
for the host you are going to test it on:
[
vagrant@client
~
]
$
$EDITOR
/etc/puppetlabs/code/hieradata/hostname/client.yaml
Add this file to the classes loaded by your test node:
--- classes: - puppet
When you apply the test environment to the client node, this class will be included. This allows for testing the module in isolation.
Let’s start within the manifests/ directory of your module. In this directory, you will find a single file named init.pp. This file must exist within every Puppet module, and it must contain the definition for the base class. The base class is a class with the same name as the module. Let’s take a look at that now:
[
vagrant@client
modules
]
$
cd
puppet/manifests
[
vagrant@client
manifests
]
$
cat
init.pp
class
puppet
{
}
Right now this class has no definition. That is an acceptable situation—the class must be defined, but it doesn’t have to do anything. We’ll flesh it out in the very next section, after we discuss the difference between a class and a manifest.
In Part I of the book, you created and applied Puppet manifests. Here’s a brief summary to refresh your memory:
puppet apply
commandThe good news for you is that a class is a manifest with special properties. It uses Puppet configuration language to declare resources exactly the same as in a manifest. This means that if you know how to write a manifest, then you know how to write a class.
Before we get started, let’s quickly review the special properties that make a class different from a manifest. A class is different in the following ways:
As we mentioned previously, class manifests are almost exactly like the manifests you used with puppet apply
. I am sure that you remember (if not, flip back to “Referring to Resources”) the resources declared to install a package and start a service. Go ahead and fill in the class manifest with those resources right now.
The manifest should look something like this:
class
puppet
{
# Install the Puppet agent
package
{
'puppet-agent'
:
ensure
=
>
'latest'
,
notify
=
>
Service
[
'puppet'
]
,
}
# Manage the Puppet service
service
{
'puppet'
:
ensure
=
>
'running'
,
enable
=
>
true
,
subscribe
=
>
Package
[
'puppet-agent'
]
,
}
}
This is very similar to the package and service manifest built out earlier in the book. As per best practice, we have defined dependencies to ensure that the service is restarted if the package is updated.
Let’s apply the test environment to the node now for our first test of the module:
[
vagrant@client
modules
]
$
puppet
apply
--environment
test
../manifests/
Notice:
Compiled
catalog
for
client.example.com
in
environment
test
Notice:
Applied
catalog
in
0.02
seconds
Let’s define parameters your Puppet module will accept. Adjust the init.pp file to look something like this:
class
puppet
(
# input parameters and default values for the class
$version
=
'latest'
,
$status
=
'running'
,
$enabled
,
# required parameter
)
{
# echo the input provided
notice
(
"
Install the $version version of Puppet, ensure it's $status, and set boot time start $enabled.
"
)
}
Here we have declared three input parameters. Two of them have default values, which will only be used if input is not provided when the class is called. The third value enabled
is required, and will generate an error if a value is not provided.
Puppet 4.3 introduced a tiered, pluggable approach to data access called Puppet Lookup. Classes can now receive parameter input from three possible places. If the data is not available in the first place, it looks in the next according to the following flow:
Let’s go ahead and test our new module to ensure the default values work as expected:
$
puppet
apply
--environment
test
../manifests/
Error:
Evaluation
Error:
Error
while
evaluating
a
Function
Call,
Must
pass
enabled
to
Class
[
Puppet
]
at
/etc/puppetlabs/code/environments/test/manifests/site.pp:2:1
Whoops! That’s right, we made the enabled
parameter required. However, we have not provided a value for this key in any data provider (Hiera is the only one enabled by default). This is one of the reasons that the Puppet Style Guide recommends supplying defaults for all parameters.
Provide every parameter with a default value that will implement the most common use case.
Adjust the module to provide a default value and try again:
class
puppet
(
$version
=
'latest'
,
$status
=
'running'
,
$enabled
=
true
,
$
puppet
apply
--environment
test
../manifests/
Notice:
Scope
(
Class
[
Puppet
]
)
:
Install
the
latest
version
of
Puppet,
ensure
it
'
s
running,
and
set
boot
time
start
true.
Notice:
Compiled
catalog
for
client.example.com
in
environment
test
Notice:
Applied
catalog
in
0.02
seconds
As you can see, the module will apply properly without any parameters provided.
You’re probably thinking that this manifest isn’t sufficient. One can’t generally install and run software without configuring it. I agree. Let’s add a configuration file.
The first step is to create a directory in which to store files. A module’s files must reside in a files/ directory within the module. You can add subdirectories to organize files:
$
cd
/etc/puppetlabs/code/environments/test/modules/puppet
$
mkdir
files
Let’s copy our existing puppet.conf configuration file to that directory to test out the concept:
$
cp
/etc/puppetlabs/puppet/puppet.conf
files/
$
$EDITOR
files/puppet.conf
Let’s make a small change to Puppet’s configuration. Add the following line to make the logging a bit more verbose:
[
main
]
log_level
=
notice
Now we’ll add a file
resource to the class manifest. Instead of using the content
attribute, we’ll use a source
attribute. Files specified by this attribute are copied intact to the node. Add the resource to manifests/init.pp:
file{
'/etc/puppetlabs/puppet/puppet.conf'
:ensure
=
>'file'
,owner
=
>'root'
,group
=
>'wheel'
,mode
=
>'0644'
,source
=
>'puppet:///modules/puppet/puppet.conf'
,}
There are three valid URIs for use as file sources:
puppet:///modules/module_name/filename
file:///path/to/file/on/local/system
http: or https://web-server/filename
(new in Puppet 4.4)Downloading files over http/https only works well if you add this attribute to the file resource, as remote web servers don’t provide file checksums:
checksum => 'mtime'
The best practice is to store all files used by a module within the module, and use the preceding puppet:///
URI pattern. Leaving the server field in the URI blank indicates to download the file from the Puppet server the agent is currently talking to. This same URI also works seamlessly with puppet apply
to get the module files from modulepath
on the local system.
The path /modules/puppet is a special path that maps to the files/ directory in the puppet
module.
Avoid specifying an explicit Puppet server in the URI source for a file, as the module will fail if any node doesn’t have direct access to that server. Synchronize the dependent files to all Puppet servers that serve the module.
You can specify an array of file sources. Puppet will go through the array of sources and use the first file it finds.
It is possible to source files that are not within a module from the Puppet server. However, this practice is deprecated and not recommended for many good reasons, so I am not going to cover it.
Place files in the same module as the manifests that use the files.
Let’s stop and test the file copy feature before we proceed. You should see something like this:
[
vagrant@client
test
]
$
sudo
puppet
apply
--environment
test
manifests/
Notice:
Compiled
catalog
for
client.example.com
in
environment
test
Notice:
/Stage
[
main
]
/Puppet/File
[
/etc/puppetlabs/puppet/puppet.conf
]
/content:
content
changed
'{md5}2c15ae72acdbd8878a6550275bc15fef'
to
'{md5}b0547c4cf4cc5a5cb7aee439765f82a4'
Notice:
/Stage
[
main
]
/Puppet/File
[
/etc/puppetlabs/puppet/puppet.conf
]
/owner:
owner
changed
'vagrant'
to
'root'
Notice:
/Stage
[
main
]
/Puppet/File
[
/etc/puppetlabs/puppet/puppet.conf
]
/group:
group
changed
'vagrant'
to
'wheel'
Notice:
Applied
catalog
in
0.37
seconds
View the contents of the file, and you’ll find that it has copied the file exactly.
One of the things changed by our file
resource was the permissions of the file. You may want to change them back for convenience while learning:
$
chown
vagrant:vagrant
/etc/puppetlabs/puppet/puppet.conf
$
chmod
0664
/etc/puppetlabs/puppet/puppet.conf
Alternatively, you could change the owner and group attributes and run Puppet again.
You can use puppet filebucket
to restore the file. First, query the filename to get the most recent hash, and then supply the hash to the restore
command:
$
sudo
puppet
filebucket
-l
list
|
grep
puppet.conf
c15ae72acdbd8878a6550275bc
2015-09-07
03:58:51
/etc/puppetlabs/puppet/puppet.conf
8c1fd25aed09e7b2c8cfc3b0eb
2015-09-07
08:36:39
/etc/puppetlabs/puppet/puppet.conf
bc1a4e205b7e2ec921fee7380d
2015-09-07
08:50:34
/etc/puppetlabs/puppet/puppet.conf
$
sudo
puppet
filebucket
restore
/etc/puppetlabs/puppet/puppet.conf
c15ae72acdbd8878a6550275bc
The file resource has optional recurse
and purge
attributes that make it possible to synchronize a directory of files:
file{
'/tmp/sync/'
:ensure
=
> directory,owner
=
>'root'
,group
=
>'wheel'
,mode
=
>'0444'
,recurse
=
>true
,# go into subdirectories
replace
=
>true
,# replace any files that already exist
purge
=
>false
,# don't remove files that we don't have
links
=
> follow,# follow symbolic links and modify the link target
force
=
>false
,source
=
>'puppet:///modules/puppet/sync/'
,}
As you can see, there are many parameters for controlling how files are installed, and under what situations they will replace other files. You can find detailed explanations for these attributes at “Puppet 4 Type Reference: file” on the Puppet docs site.
I imagine you’re saying to yourself, “Not every system will get the same configuration.” Good point! Let’s build a customized template for this configuration file.
Create a directory in which to store templates. Like files, templates have their own directory in the module structure:
$
cd
/etc/puppetlabs/code/environments/test/modules/puppet
$
mkdir
templates
The template we are going to build will utilize four variables. Let’s build a manifest that supplies those variables. First, let’s modify the module input to set default values for each of these variables:
class
puppet
(
$version
=
'latest'
,
$status
=
'running'
,
$enabled
=
true
,
$server
=
'puppet.example.com'
,
$common_loglevel
=
'warning'
,
$agent_loglevel
=
undef
,
$apply_loglevel
=
undef
,
)
{
With this declaration, we now have seven parameters in our class that we can use in a template. You’ll notice that we explicitly set the agent and apply log levels to an undefined value. We’ll use these only if values are passed in when the class is declared. (If we left out a default value, then the parameters would be required, which is the opposite of our intention.)
There are two different template parsers within Puppet:
It doesn’t matter which one you use, so we’ll teach you to use each of these.
Both Puppet EPP and Ruby ERB files are plain-text files that contain some common syntax and template tags. Outside of these tags is normal, unprocessed text. Within the tags are Puppet or Ruby variables and code:
<%= variable or code %> |
This tag is replaced with the value of the variable, or result of the code. |
<% code block %> |
The code is executed. Nothing is returned unless the code prints output. |
<%# a comment %> |
This tag is removed from the output. |
<% code block -%> |
Immediately trailing newlines or whitespace is removed. Use to prevent blank lines. |
<%- code block %> |
Leading newlines or whitespace is removed. Use when indenting template tags. |
<%= variable or code -%> |
Removes trailing whitespace after the result of the code. (There is no option for trimming leading whitespace.) |
<%% %%> |
Double percent signs are replaced with a single percent sign. Use to prevent interpolation. |
Don’t worry about memorizing these patterns. These tags will be used extensively in the next few pages, and you’ll get to see them applied in context.
Adjust the file declaration from the previous section to remove the source
attribute and replace it with a content
attribute. The content will be provided by the epp()
function:
file
{
'/etc/puppetlabs/puppet/puppet.conf'
:
ensure
=>
'file'
,
owner
=>
'root'
,
group
=>
'wheel'
,
mode
=>
'0644'
,
content
=>
epp
(
'puppet/puppet.conf.epp'
),
}
The epp()
function takes a two arguments:
modulename/filename.epp
. The file should be placed in the templates/ directory of the module. Puppet EPP templates should end with the .epp extension to indicate that the file contains tags for the Puppet EPP template processor.Let’s create a template file. From within the module directory, invoke your editor like so:
[
vagrant@client
puppet
]
$
$EDITOR
templates/puppet.conf.epp
The template should look something like this:
# Generated by Puppet EPP template processor
[
master
]
log_level
=
<
%= $::puppet::common_loglevel %>
# This is used by "puppet agent"
[agent]
<% if $puppet::agent_loglevel !=
undef
{
-
%>
log_level = <%= $::puppet::agent_loglevel %>
<
% }
-
%>
server = <%= $::puppet::server %>
# This is used for "puppet apply"
[
user
]
<
% if
$puppet
::
apply_loglevel
!=
undef
{
-
%>
log_level = <%= $::puppet::apply_loglevel %>
<
% }
-%>
This example utilizes four variables. Each instance of <%= $::class::variable %>
is replaced with the Puppet variable from that class. We added those variables to the manifest as our very first step.
In this declaration, the epp()
function processes the EPP template and provides the content to be placed in the file. It does this by replacing the variable lookups in the file we created with variables from the current variable scope (within this class).
Go ahead and test this change right now with puppet apply
. You will see the contents of the Puppet configuration file get updated:
[
vagrant@client
test
]
$
sudo
puppet
apply
--environment
test
manifests/
Info:
Applying
configuration
version
'1441617320'
Info:
Computing
checksum
on
file
/etc/puppetlabs/puppet/puppet.conf
Info:
/Stage
[
main
]
/Puppet/File
[
/etc/puppetlabs/puppet/puppet.conf
]
:
Filebucketed
/etc/puppetlabs/puppet/puppet.conf
to
puppet
with
sum
1698e7d7bfb88eace241ca
Notice:
/Stage
[
main
]
/Puppet/File
[
/etc/puppetlabs/puppet/puppet.conf
]
/content:
changed
'{md5}1698e7d7bfb88eace241ca'
to
'{md5}ae81b356db7fcf0846901d'
Notice:
Applied
catalog
in
0.35
seconds
EPP templates can do far more than variable replacement. You can put any Puppet function within <% ... %>
tags without the equals sign. Here’s an example that uses conditional evaluation to limit duplicate assignment of log levels that aren’t different:
[
user
]
<
% if
$:
:puppet
::
apply_loglevel
!=
undef
and
$:
:puppet
::
apply_loglevel
!=
$:
:puppet
::
common_loglevel
{
-
%>
log_level = <%= $::puppet::apply_loglevel %>
<
% }
-%>
By placing this line of output within the Puppet if
condition, we ensure that the line will only be output if both conditions match. This will avoid outputting the configuration line if the log level matches the main log level, thus simplifying the configuration file.
It was pretty annoying to have to list the fully qualified name of each variable, wasn’t it? You can simplify the variable naming by providing a hash of input parameters for the template. This is very useful when the Puppet variable names don’t match the variable names used in the template. For example:
content
=>
epp
(
'puppet/puppet.conf.epp'
,
{
'server'
=>
$server
,
'common_loglevel'
=>
$common_loglevel
,
'agent_loglevel'
=>
$agent_loglevel
,
'apply_loglevel'
=>
$apply_loglevel
,
}),
When providing parameter input, make sure the very first line of the template contains the following special syntax for accepting the variables:
<
%- | String $server,
String $common_loglevel,
Optional['String'] $agent_loglevel = undef,
Optional['String'] $apply_loglevel = undef,
| -
%>
This input format exactly matches the input assignments used for class parameters, where the server
and common_loglevel
values are required to be provided, while the other log-level parameters are optional.
The hash of values passed to a template must match the definition at the top of the template. If it doesn’t supply all required values, an error will be issued. If it supplies too many values, a different error will be issued.
You can use any Puppet function within the template tags. By far the most common operation to perform within a template is to iterate through some values.
Here’s an example where we use the reduce()
function to iterate through an array of tags used to limit the resources applied. This example uses the sprintf()
function creatively to add commas between values:
[
agent]
tags
=
<%=
$taglist
.reduce|
$tags
,$tagname
|
{
sprintf(
"%s,%s"
,$tags
,$tagname
)
}
-%>
This iteration function was used as shown in “Looping Through Iterations”.
EPP templates are new and thus only modules designed for Puppet 4 will use them.
Complete documentation for EPP templates is available at “Puppet Functions: epp” on the Puppet docs site.
Although EPP modules are superior, modules that provide backward compatibility for earlier versions of Puppet may need to utilize ERB templates.
Remove the epp()
function and replace it with a template()
function. Rename the file to use an .erb extension:
file
{
'/etc/puppetlabs/puppet/puppet.conf'
:
ensure
=>
ensure
,
owner
=>
'root'
,
group
=>
'wheel'
,
mode
=>
'0644'
,
content
=>
template
(
'puppet/puppet.conf.erb'
),
}
Similar to the epp()
function, the template()
function takes a single argument: the URI of the ERB template.
Let’s create the ERB template file. The template should be placed in the templates/ directory of the module. ERB templates should end with the .erb extension to indicate that the file contains tags for the ERB template processor:
[
vagrant@client
puppet
]
$
$EDITOR
templates/puppet.conf.erb
Similar to the EPP example, we’ll utilize the values of Puppet variables to customize the file. Each instance of <%= @variable %>
is replaced with the value of the Puppet variable named after the @
sign. Unlike EPP, there is not an explicit list of variables for the template. The variables accessed with the @
prefix must exist in the same scope (e.g., within the module class) as the template declaration.
The contents of the file should look like this:
# Generated by Puppet ERB template processor
[
main
]
log_level
=
<
%= @common_loglevel %>
# This is used by "puppet agent"
[agent]
<% if @agent_loglevel -%>
log_level =
<
%= @agent_loglevel %>
<% end -%>
server =
<
%= @server -%>
# This is used for "puppet apply"
[user]
<% if @apply_loglevel -%>
log_level =
<%=
@apply_loglevel
%>
<% end -%>
Look up variables from another class using the scope.lookupvar()
function, or use scope[]
as if it were a hash. For example, if we wanted to look up the log level used by MCollective, either of the following would work:
loglevel
=
<
%= scope.lookupvar('mcollective::loglevel') -%>
loglevel =
<%=
scope
[
'::mcollective::loglevel'
]
-%>
Avoid looking up data outside the module within a template, as it hides the external dependency from a code review of the manifest. Instead, lookup the data within the manifest and provide the data to the template in variables.
Call Puppet functions using scope.call_function(puppet_function, [params])
. This takes two arguments:
For example, you could call the fqdn_rand()
function to produce a random number based on the node’s hostname:
server
=
<%=
scope
.
call_function
(
'fqdn_rand'
,
[
'3600'
]
)
-%>
As ERB templates were intended for inline Ruby development, you can put any Ruby statement within <% ... %>
tags without the equals sign. Here’s an example that would limit duplicate assignment of the same log level:
[
user
]
<
% if
@apply_loglevel
!=
@loglevel
-
%>
log_level = <%= @apply_loglevel %>
<
% end
-%>
By wrapping this line of the template within the Ruby block, it will skip the output line if the log level matches the main log level, thus simplifying the configuration file output.
Go ahead and test this change right now with puppet apply
. You will see the contents of the Puppet configuration file get updated.
Here’s an example where we use the Ruby each()
function to iterate through an array of tags, which can be used to limit the resources evaluated. This example uses the dash creatively to suppress line feeds and output tags as a single comma-separated value:
[
agent]
tags
=
<%=
tags
=
''
;
@taglist.eachdo
|
tagname|
tags +=
tagname +','
end tags.chop# remove trailing comma
%>
You’ll note that we don’t put an @
sign before the variable name. That is because we are not referencing a variable in the Puppet module class, but instead from the local loop shown in this example.
More documentation for using ERB templates with Puppet can be found at “Embedded Ruby (ERB) Template Syntax” on the Puppet docs site.
ERB is commonly used by Ruby in many other situations, so you can find advice and help for using ERB syntax with any search engine.
Notice that both the original template and the preceding block sometimes utilize a leading dash in the closure: -%>
. The dash tells the interpreter to suppress an immediately following line feed. This is commonly used to keep comments or Ruby statements from adding blank lines to the output, but can also be used to concatenate two sequential lines.
You want to avoid trimming whitespace with the dash if that is the end of the line, or two lines will be joined together.
Use Puppet EPP templates with Puppet configuration language in both the manifests and the templates. Specify parameters for the template explicitly for clarity and readability.
I cannot emphasize the preceding suggestion strongly enough. I can’t tell you how many hours I have spent searching through manifests to determine where a variable used in a template was sourced from.
Now that we have created the resources, and defined our Hiera data, it’s time to test the module. We expect to see all the resources change status:
$
puppet
apply
--environment
test
../manifests/
Notice:
Compiled
catalog
for
client.example.com
in
environment
test
Notice:
/Stage
[
main
]
/Puppet/Service
[
puppet
]
/ensure:
ensure
changed
'running'
to
'stopped'
Notice:
/Stage
[
main
]
/Puppet/Service
[
puppet
]
/enable:
enable
changed
'true'
to
'false'
Notice:
Applied
catalog
in
31.98
seconds
If you don’t see any of the above, it could be that the services are already configured that way (no changes) or that the values weren’t defined in Hiera correctly.
You can easily test this by changing the Hiera file and observing the change in output. For example, if you set enabled
to true
, you should get the following output when reapplying the class:
$
puppet
apply
--environment
test
../manifests/
Notice:
Compiled
catalog
for
client.example.com
in
environment
test
Notice:
/Stage
[
main
]
/Puppet/Service
[
puppet
]
/enable:
enable
changed
'false'
to
'true'
Notice:
Applied
catalog
in
0.35
seconds
In this section, we’re going to talk about peeking beneath the hood: looking at variables and resources in other classes.
Don’t use the techniques described here for anything other than debugging.
Environments are strict. You cannot see data or code that is not loaded in your active environment.
Within an environment, class boundaries are not enforced. You can access both variables and resources in other classes. Here’s an example:
class
other_class
(
$idea
=
'games!'
)
{
$sentence
=
"an idea: ${idea}"
notify
{
'announcement'
:
message
=>
"I have ${sentence}"
}
}
class
my_class
{
# I can see the other class parameter
notice
(
"The idea was: ${Class['other_class']['idea']}"
)
# I can see the other class variables
notice
(
"The sentence was: ${::other_class::sentence}"
)
# I can see parameters of resources in another class
notice
(
"Entire message was: ${Notify['announcement']['message']}"
)
}
Given an idea of games! you’d see output like this:
$
puppet
apply
/
vagrant
/m
anifests
/
peek
.
pp
Notice
:
Scope
(
Class
[
My_class
]
)
:
The
idea
was
:
games!
Notice
:
Scope
(
Class
[
My_class
]
)
:
The
sentence
was
:
an
idea
:
games!
Notice
:
Scope
(
Class
[
My_class
]
)
:
Entire
message
was
:
I
have
an
idea
:
games!
Let’s review some of the best practices for module development we covered in this chapter:
You can find more detailed guidelines in the Puppet Labs Style Guide.
Modules provide an independent namespace for reusable blocks of code that configure or maintain something. A module provides new resource types that can be independently used by others.
In this chapter, we discussed how to:
This chapter covered the required pieces of modules, and their most common use cases. In the next chapter, we’re going to look at expert features and improved functionality you can utilize within your module.