"A writer has the duty to be good, not lousy; true, not false; lively, not dull; accurate, not full of error." | ||
--E.B. White |
In this chapter, we will cover the following recipes:
- Making quick edits to config files
- Editing INI style files with puppetlabs-inifile
- Using Augeas to reliably edit config files
- Building config files using snippets
- Using ERB templates
- Using array iteration in templates
- Using EPP templates
- Using GnuPG to encrypt secrets
- Installing packages from a third-party repository
- Comparing package versions
In this chapter, we'll see how to make small edits to files, how to make larger changes in a structured way using the Augeas tool, how to construct files from concatenated snippets, and how to generate files from templates. We'll also learn how to install packages from additional repositories, and how to manage those repositories. In addition, we'll see how to store and decrypt secret data with Puppet.
When you need to have Puppet change a particular setting in a config file, it's common to simply deploy the whole file with Puppet. This isn't always possible, though; especially if it's a file that several different parts of your Puppet manifest may need to modify.
- Create a manifest named
oneline.pp
that will usefile_line
on a file in/tmp
:file {'/tmp/cookbook': ensure => 'file', } file_line {'cookbook-hello': path => '/tmp/cookbook', line => 'Hello World!', require => File['/tmp/cookbook'], }
- Run
puppet apply
on theoneline.pp
manifest:t@mylaptop ~/.puppet/manifests $ puppet apply oneline.pp Notice: Compiled catalog for mylaptop in environment production in 0.39 seconds Notice: /Stage[main]/Main/File[/tmp/cookbook]/ensure: created Notice: /Stage[main]/Main/File_line[cookbook-hello]/ensure: created Notice: Finished catalog run in 0.02 seconds
- Now verify that
/tmp/cookbook
contains the line we defined:t@mylaptop ~/.puppet/manifests $ cat /tmp/cookbook Hello World!
We installed the puppetlabs-stdlib
module into the default module path for Puppet, so when we ran puppet apply
, Puppet knew where to find the file_line
type definition. Puppet then created the /tmp/cookbook
file if it didn't exist. The line Hello World!
was not found in the file, so Puppet added the line to the file.
Modify the oneline.pp
file and add another file_line
resource:
Now apply the manifest again and verify whether the new line is appended to the file:
Verify the contents of /tmp/cookbook
before your Puppet run:
Verify that the line has been removed and the goodbye line has been replaced:
t@mylaptop ~/.puppet/manifests $ cat /tmp/cookbook Oh freddled gruntbuggly, thanks for all the fish.
Editing files with file_line
works well if the file is unstructured. Structured files may have similar lines in different sections that have different meanings. In the next section, we'll show you how to deal with one particular type of structured file, a file using INI syntax.
puppetlabs-stdlib
module
- Create a manifest named
oneline.pp
that will usefile_line
on a file in/tmp
:file {'/tmp/cookbook': ensure => 'file', } file_line {'cookbook-hello': path => '/tmp/cookbook', line => 'Hello World!', require => File['/tmp/cookbook'], }
- Run
puppet apply
on theoneline.pp
manifest:t@mylaptop ~/.puppet/manifests $ puppet apply oneline.pp Notice: Compiled catalog for mylaptop in environment production in 0.39 seconds Notice: /Stage[main]/Main/File[/tmp/cookbook]/ensure: created Notice: /Stage[main]/Main/File_line[cookbook-hello]/ensure: created Notice: Finished catalog run in 0.02 seconds
- Now verify that
/tmp/cookbook
contains the line we defined:t@mylaptop ~/.puppet/manifests $ cat /tmp/cookbook Hello World!
We installed the puppetlabs-stdlib
module into the default module path for Puppet, so when we ran puppet apply
, Puppet knew where to find the file_line
type definition. Puppet then created the /tmp/cookbook
file if it didn't exist. The line Hello World!
was not found in the file, so Puppet added the line to the file.
Modify the oneline.pp
file and add another file_line
resource:
Now apply the manifest again and verify whether the new line is appended to the file:
Verify the contents of /tmp/cookbook
before your Puppet run:
Verify that the line has been removed and the goodbye line has been replaced:
t@mylaptop ~/.puppet/manifests $ cat /tmp/cookbook Oh freddled gruntbuggly, thanks for all the fish.
Editing files with file_line
works well if the file is unstructured. Structured files may have similar lines in different sections that have different meanings. In the next section, we'll show you how to deal with one particular type of structured file, a file using INI syntax.
file_line
resource type, we can ensure that a line exists or is absent in a config file. Using file_line
we can quickly make edits to files without controlling the entire file.
oneline.pp
that will use file_line
on a file in /tmp
:file {'/tmp/cookbook': ensure => 'file', } file_line {'cookbook-hello': path => '/tmp/cookbook', line => 'Hello World!', require => File['/tmp/cookbook'], }
puppet apply
on the oneline.pp
manifest:t@mylaptop ~/.puppet/manifests $ puppet apply oneline.pp Notice: Compiled catalog for mylaptop in environment production in 0.39 seconds Notice: /Stage[main]/Main/File[/tmp/cookbook]/ensure: created Notice: /Stage[main]/Main/File_line[cookbook-hello]/ensure: created Notice: Finished catalog run in 0.02 seconds
/tmp/cookbook
contains the line we defined:t@mylaptop ~/.puppet/manifests $ cat /tmp/cookbook Hello World!
We installed the puppetlabs-stdlib
module into the default module path for Puppet, so when we ran puppet apply
, Puppet knew where to find the file_line
type definition. Puppet then created the /tmp/cookbook
file if it didn't exist. The line Hello World!
was not found in the file, so Puppet added the line to the file.
Modify the oneline.pp
file and add another file_line
resource:
Now apply the manifest again and verify whether the new line is appended to the file:
Verify the contents of /tmp/cookbook
before your Puppet run:
Verify that the line has been removed and the goodbye line has been replaced:
t@mylaptop ~/.puppet/manifests $ cat /tmp/cookbook Oh freddled gruntbuggly, thanks for all the fish.
Editing files with file_line
works well if the file is unstructured. Structured files may have similar lines in different sections that have different meanings. In the next section, we'll show you how to deal with one particular type of structured file, a file using INI syntax.
the puppetlabs-stdlib
module into the default module path for Puppet, so when we ran puppet apply
, Puppet knew where to find the file_line
type definition. Puppet then created the /tmp/cookbook
file if it didn't exist. The line Hello World!
was not found in the file, so Puppet added the line to the file.
Modify the oneline.pp
file and add another file_line
resource:
Now apply the manifest again and verify whether the new line is appended to the file:
Verify the contents of /tmp/cookbook
before your Puppet run:
Verify that the line has been removed and the goodbye line has been replaced:
t@mylaptop ~/.puppet/manifests $ cat /tmp/cookbook Oh freddled gruntbuggly, thanks for all the fish.
Editing files with file_line
works well if the file is unstructured. Structured files may have similar lines in different sections that have different meanings. In the next section, we'll show you how to deal with one particular type of structured file, a file using INI syntax.
file_line
and add more lines to the file; we can have multiple resources modifying a single file.
oneline.pp
file
and add another file_line
resource:
Now apply the manifest again and verify whether the new line is appended to the file:
Verify the contents of /tmp/cookbook
before your Puppet run:
Verify that the line has been removed and the goodbye line has been replaced:
t@mylaptop ~/.puppet/manifests $ cat /tmp/cookbook Oh freddled gruntbuggly, thanks for all the fish.
Editing files with file_line
works well if the file is unstructured. Structured files may have similar lines in different sections that have different meanings. In the next section, we'll show you how to deal with one particular type of structured file, a file using INI syntax.
- Create an
initest.pp
manifest with the following contents:ini_setting {'server_true': path => '/tmp/server.conf', section => 'main', setting => 'server', value => 'true', }
- Apply the manifest:
t@mylaptop ~/.puppet/manifests $ puppet apply initest.pp Notice: Compiled catalog for burnaby in environment production in 0.14 seconds Notice: /Stage[main]/Main/Ini_setting[server_true]/ensure: created Notice: Finished catalog run in 0.02 seconds
- Verify the contents of the
/tmp/server.conf
file:t@mylaptop ~/.puppet/manifests $ cat /tmp/server.conf [main] server = true
The inifile
module defines two types, ini_setting
and ini_subsetting
. Our manifest defines an ini_setting
resource that creates a server = true setting within the main section of the ini
file. In our case, the file didn't exist, so Puppet created the file, then created the main
section, and finally added the setting to the main
section.
Using ini_subsetting
, you can have several resources added to a setting. For instance, our server.conf
file has a server's line, we could have each node append its own hostname to a server's line. Add the following to the end of the initest.pp
file:
Now temporarily change your hostname and rerun Puppet:
If your configuration files are not in INI syntax, another tool, Augeas, can be used. In the following section, we will use augeas
to modify files.
- Create an
initest.pp
manifest with the following contents:ini_setting {'server_true': path => '/tmp/server.conf', section => 'main', setting => 'server', value => 'true', }
- Apply the manifest:
t@mylaptop ~/.puppet/manifests $ puppet apply initest.pp Notice: Compiled catalog for burnaby in environment production in 0.14 seconds Notice: /Stage[main]/Main/Ini_setting[server_true]/ensure: created Notice: Finished catalog run in 0.02 seconds
- Verify the contents of the
/tmp/server.conf
file:t@mylaptop ~/.puppet/manifests $ cat /tmp/server.conf [main] server = true
The inifile
module defines two types, ini_setting
and ini_subsetting
. Our manifest defines an ini_setting
resource that creates a server = true setting within the main section of the ini
file. In our case, the file didn't exist, so Puppet created the file, then created the main
section, and finally added the setting to the main
section.
Using ini_subsetting
, you can have several resources added to a setting. For instance, our server.conf
file has a server's line, we could have each node append its own hostname to a server's line. Add the following to the end of the initest.pp
file:
Now temporarily change your hostname and rerun Puppet:
If your configuration files are not in INI syntax, another tool, Augeas, can be used. In the following section, we will use augeas
to modify files.
/tmp/server.conf
file and ensure that the server_true
setting is set in that file:
initest.pp
manifest with the following contents:ini_setting {'server_true': path => '/tmp/server.conf', section => 'main', setting => 'server', value => 'true', }
t@mylaptop ~/.puppet/manifests $ puppet apply initest.pp Notice: Compiled catalog for burnaby in environment production in 0.14 seconds Notice: /Stage[main]/Main/Ini_setting[server_true]/ensure: created Notice: Finished catalog run in 0.02 seconds
/tmp/server.conf
file:t@mylaptop ~/.puppet/manifests $ cat /tmp/server.conf [main] server = true
The inifile
module defines two types, ini_setting
and ini_subsetting
. Our manifest defines an ini_setting
resource that creates a server = true setting within the main section of the ini
file. In our case, the file didn't exist, so Puppet created the file, then created the main
section, and finally added the setting to the main
section.
Using ini_subsetting
, you can have several resources added to a setting. For instance, our server.conf
file has a server's line, we could have each node append its own hostname to a server's line. Add the following to the end of the initest.pp
file:
Now temporarily change your hostname and rerun Puppet:
If your configuration files are not in INI syntax, another tool, Augeas, can be used. In the following section, we will use augeas
to modify files.
inifile
module defines two types, ini_setting
and ini_subsetting
. Our manifest defines an ini_setting
resource that creates a server = true setting within
the main section of the ini
file. In our case, the file didn't exist, so Puppet created the file, then created the main
section, and finally added the setting to the main
section.
Using ini_subsetting
, you can have several resources added to a setting. For instance, our server.conf
file has a server's line, we could have each node append its own hostname to a server's line. Add the following to the end of the initest.pp
file:
Now temporarily change your hostname and rerun Puppet:
If your configuration files are not in INI syntax, another tool, Augeas, can be used. In the following section, we will use augeas
to modify files.
ini_subsetting
, you can have
Thankfully, Augeas is here to help. Augeas is a system that aims to simplify working with different config file formats by presenting them all as a simple tree of values. Puppet's Augeas support allows you to create augeas
resources that can make the required config changes intelligently and automatically.
Follow these steps to create an example augeas
resource:
- Modify your
base
module as follows:class base { augeas { 'enable-ip-forwarding': incl => '/etc/sysctl.conf', lens => 'Sysctl.lns', changes => ['set net.ipv4.ip_forward 1'], } }
- Run Puppet:
[root@cookbook ~]# puppet agent -t Info: Applying configuration version '1412130479' Notice: Augeas[enable-ip-forwarding](provider=augeas): --- /etc/sysctl.conf 2014-09-04 03:41:09.000000000 -0400 +++ /etc/sysctl.conf.augnew 2014-09-30 22:28:03.503000039 -0400 @@ -4,7 +4,7 @@ # sysctl.conf(5) for more details. # Controls IP packet forwarding -net.ipv4.ip_forward = 0 +net.ipv4.ip_forward = 1 # Controls source route verification net.ipv4.conf.default.rp_filter = 1 Notice: /Stage[main]/Base/Augeas[enable-ip-forwarding]/returns: executed successfully Notice: Finished catalog run in 2.27 seconds
- Check whether the setting has been correctly applied:
[root@cookbook ~]# sysctl -p |grep ip_forward net.ipv4.ip_forward = 1
We declare an augeas
resource named enable-ip-forwarding
:
We specify that we want to make changes in the file /etc/sysctl.conf
:
Next we specify the lens to use on this file. Augeas uses files called lenses to translate a configuration file into an object representation. Augeas ships with several lenses, they are located in /usr/share/augeas/lenses
by default. When specifying the lens in an augeas
resource, the name of the lens is capitalized and has the .lns
suffix. In this case, we will specify the Sysctl
lens as follows:
In general, Augeas changes take the following form:
In this case, the setting will be translated into a line like this in /etc/sysctl.conf
:
For more information about using Puppet and Augeas, see the page on the Puppet Labs website http://projects.puppetlabs.com/projects/1/wiki/Puppet_Augeas.
Another project that uses Augeas is Augeasproviders. Augeasproviders uses Augeas to define several types. One of these types is sysctl
, using this type you can make sysctl changes without knowing how to write the changes in Augeas. More information is available on the forge at https://forge.puppetlabs.com/domcleal/augeasproviders.
Learning how to use Augeas can be a little confusing at first. Augeas provides a command line tool, augtool
, which can be used to get acquainted with making changes in Augeas.
augeas
resource:
base
module as follows:class base { augeas { 'enable-ip-forwarding': incl => '/etc/sysctl.conf', lens => 'Sysctl.lns', changes => ['set net.ipv4.ip_forward 1'], } }
[root@cookbook ~]# puppet agent -t Info: Applying configuration version '1412130479' Notice: Augeas[enable-ip-forwarding](provider=augeas): --- /etc/sysctl.conf 2014-09-04 03:41:09.000000000 -0400 +++ /etc/sysctl.conf.augnew 2014-09-30 22:28:03.503000039 -0400 @@ -4,7 +4,7 @@ # sysctl.conf(5) for more details. # Controls IP packet forwarding -net.ipv4.ip_forward = 0 +net.ipv4.ip_forward = 1 # Controls source route verification net.ipv4.conf.default.rp_filter = 1 Notice: /Stage[main]/Base/Augeas[enable-ip-forwarding]/returns: executed successfully Notice: Finished catalog run in 2.27 seconds
[root@cookbook ~]# sysctl -p |grep ip_forward net.ipv4.ip_forward = 1
We declare an augeas
resource named enable-ip-forwarding
:
We specify that we want to make changes in the file /etc/sysctl.conf
:
Next we specify the lens to use on this file. Augeas uses files called lenses to translate a configuration file into an object representation. Augeas ships with several lenses, they are located in /usr/share/augeas/lenses
by default. When specifying the lens in an augeas
resource, the name of the lens is capitalized and has the .lns
suffix. In this case, we will specify the Sysctl
lens as follows:
In general, Augeas changes take the following form:
In this case, the setting will be translated into a line like this in /etc/sysctl.conf
:
For more information about using Puppet and Augeas, see the page on the Puppet Labs website http://projects.puppetlabs.com/projects/1/wiki/Puppet_Augeas.
Another project that uses Augeas is Augeasproviders. Augeasproviders uses Augeas to define several types. One of these types is sysctl
, using this type you can make sysctl changes without knowing how to write the changes in Augeas. More information is available on the forge at https://forge.puppetlabs.com/domcleal/augeasproviders.
Learning how to use Augeas can be a little confusing at first. Augeas provides a command line tool, augtool
, which can be used to get acquainted with making changes in Augeas.
augeas
resource named enable-ip-forwarding
:
We specify that we want to make changes in the file /etc/sysctl.conf
:
Next we specify the lens to use on this file. Augeas uses files called lenses to translate a configuration file into an object representation. Augeas ships with several lenses, they are located in /usr/share/augeas/lenses
by default. When specifying the lens in an augeas
resource, the name of the lens is capitalized and has the .lns
suffix. In this case, we will specify the Sysctl
lens as follows:
In general, Augeas changes take the following form:
In this case, the setting will be translated into a line like this in /etc/sysctl.conf
:
For more information about using Puppet and Augeas, see the page on the Puppet Labs website http://projects.puppetlabs.com/projects/1/wiki/Puppet_Augeas.
Another project that uses Augeas is Augeasproviders. Augeasproviders uses Augeas to define several types. One of these types is sysctl
, using this type you can make sysctl changes without knowing how to write the changes in Augeas. More information is available on the forge at https://forge.puppetlabs.com/domcleal/augeasproviders.
Learning how to use Augeas can be a little confusing at first. Augeas provides a command line tool, augtool
, which can be used to get acquainted with making changes in Augeas.
/etc/sysctl.conf
as the example because it can contain a wide variety of kernel settings and you may want to change these settings for all sorts of different purposes and in different Puppet classes. You might want to enable IP forwarding, as in the example, for a router class but you might also want to tune the value of net.core.somaxconn
for a load-balancer class.
/etc/sysctl.conf
file and distributing it as a text file won't work because you might have several different and conflicting versions depending on the setting you want to modify. Augeas is the right solution here because you can define augeas
resources in different places, which modify the same file and they won't conflict.
Augeas, see the page on the Puppet Labs website http://projects.puppetlabs.com/projects/1/wiki/Puppet_Augeas.
Another project that uses Augeas is Augeasproviders. Augeasproviders uses Augeas to define several types. One of these types is sysctl
, using this type you can make sysctl changes without knowing how to write the changes in Augeas. More information is available on the forge at https://forge.puppetlabs.com/domcleal/augeasproviders.
Learning how to use Augeas can be a little confusing at first. Augeas provides a command line tool, augtool
, which can be used to get acquainted with making changes in Augeas.
Sometimes you can't deploy a whole config file in one piece, yet making line by line edits isn't enough. Often, you need to build a config file from various bits of configuration managed by different classes. You may run into a situation where local information needs to be imported into the file as well. In this example, we'll build a config file using a local file as well as snippets defined in our manifests.
In your Git repository create an environment.conf
file with the following contents:
Create the public directory and download the module into that directory as follows:
Now that we have the concat
module available on our server, we can create a concat
container resource in our base
module:
Create a concat::fragment
module for the header of the new file:
Create a concat::fragment
that includes a local file:
Create a concat::fragment
module that will go at the end of the file:
On the node, create /etc/hosts.allow.local
with the following contents:
The concat
resource defines a container that will hold all the subsequent concat::fragment
resources. Each concat::fragment
resource references the concat
resource as the target. Each concat::fragment
also includes an order
attribute. The order
attribute is used to specify the order in which the fragments are added to the final file. Our /etc/hosts.allow
file is built with the header line, the contents of the local file, and finally the in.tftpd
line we defined.
concat
module. We will start by installing the concat
module, in a previous example we installed the module to our local machine. In this example, we'll modify the Puppet server configuration and download the module to the Puppet server.
environment.conf
file with the following contents:
Now that we have the concat
module available on our server, we can create a concat
container resource in our base
module:
Create a concat::fragment
module for the header of the new file:
Create a concat::fragment
that includes a local file:
Create a concat::fragment
module that will go at the end of the file:
On the node, create /etc/hosts.allow.local
with the following contents:
The concat
resource defines a container that will hold all the subsequent concat::fragment
resources. Each concat::fragment
resource references the concat
resource as the target. Each concat::fragment
also includes an order
attribute. The order
attribute is used to specify the order in which the fragments are added to the final file. Our /etc/hosts.allow
file is built with the header line, the contents of the local file, and finally the in.tftpd
line we defined.
the concat
module available on our server, we can create a concat
container resource in our base
module:
Create a concat::fragment
module for the header of the new file:
Create a concat::fragment
that includes a local file:
Create a concat::fragment
module that will go at the end of the file:
On the node, create /etc/hosts.allow.local
with the following contents:
The concat
resource defines a container that will hold all the subsequent concat::fragment
resources. Each concat::fragment
resource references the concat
resource as the target. Each concat::fragment
also includes an order
attribute. The order
attribute is used to specify the order in which the fragments are added to the final file. Our /etc/hosts.allow
file is built with the header line, the contents of the local file, and finally the in.tftpd
line we defined.
concat
resource defines
a container that will hold all the subsequent concat::fragment
resources. Each concat::fragment
resource references the concat
resource as the target. Each concat::fragment
also includes an order
attribute. The order
attribute is used to specify the order in which the fragments are added to the final file. Our /etc/hosts.allow
file is built with the header line, the contents of the local file, and finally the in.tftpd
line we defined.
While you can deploy config files easily with Puppet as simple text files, templates are much more powerful. A template file can do calculations, execute Ruby code, or reference the values of variables from your Puppet manifests. Anywhere you might deploy a text file using Puppet, you can use a template instead.
In this example, we'll use an ERB template to insert a password into a backup script:
- Create the file
modules/admin/templates/backup-mysql.sh.erb
with the following contents:#!/bin/sh /usr/bin/mysqldump -uroot \ -p<%= @mysql_password %> \ --all-databases | \ /bin/gzip > /backup/mysql/all-databases.sql.gz
- Modify your
site.pp
file as follows:node 'cookbook' { $mysql_password = 'secret' file { '/usr/local/bin/backup-mysql': content => template('admin/backup-mysql.sh.erb'), mode => '0755', } }
- Run Puppet:
[root@cookbook ~]# puppet agent -t Info: Caching catalog for cookbook.example.com Info: Applying configuration version '1412140971' Notice: /Stage[main]/Main/Node[cookbook]/File[/usr/local/bin/backup-mysql]/ensure: defined content as '{md5}c12af56559ef36529975d568ff52dca5' Notice: Finished catalog run in 0.31 seconds
- Check whether Puppet has correctly inserted the password into the template:
[root@cookbook ~]# cat /usr/local/bin/backup-mysql #!/bin/sh /usr/bin/mysqldump -uroot \ -psecret \ --all-databases | \ /bin/gzip > /backup/mysql/all-databases.sql.gz
Wherever a variable is referenced in the template, for example <%= @mysql_password %>
, Puppet will replace it with the corresponding value, secret
.
modules/admin/templates/backup-mysql.sh.erb
with the following contents:#!/bin/sh /usr/bin/mysqldump -uroot \ -p<%= @mysql_password %> \ --all-databases | \ /bin/gzip > /backup/mysql/all-databases.sql.gz
site.pp
file as follows:node 'cookbook' { $mysql_password = 'secret' file { '/usr/local/bin/backup-mysql': content => template('admin/backup-mysql.sh.erb'), mode => '0755', } }
[root@cookbook ~]# puppet agent -t Info: Caching catalog for cookbook.example.com Info: Applying configuration version '1412140971' Notice: /Stage[main]/Main/Node[cookbook]/File[/usr/local/bin/backup-mysql]/ensure: defined content as '{md5}c12af56559ef36529975d568ff52dca5' Notice: Finished catalog run in 0.31 seconds
[root@cookbook ~]# cat /usr/local/bin/backup-mysql #!/bin/sh /usr/bin/mysqldump -uroot \ -psecret \ --all-databases | \ /bin/gzip > /backup/mysql/all-databases.sql.gz
Wherever a variable is referenced in the template, for example <%= @mysql_password %>
, Puppet will replace it with the corresponding value, secret
.
In the previous example, we saw that you can use Ruby to interpolate different values in templates depending on the result of an expression. But you're not limited to getting one value at a time. You can put lots of them in a Puppet array and then have the template generate some content for each element of the array using a loop.
Follow these steps to build an example of iterating over arrays:
- Modify your
site.pp
file as follows:node 'cookbook' { $ipaddresses = ['192.168.0.1', '158.43.128.1', '10.0.75.207' ] file { '/tmp/addresslist.txt': content => template('base/addresslist.erb') } }
- Create the file
modules/base/templates/addresslist.erb
with the following contents:<% @ipaddresses.each do |ip| -%> IP address <%= ip %> is present <% end -%>
- Run Puppet:
[root@cookbook ~]# puppet agent -t Info: Caching catalog for cookbook.example.com Info: Applying configuration version '1412141917' Notice: /Stage[main]/Main/Node[cookbook]/File[/tmp/addresslist.txt]/ensure: defined content as '{md5}073851229d7b2843830024afb2b3902d' Notice: Finished catalog run in 0.30 seconds
- Check the contents of the generated file:
[root@cookbook ~]# cat /tmp/addresslist.txt IP address 192.168.0.1 is present. IP address 158.43.128.1 is present. IP address 10.0.75.207 is present.
In the first line of the template, we reference the array ipaddresses
, and call its each
method:
site.pp
file as follows:node 'cookbook' { $ipaddresses = ['192.168.0.1', '158.43.128.1', '10.0.75.207' ] file { '/tmp/addresslist.txt': content => template('base/addresslist.erb') } }
modules/base/templates/addresslist.erb
with the following contents:<% @ipaddresses.each do |ip| -%> IP address <%= ip %> is present <% end -%>
[root@cookbook ~]# puppet agent -t Info: Caching catalog for cookbook.example.com Info: Applying configuration version '1412141917' Notice: /Stage[main]/Main/Node[cookbook]/File[/tmp/addresslist.txt]/ensure: defined content as '{md5}073851229d7b2843830024afb2b3902d' Notice: Finished catalog run in 0.30 seconds
[root@cookbook ~]# cat /tmp/addresslist.txt IP address 192.168.0.1 is present. IP address 158.43.128.1 is present. IP address 10.0.75.207 is present.
In the first line of the template, we reference the array ipaddresses
, and call its each
method:
template, we reference the array ipaddresses
, and call its each
method:
EPP templates are a new feature in Puppet 3.5 and newer versions. EPP templates use a syntax similar to ERB templates but are not compiled through Ruby. Two new functions are defined to call EPP templates, epp
, and inline_epp
. These functions are the EPP equivalents of the ERB functions template
and inline_template
, respectively. The main difference with EPP templates is that variables are referenced using the Puppet notation, $variable
instead of @variable
.
- Create an EPP template in
~/puppet/epp-test.epp
with the following content:This is <%= $message %>.
- Create an
epp.pp
manifest, which uses theepp
andinline_epp
functions:$message = "the message" file {'/tmp/epp-test': content => epp('/home/thomas/puppet/epp-test.epp') } notify {inline_epp('Also prints <%= $message %>'):}
- Apply the manifest making sure to use the future parser (the future parser is required for the
epp
andinline_epp
functions to be defined):t@mylaptop ~/puppet $ puppet apply epp.pp --parser=future Notice: Compiled catalog for mylaptop in environment production in 1.03 seconds Notice: /Stage[main]/Main/File[/tmp/epp-test]/ensure: defined content as '{md5}999ccc2507d79d50fae0775d69b63b8c' Notice: Also prints the message
- Verify that the template worked as intended:
t@mylaptop ~/puppet $ cat /tmp/epp-test This is the message.
Both epp
and inline_epp
allow for variables to be overridden within the function call. A second parameter to the function call can be used to specify values for variables used within the scope of the function call. For example, we can override the value of $message
with the following code:
Now when we run Puppet and verify the output we see that the value of $message
has been overridden:
~/puppet/epp-test.epp
with the following content:This is <%= $message %>.
epp.pp
manifest, which uses the epp
and inline_epp
functions:$message = "the message" file {'/tmp/epp-test': content => epp('/home/thomas/puppet/epp-test.epp') } notify {inline_epp('Also prints <%= $message %>'):}
epp
and inline_epp
functions to be defined):t@mylaptop ~/puppet $ puppet apply epp.pp --parser=future Notice: Compiled catalog for mylaptop in environment production in 1.03 seconds Notice: /Stage[main]/Main/File[/tmp/epp-test]/ensure: defined content as '{md5}999ccc2507d79d50fae0775d69b63b8c' Notice: Also prints the message
t@mylaptop ~/puppet $ cat /tmp/epp-test This is the message.
Both epp
and inline_epp
allow for variables to be overridden within the function call. A second parameter to the function call can be used to specify values for variables used within the scope of the function call. For example, we can override the value of $message
with the following code:
Now when we run Puppet and verify the output we see that the value of $message
has been overridden:
epp
and inline_epp
functions are defined. The main difference between EPP templates and ERB templates is that variables are referenced in the same way they are within Puppet manifests.
Both epp
and inline_epp
allow for variables to be overridden within the function call. A second parameter to the function call can be used to specify values for variables used within the scope of the function call. For example, we can override the value of $message
with the following code:
Now when we run Puppet and verify the output we see that the value of $message
has been overridden:
epp
and inline_epp
allow
for variables to be overridden within the function call. A second parameter to the function call can be used to specify values for variables used within the scope of the function call. For example, we can override the value of $message
with the following code:
Now when we run Puppet and verify the output we see that the value of $message
has been overridden:
It's a common requirement for third-party developers and contractors to be able to make changes via Puppet, but they definitely shouldn't see any confidential information. Similarly, if you're using a distributed Puppet setup like that described in Chapter 2, Puppet Infrastructure, every machine has a copy of the whole repo, including secrets for other machines that it doesn't need and shouldn't have. How can we prevent this?
One answer is to encrypt the secrets using the GnuPG tool, so that any secret information in the Puppet repo is undecipherable (for all practical purposes) without the appropriate key. Then we distribute the key securely to the people or machines that need it.
First you'll need an encryption key, so follow these steps to generate one. If you already have a GnuPG key that you'd like to use, go on to the next section. To complete this section, you will need to install the gpg command:
- Use
puppet
resource to install gpg:# puppet resource package gnupg ensure=installed
- Run the following command. Answer the prompts as shown, except to substitute your name and e-mail address for mine. When prompted for a passphrase, just hit Enter:
t@mylaptop ~/puppet $ gpg --gen-key gpg (GnuPG) 1.4.18; Copyright (C) 2014 Free Software Foundation, Inc. This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Please select what kind of key you want: (1) RSA and RSA (default) (2) DSA and Elgamal (3) DSA (sign only) (4) RSA (sign only) Your selection? 1 RSA keys may be between 1024 and 4096 bits long. What keysize do you want? (2048) 2048 Requested keysize is 2048 bits Please specify how long the key should be valid. 0 = key does not expire <n> = key expires in n days <n>w = key expires in n weeks <n>m = key expires in n months <n>y = key expires in n years Key is valid for? (0) 0 Key does not expire at all Is this correct? (y/N) y You need a user ID to identify your key; the software constructs the user ID from the Real Name, Comment and Email Address in this form: "Heinrich Heine (Der Dichter) <[email protected]>" Real name: Thomas Uphill Email address: [email protected] Comment: <enter> You selected this USER-ID: "Thomas Uphill <[email protected]>" Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? o You need a Passphrase to protect your secret key.
Hit enter twice here to have an empty passphrase
You don't want a passphrase - this is probably a *bad* idea! I will do it anyway. You can change your passphrase at any time, using this program with the option "--edit-key". gpg: key F1C1EE49 marked as ultimately trusted public and secret key created and signed. gpg: checking the trustdb gpg: 3 marginal(s) needed, 1 complete(s) needed, PGP trust model gpg: depth: 0 valid: 1 signed: 0 trust: 0-, 0q, 0n, 0m, 0f, 1u pub 2048R/F1C1EE49 2014-10-01 Key fingerprint = 461A CB4C 397F 06A7 FB82 3BAD 63CF 50D8 F1C1 EE49 uid Thomas Uphill <[email protected]> sub 2048R/E2440023 2014-10-01
- You may see a message like this if your system is not configured with a source of randomness:
We need to generate a lot of random bytes. It is a good idea to perform some other action (type on the keyboard, move the mouse, utilize the disks) during the prime generation; this gives the random number generator a better chance to gain enough entropy.
- In this case, install and start a random number generator daemon such as
haveged
orrng-tools
. Copy the gpg key you just created into thepuppet
user's account on your Puppet master:t@mylaptop ~ $ scp -r .gnupg [email protected]: gpg.conf 100% 7680 7.5KB/s 00:00 random_seed 100% 600 0.6KB/s 00:00 pubring.gpg 100% 1196 1.2KB/s 00:00 secring.gpg 100% 2498 2.4KB/s 00:00 trustdb.gpg 100% 1280 1.3KB/s 00:00
With your encryption key installed on the puppet
user's keyring (the key generation process described in the previous section will do this for you), you're ready to set up Puppet to decrypt secrets.
- Create the following directory:
t@cookbook:~/puppet$ mkdir -p modules/admin/lib/puppet/parser/functions
- Create the file
modules/admin/lib/puppet/parser/functions/secret.rb
with the following contents:module Puppet::Parser::Functions newfunction(:secret, :type => :rvalue) do |args| 'gpg --no-tty -d #{args[0]}' end end
- Create the file
secret_message
with the following contents:For a moment, nothing happened. Then, after a second or so, nothing continued to happen.
- Encrypt this file with the following command (use the e-mail address you supplied when creating the GnuPG key):
t@mylaptop ~/puppet $ gpg -e -r [email protected] secret_message
- Move the resulting encrypted file into your Puppet repo:
t@mylaptop:~/puppet$ mv secret_message.gpg modules/admin/files/
- Remove the original (plaintext) file:
t@mylaptop:~/puppet$ rm secret_message
- Modify your
site.pp
file as follows:node 'cookbook' { $message = secret('/etc/puppet/environments/production/ modules/admin/files/secret_message.gpg') notify { "The secret message is: ${message}": } }
- Run Puppet:
[root@cookbook ~]# puppet agent -t Info: Caching catalog for cookbook.example.com Info: Applying configuration version '1412145910' Notice: The secret message is: For a moment, nothing happened. Then, after a second or so, nothing continued to happen. Notice: Finished catalog run in 0.27 seconds
First, we've created a custom function to allow Puppet to decrypt the secret files using GnuPG:
Having set up the secret
function and the required key, we now encrypt a message to this key:
We then call the secret
function to decrypt this file and get the contents:
You should use the secret
function, or something like it, to protect any confidential data in your Puppet repo: passwords, AWS credentials, license keys, even other secret keys such as SSL host keys.
You may decide to use a single key, which you push to machines as they're built, perhaps as part of a bootstrap process like that described in the Bootstrapping Puppet with Bash recipe in Chapter 2, Puppet Infrastructure. For even greater security, you might like to create a new key for each machine, or group of machines, and encrypt a given secret only for the machines that need it.
For example, your web servers might need a certain secret that you don't want to be accessible on any other machine. You could create a key for web servers, and encrypt the data only for this key.
If you want to use encrypted data with Hiera, there is a GnuPG backend for Hiera available at http://www.craigdunn.org/2011/10/secret-variables-in-puppet-with-hiera-and-gpg/.
- The Configuring Hiera recipe in Chapter 2, Puppet Infrastructure
- The Storing secret data with hiera-gpg recipe in Chapter 2, Puppet Infrastructure
encryption key, so follow these steps to generate one. If you already have a GnuPG key that you'd like to use, go on to the next section. To complete this section, you will need to install the gpg command:
- Use
puppet
resource to install gpg:# puppet resource package gnupg ensure=installed
- Run the following command. Answer the prompts as shown, except to substitute your name and e-mail address for mine. When prompted for a passphrase, just hit Enter:
t@mylaptop ~/puppet $ gpg --gen-key gpg (GnuPG) 1.4.18; Copyright (C) 2014 Free Software Foundation, Inc. This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Please select what kind of key you want: (1) RSA and RSA (default) (2) DSA and Elgamal (3) DSA (sign only) (4) RSA (sign only) Your selection? 1 RSA keys may be between 1024 and 4096 bits long. What keysize do you want? (2048) 2048 Requested keysize is 2048 bits Please specify how long the key should be valid. 0 = key does not expire <n> = key expires in n days <n>w = key expires in n weeks <n>m = key expires in n months <n>y = key expires in n years Key is valid for? (0) 0 Key does not expire at all Is this correct? (y/N) y You need a user ID to identify your key; the software constructs the user ID from the Real Name, Comment and Email Address in this form: "Heinrich Heine (Der Dichter) <[email protected]>" Real name: Thomas Uphill Email address: [email protected] Comment: <enter> You selected this USER-ID: "Thomas Uphill <[email protected]>" Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? o You need a Passphrase to protect your secret key.
Hit enter twice here to have an empty passphrase
You don't want a passphrase - this is probably a *bad* idea! I will do it anyway. You can change your passphrase at any time, using this program with the option "--edit-key". gpg: key F1C1EE49 marked as ultimately trusted public and secret key created and signed. gpg: checking the trustdb gpg: 3 marginal(s) needed, 1 complete(s) needed, PGP trust model gpg: depth: 0 valid: 1 signed: 0 trust: 0-, 0q, 0n, 0m, 0f, 1u pub 2048R/F1C1EE49 2014-10-01 Key fingerprint = 461A CB4C 397F 06A7 FB82 3BAD 63CF 50D8 F1C1 EE49 uid Thomas Uphill <[email protected]> sub 2048R/E2440023 2014-10-01
- You may see a message like this if your system is not configured with a source of randomness:
We need to generate a lot of random bytes. It is a good idea to perform some other action (type on the keyboard, move the mouse, utilize the disks) during the prime generation; this gives the random number generator a better chance to gain enough entropy.
- In this case, install and start a random number generator daemon such as
haveged
orrng-tools
. Copy the gpg key you just created into thepuppet
user's account on your Puppet master:t@mylaptop ~ $ scp -r .gnupg [email protected]: gpg.conf 100% 7680 7.5KB/s 00:00 random_seed 100% 600 0.6KB/s 00:00 pubring.gpg 100% 1196 1.2KB/s 00:00 secring.gpg 100% 2498 2.4KB/s 00:00 trustdb.gpg 100% 1280 1.3KB/s 00:00
With your encryption key installed on the puppet
user's keyring (the key generation process described in the previous section will do this for you), you're ready to set up Puppet to decrypt secrets.
- Create the following directory:
t@cookbook:~/puppet$ mkdir -p modules/admin/lib/puppet/parser/functions
- Create the file
modules/admin/lib/puppet/parser/functions/secret.rb
with the following contents:module Puppet::Parser::Functions newfunction(:secret, :type => :rvalue) do |args| 'gpg --no-tty -d #{args[0]}' end end
- Create the file
secret_message
with the following contents:For a moment, nothing happened. Then, after a second or so, nothing continued to happen.
- Encrypt this file with the following command (use the e-mail address you supplied when creating the GnuPG key):
t@mylaptop ~/puppet $ gpg -e -r [email protected] secret_message
- Move the resulting encrypted file into your Puppet repo:
t@mylaptop:~/puppet$ mv secret_message.gpg modules/admin/files/
- Remove the original (plaintext) file:
t@mylaptop:~/puppet$ rm secret_message
- Modify your
site.pp
file as follows:node 'cookbook' { $message = secret('/etc/puppet/environments/production/ modules/admin/files/secret_message.gpg') notify { "The secret message is: ${message}": } }
- Run Puppet:
[root@cookbook ~]# puppet agent -t Info: Caching catalog for cookbook.example.com Info: Applying configuration version '1412145910' Notice: The secret message is: For a moment, nothing happened. Then, after a second or so, nothing continued to happen. Notice: Finished catalog run in 0.27 seconds
First, we've created a custom function to allow Puppet to decrypt the secret files using GnuPG:
Having set up the secret
function and the required key, we now encrypt a message to this key:
We then call the secret
function to decrypt this file and get the contents:
You should use the secret
function, or something like it, to protect any confidential data in your Puppet repo: passwords, AWS credentials, license keys, even other secret keys such as SSL host keys.
You may decide to use a single key, which you push to machines as they're built, perhaps as part of a bootstrap process like that described in the Bootstrapping Puppet with Bash recipe in Chapter 2, Puppet Infrastructure. For even greater security, you might like to create a new key for each machine, or group of machines, and encrypt a given secret only for the machines that need it.
For example, your web servers might need a certain secret that you don't want to be accessible on any other machine. You could create a key for web servers, and encrypt the data only for this key.
If you want to use encrypted data with Hiera, there is a GnuPG backend for Hiera available at http://www.craigdunn.org/2011/10/secret-variables-in-puppet-with-hiera-and-gpg/.
- The Configuring Hiera recipe in Chapter 2, Puppet Infrastructure
- The Storing secret data with hiera-gpg recipe in Chapter 2, Puppet Infrastructure
installed on the puppet
user's keyring (the key generation process described in the previous section will do this for you), you're ready to set up Puppet to decrypt secrets.
- Create the following directory:
t@cookbook:~/puppet$ mkdir -p modules/admin/lib/puppet/parser/functions
- Create the file
modules/admin/lib/puppet/parser/functions/secret.rb
with the following contents:module Puppet::Parser::Functions newfunction(:secret, :type => :rvalue) do |args| 'gpg --no-tty -d #{args[0]}' end end
- Create the file
secret_message
with the following contents:For a moment, nothing happened. Then, after a second or so, nothing continued to happen.
- Encrypt this file with the following command (use the e-mail address you supplied when creating the GnuPG key):
t@mylaptop ~/puppet $ gpg -e -r [email protected] secret_message
- Move the resulting encrypted file into your Puppet repo:
t@mylaptop:~/puppet$ mv secret_message.gpg modules/admin/files/
- Remove the original (plaintext) file:
t@mylaptop:~/puppet$ rm secret_message
- Modify your
site.pp
file as follows:node 'cookbook' { $message = secret('/etc/puppet/environments/production/ modules/admin/files/secret_message.gpg') notify { "The secret message is: ${message}": } }
- Run Puppet:
[root@cookbook ~]# puppet agent -t Info: Caching catalog for cookbook.example.com Info: Applying configuration version '1412145910' Notice: The secret message is: For a moment, nothing happened. Then, after a second or so, nothing continued to happen. Notice: Finished catalog run in 0.27 seconds
First, we've created a custom function to allow Puppet to decrypt the secret files using GnuPG:
Having set up the secret
function and the required key, we now encrypt a message to this key:
We then call the secret
function to decrypt this file and get the contents:
You should use the secret
function, or something like it, to protect any confidential data in your Puppet repo: passwords, AWS credentials, license keys, even other secret keys such as SSL host keys.
You may decide to use a single key, which you push to machines as they're built, perhaps as part of a bootstrap process like that described in the Bootstrapping Puppet with Bash recipe in Chapter 2, Puppet Infrastructure. For even greater security, you might like to create a new key for each machine, or group of machines, and encrypt a given secret only for the machines that need it.
For example, your web servers might need a certain secret that you don't want to be accessible on any other machine. You could create a key for web servers, and encrypt the data only for this key.
If you want to use encrypted data with Hiera, there is a GnuPG backend for Hiera available at http://www.craigdunn.org/2011/10/secret-variables-in-puppet-with-hiera-and-gpg/.
- The Configuring Hiera recipe in Chapter 2, Puppet Infrastructure
- The Storing secret data with hiera-gpg recipe in Chapter 2, Puppet Infrastructure
custom function to allow Puppet to decrypt the secret files using GnuPG:
Having set up the secret
function and the required key, we now encrypt a message to this key:
We then call the secret
function to decrypt this file and get the contents:
You should use the secret
function, or something like it, to protect any confidential data in your Puppet repo: passwords, AWS credentials, license keys, even other secret keys such as SSL host keys.
You may decide to use a single key, which you push to machines as they're built, perhaps as part of a bootstrap process like that described in the Bootstrapping Puppet with Bash recipe in Chapter 2, Puppet Infrastructure. For even greater security, you might like to create a new key for each machine, or group of machines, and encrypt a given secret only for the machines that need it.
For example, your web servers might need a certain secret that you don't want to be accessible on any other machine. You could create a key for web servers, and encrypt the data only for this key.
If you want to use encrypted data with Hiera, there is a GnuPG backend for Hiera available at http://www.craigdunn.org/2011/10/secret-variables-in-puppet-with-hiera-and-gpg/.
- The Configuring Hiera recipe in Chapter 2, Puppet Infrastructure
- The Storing secret data with hiera-gpg recipe in Chapter 2, Puppet Infrastructure
You may decide to use a single key, which you push to machines as they're built, perhaps as part of a bootstrap process like that described in the Bootstrapping Puppet with Bash recipe in Chapter 2, Puppet Infrastructure. For even greater security, you might like to create a new key for each machine, or group of machines, and encrypt a given secret only for the machines that need it.
For example, your web servers might need a certain secret that you don't want to be accessible on any other machine. You could create a key for web servers, and encrypt the data only for this key.
If you want to use encrypted data with Hiera, there is a GnuPG backend for Hiera available at http://www.craigdunn.org/2011/10/secret-variables-in-puppet-with-hiera-and-gpg/.
- The Configuring Hiera recipe in Chapter 2, Puppet Infrastructure
- The Storing secret data with hiera-gpg recipe in Chapter 2, Puppet Infrastructure
Most often you will want to install packages from the main distribution repo, so a simple package resource will do:
In this example, we'll use the popular Percona APT repo (Percona is a MySQL consulting firm who maintain and release their own specialized version of MySQL, more information is available at http://www.percona.com/software/repositories):
- Create the file
modules/admin/manifests/percona_repo.pp
with the following contents:# Install Percona APT repo class admin::percona_repo { exec { 'add-percona-apt-key': unless => '/usr/bin/apt-key list |grep percona', command => '/usr/bin/gpg --keyserver hkp://keys.gnupg.net --recv-keys 1C4CBDCDCD2EFD2A && /usr/bin/gpg -a --export CD2EFD2A | apt-key add -', notify => Exec['percona-apt-update'], } exec { 'percona-apt-update': command => '/usr/bin/apt-get update', require => [File['/etc/apt/sources.list.d/percona.list'], File['/etc/apt/preferences.d/00percona.pref']], refreshonly => true, } file { '/etc/apt/sources.list.d/percona.list': content => 'deb http://repo.percona.com/apt wheezy main', notify => Exec['percona-apt-update'], } file { '/etc/apt/preferences.d/00percona.pref': content => "Package: *\nPin: release o=Percona Development Team\nPin-Priority: 1001", notify => Exec['percona-apt-update'], } }
- Modify your
site.pp
file as follows:node 'cookbook' { include admin::percona_repo package { 'percona-server-server-5.5': ensure => installed, require => Class['admin::percona_repo'], } }
- Run Puppet:
root@cookbook-deb:~# puppet agent -t Info: Caching catalog for cookbook-deb Notice: /Stage[main]/Admin::Percona_repo/Exec[add-percona-apt-key]/returns: executed successfully Info: /Stage[main]/Admin::Percona_repo/Exec[add-percona-apt-key]: Scheduling refresh of Exec[percona-apt-update] Notice: /Stage[main]/Admin::Percona_repo/File[/etc/apt/sources.list.d/percona.list]/ensure: defined content as '{md5}b8d479374497255804ffbf0a7bcdf6c2' Info: /Stage[main]/Admin::Percona_repo/File[/etc/apt/sources.list.d/percona.list]: Scheduling refresh of Exec[percona-apt-update] Notice: /Stage[main]/Admin::Percona_repo/File[/etc/apt/preferences.d/00percona.pref]/ensure: defined content as '{md5}1d8ca6c1e752308a9bd3018713e2d1ad' Info: /Stage[main]/Admin::Percona_repo/File[/etc/apt/preferences.d/00percona.pref]: Scheduling refresh of Exec[percona-apt-update] Notice: /Stage[main]/Admin::Percona_repo/Exec[percona-apt-update]: Triggered 'refresh' from 3 events
In order to install any Percona package, we first need to have the repository configuration installed on the machine. This is why the percona-server-server-5.5
package (Percona's version of the standard MySQL server) requires the admin::percona_repo
class:
So, what does the admin::percona_repo
class do? It:
First of all, we install the APT key:
The unless
parameter checks the output of apt-key list
to make sure that the Percona key is not already installed, in which case we need not do anything. Assuming it isn't, the command
runs:
Having installed the key, we add the repo configuration:
Then run apt-get update
to update the system's APT cache with the metadata from the new repo:
Finally, we configure the APT pin priority for the repo:
This ensures that packages installed from the Percona repo will never be superseded by packages from somewhere else (the main Ubuntu distro, for example). Otherwise, you could end up with broken dependencies and be unable to install the Percona packages automatically.
The APT package framework is specific to the Debian and Ubuntu systems. There is a forge module for managing apt repos, https://forge.puppetlabs.com/puppetlabs/apt. If you're on a Red Hat or CentOS-based system, you can use the yumrepo
resources to manage RPM repositories directly:
http://docs.puppetlabs.com/references/latest/type.html#yumrepo
the popular Percona APT repo (Percona is a MySQL consulting firm who maintain and release their own specialized version of MySQL, more information is available at http://www.percona.com/software/repositories):
- Create the file
modules/admin/manifests/percona_repo.pp
with the following contents:# Install Percona APT repo class admin::percona_repo { exec { 'add-percona-apt-key': unless => '/usr/bin/apt-key list |grep percona', command => '/usr/bin/gpg --keyserver hkp://keys.gnupg.net --recv-keys 1C4CBDCDCD2EFD2A && /usr/bin/gpg -a --export CD2EFD2A | apt-key add -', notify => Exec['percona-apt-update'], } exec { 'percona-apt-update': command => '/usr/bin/apt-get update', require => [File['/etc/apt/sources.list.d/percona.list'], File['/etc/apt/preferences.d/00percona.pref']], refreshonly => true, } file { '/etc/apt/sources.list.d/percona.list': content => 'deb http://repo.percona.com/apt wheezy main', notify => Exec['percona-apt-update'], } file { '/etc/apt/preferences.d/00percona.pref': content => "Package: *\nPin: release o=Percona Development Team\nPin-Priority: 1001", notify => Exec['percona-apt-update'], } }
- Modify your
site.pp
file as follows:node 'cookbook' { include admin::percona_repo package { 'percona-server-server-5.5': ensure => installed, require => Class['admin::percona_repo'], } }
- Run Puppet:
root@cookbook-deb:~# puppet agent -t Info: Caching catalog for cookbook-deb Notice: /Stage[main]/Admin::Percona_repo/Exec[add-percona-apt-key]/returns: executed successfully Info: /Stage[main]/Admin::Percona_repo/Exec[add-percona-apt-key]: Scheduling refresh of Exec[percona-apt-update] Notice: /Stage[main]/Admin::Percona_repo/File[/etc/apt/sources.list.d/percona.list]/ensure: defined content as '{md5}b8d479374497255804ffbf0a7bcdf6c2' Info: /Stage[main]/Admin::Percona_repo/File[/etc/apt/sources.list.d/percona.list]: Scheduling refresh of Exec[percona-apt-update] Notice: /Stage[main]/Admin::Percona_repo/File[/etc/apt/preferences.d/00percona.pref]/ensure: defined content as '{md5}1d8ca6c1e752308a9bd3018713e2d1ad' Info: /Stage[main]/Admin::Percona_repo/File[/etc/apt/preferences.d/00percona.pref]: Scheduling refresh of Exec[percona-apt-update] Notice: /Stage[main]/Admin::Percona_repo/Exec[percona-apt-update]: Triggered 'refresh' from 3 events
In order to install any Percona package, we first need to have the repository configuration installed on the machine. This is why the percona-server-server-5.5
package (Percona's version of the standard MySQL server) requires the admin::percona_repo
class:
So, what does the admin::percona_repo
class do? It:
First of all, we install the APT key:
The unless
parameter checks the output of apt-key list
to make sure that the Percona key is not already installed, in which case we need not do anything. Assuming it isn't, the command
runs:
Having installed the key, we add the repo configuration:
Then run apt-get update
to update the system's APT cache with the metadata from the new repo:
Finally, we configure the APT pin priority for the repo:
This ensures that packages installed from the Percona repo will never be superseded by packages from somewhere else (the main Ubuntu distro, for example). Otherwise, you could end up with broken dependencies and be unable to install the Percona packages automatically.
The APT package framework is specific to the Debian and Ubuntu systems. There is a forge module for managing apt repos, https://forge.puppetlabs.com/puppetlabs/apt. If you're on a Red Hat or CentOS-based system, you can use the yumrepo
resources to manage RPM repositories directly:
http://docs.puppetlabs.com/references/latest/type.html#yumrepo
Percona package, we first need to have the repository configuration installed on the machine. This is why the percona-server-server-5.5
package (Percona's version of the standard MySQL server) requires the admin::percona_repo
class:
So, what does the admin::percona_repo
class do? It:
First of all, we install the APT key:
The unless
parameter checks the output of apt-key list
to make sure that the Percona key is not already installed, in which case we need not do anything. Assuming it isn't, the command
runs:
Having installed the key, we add the repo configuration:
Then run apt-get update
to update the system's APT cache with the metadata from the new repo:
Finally, we configure the APT pin priority for the repo:
This ensures that packages installed from the Percona repo will never be superseded by packages from somewhere else (the main Ubuntu distro, for example). Otherwise, you could end up with broken dependencies and be unable to install the Percona packages automatically.
The APT package framework is specific to the Debian and Ubuntu systems. There is a forge module for managing apt repos, https://forge.puppetlabs.com/puppetlabs/apt. If you're on a Red Hat or CentOS-based system, you can use the yumrepo
resources to manage RPM repositories directly:
http://docs.puppetlabs.com/references/latest/type.html#yumrepo
for managing apt repos, https://forge.puppetlabs.com/puppetlabs/apt. If you're on a Red Hat or CentOS-based system, you can use the yumrepo
resources to manage RPM repositories directly:
http://docs.puppetlabs.com/references/latest/type.html#yumrepo
Package version numbers are odd things. They look like decimal numbers, but they're not: a version number is often in the form of 2.6.4
, for example. If you need to compare one version number with another, you can't do a straightforward string comparison: 2.6.4
would be interpreted as greater than 2.6.12
. And a numeric comparison won't work because they're not valid numbers.
Puppet's versioncmp
function comes to the rescue. If you pass two things that look like version numbers, it will compare them and return a value indicating which is greater:
Here's an example using the versioncmp
function:
- Modify your
site.pp
file as follows:node 'cookbook' { $app_version = '1.2.2' $min_version = '1.2.10' if versioncmp($app_version, $min_version) >= 0 { notify { 'Version OK': } } else { notify { 'Upgrade needed': } } }
- Run Puppet:
[root@cookbook ~]# puppet agent -t Info: Caching catalog for cookbook.example.com Notice: Upgrade needed
- Now change the value of
$app_version
:$app_version = '1.2.14'
- Run Puppet again:
[root@cookbook ~]# puppet agent -t Info: Caching catalog for cookbook.example.com Notice: Version OK
versioncmp
function:
site.pp
file as follows:node 'cookbook' { $app_version = '1.2.2' $min_version = '1.2.10' if versioncmp($app_version, $min_version) >= 0 { notify { 'Version OK': } } else { notify { 'Upgrade needed': } } }
[root@cookbook ~]# puppet agent -t Info: Caching catalog for cookbook.example.com Notice: Upgrade needed
$app_version
:$app_version = '1.2.14'
$min_version
) is 1.2.10
. So, in the first example, we want to compare it with $app_version
of 1.2.2
. A simple alphabetic comparison of these two strings (in Ruby, for example) would give the wrong result, but versioncmp
correctly determines that 1.2.2
is less than 1.2.10
and alerts us that we need to upgrade.
$app_version
is now 1.2.14
, which versioncmp
correctly recognizes as greater than $min_version
and so we get the message Version OK.