Modules
Modules are self-contained, distributable, and (ideally) reusable recipes to manage specific applications or system's elements.
They are basically just a directory with a predefined and standard structure that enforces configuration over naming conventions for the managed provided classes, extensions, and files.
The $modulepath
configuration entry defines where modules are searched; this can be a list of colon separated directories.
Paths of a module and auto loading
Modules have a standard structure, for example, for a MySQL module the code reads thus:
mysql/ # Main module directory mysql/manifests/ # Manifests directory. Puppet code here. mysql/lib/ # Plugins directory. Ruby code here mysql/templates/ # ERB Templates directory mysql/files/ # Static files directory mysql/spec/ # Puppet-rspec test directory mysql/tests/ # Tests / Usage examples directory mysql/facts.d/ # Directory for external facts mysql/Modulefile # Module's metadata descriptor
This layout enables useful conventions, which are widely used in Puppet world; we must know them to understand where to look for files and classes:
For example, we can use modules and write the following code:
include mysql
Puppet will then automatically look for a class called mysql
defined in the file $modulepath/mysql/manifests/init.pp
:
The init.pp
script is a special case that applies for classes that have the same name of the module. For sub classes there's a similar convention that takes in consideration the subclass name:
include mysql::server
It then auto loads the $modulepath/mysql/manifests/server.pp
file.
A similar scheme is also followed for defines or classes at lower levels:
mysql::conf { ...}
This define is searched in $modulepath/mysql/manifests/conf.pp
:
include mysql::server::ha
It then looks for $modulepath/mysql/manifests/server/ha.pp
.
It's generally recommended to follow these naming conventions that allow auto loading of classes and defines without the need to explicitly import the manifests that contain them.
Note
Note that, even if not considered good practice, we can currently define more than one class or define inside the same manifest as, when Puppet parses a manifest, it parses its whole contents.
Module's naming conventions apply also to the files that Puppet provides to clients.
We have seen that the file
resource accepts two different and alternative arguments to manage the content of a file: source
and content
. Both of them have a naming convention when used inside modules.
Templates, typically parsed via the template
or the epp
functions with syntax like the one given here, are found in a place like $modulepath/mysql/templates/my.cnf.erb
:
content => template('mysql/my.cnf.erb'),
This also applies to sub directories, so for example:
content => template('apache/vhost/vhost.conf.erb'),
It uses a template located in $modulepath/apache/templates/vhost/vhost.conf.erb
.
A similar approach is followed with static files provided via the source
argument:
source => 'puppet:///modules/mysql/my.cnf'
It serves a file placed in $modulepath/mysql/files/my.cnf
:
source => 'puppet:///modules/site/openssh/sshd_config'
This serves a file placed in $modulepath/site/openssh/sshd_config
.
Notice the differences in templates and source paths. Templates are resolved in the server, and they are always placed inside the modules. Sources are retrieved by the client and modules
in the URL could be a different mount point if it's configured on the server.
Finally, the whole content of the lib
subdirectory in a module has a standard scheme. Note that here, we can place Ruby code that extends Puppet's functionality and is automatically redistributed from the Master to all clients (if the pluginsync
configuration parameter is set to true
, this is default for Puppet 3 and widely recommended in any setup):
mysql/lib/augeas/lenses/ # Custom Augeas lenses. mysql/lib/facter/ # Custom facts. mysql/lib/puppet/type/ # Custom types. mysql/lib/puppet/provider/<type_name>/ # Custom providers. mysql/lib/puppet/parser/functions/ # Custom functions.
Templates
Files provisioned by Puppet can be templates written in Ruby's ERB templating language or in the Embedded Puppet Template Syntax (EPP).
An ERB template can contain whatever text we need and have inside <% %>
tags, interpolation of variables or Ruby code. We can access, in a template, all the Puppet variables (facts or user assigned) with the <%=
tag:
# File managed by Puppet on <%= @fqdn %> search <%= @domain %>
The @
prefix for variable names is highly recommended in all Puppet versions, and mandatory starting from 4.0.
To use out of scope variables, we can use the scope.lookupvar
method:
path <%= scope.lookupvar('apache::vhost_dir') %>
This uses the variable's fully qualified name. If the variable is at top scope then run the following command:
path <%= scope.lookupvar('::fqdn') %>
Since Puppet 3, we can use this alternative syntax:
path <%= scope['apache::vhost_dir'] %>
In ERB templates, we can also use more elaborate Ruby code inside a <%
opening tag, for example, to reiterate over an array:
<% @dns_servers.each do |ns| %> nameserver <%= ns %> <% end %>
The <%
tag is used to place a line of text if some conditions are met:
<% if scope.lookupvar('puppet::db') == "puppetdb" -%> storeconfigs_backend = puppetdb <% end -%>
Noticed the -%>
ending tag here? When the dash is present, no line is introduced on the generated file, as it would if we had written <% end %>
.
EPP templates are quite similar, they are also plain text files and they use the same tags for the embedded code, the main differences are that EPPs use Puppet code instead of Ruby, that they can receive type-checked parameters, and that they can directly access other variables where in ERBs we'd have to use lookup functions.
The parameters definition is optional but if it's included it has to be in the beginning of the file:
<%- | Array[String] $dns_servers, String $search_domain | -%>
To use templates in Puppet code, we have to use the template
function for ERBs or the epp
function for EPPs; epp
can receive a hash with the values of the arguments as a second argument:
file { '/etc/resolv.conf': content => epp('resolvconf/resolv.conf.epp', { 'dns_ervers': ['8.8.8.8', '8.8.4.4'], 'search_domain': 'example.com', }) }