"By all means leave the road when you wish. That is precisely the use of a road: to reach individually chosen points of departure." | ||
--Robert Bringhurst, The Elements of Typographic Style |
In this chapter, we will cover the following recipes:
- Creating custom facts
- Adding external facts
- Setting facts as environment variables
- Generating manifests with the Puppet resource command
- Generating manifests with other tools
- Using an external node classifier
- Creating your own resource types
- Creating your own providers
- Creating custom functions
- Testing your Puppet manifests with rspec-puppet
- Using librarian-puppet
- Using r10k
Puppet is a useful tool by itself, but you can get much greater benefits by using Puppet in combination with other tools and frameworks. We'll look at some ways of getting data into Puppet, including custom Facter facts, external facts, and tools to generate Puppet manifests automatically from the existing configuration.
While Facter's built-in facts are useful, it's actually quite easy to add your own facts. For example, if you have machines in different data centers or hosting providers, you could add a custom fact for this so that Puppet can determine whether any local settings need to be applied (for example, local DNS servers or network routes).
Here's an example of a simple custom fact:
- Create the directory
modules/facts/lib/facter
and then create the filemodules/facts/lib/facter/hello.rb
with the following contents:Facter.add(:hello) do setcode do "Hello, world" end end
- Modify your
site.pp
file as follows:node 'cookbook' { notify { $::hello: } }
- Run Puppet:
[root@cookbook ~]# puppet agent -t Notice: /File[/var/lib/puppet/lib/facter/hello.rb]/ensure: defined content as '{md5}f66d5e290459388c5ffb3694dd22388b' Info: Loading facts Info: Caching catalog for cookbook.example.com Info: Applying configuration version '1416205745' Notice: Hello, world Notice: /Stage[main]/Main/Node[cookbook]/Notify[Hello, world]/message: defined 'message' as 'Hello, world' Notice: Finished catalog run in 0.53 seconds
Facter facts are defined in Ruby files that are distributed with facter. Puppet can add additional facts to facter by creating files within the lib/facter
subdirectory of a module. These files are then transferred to client nodes as we saw earlier with the puppetlabs-stdlib
module. To have the command-line facter use these puppet
facts, append the -p
option to facter as shown in the following command line:
To reference the fact in your manifests, just use its name like a built-in fact:
The name of the Ruby file that holds the fact definition is irrelevant. You can name this file whatever you wish; the name of the fact comes from the Facter.add()
function call. You may also call this function several times within a single Ruby file to define multiple facts as necessary. For instance, you could grep
the /proc/meminfo
file and return several facts based on memory information as shown in the meminfo.rb
file in the following code snippet:
You can extend the use of facts to build a completely nodeless Puppet configuration; in other words, Puppet can decide what resources to apply to a machine, based solely on the results of facts. Jordan Sissel has written about this approach at http://www.semicomplete.com/blog/geekery/puppet-nodeless-configuration.html.
You can find out more about custom facts, including how to make sure that OS-specific facts work only on the relevant systems, and how to weigh facts so that they're evaluated in a specific order at the puppetlabs website:
- The Importing dynamic information recipe in Chapter 3, Writing Better Manifests
- The Configuring Hiera recipe in Chapter 2, Puppet Infrastructure
modules/facts/lib/facter
and then create the file modules/facts/lib/facter/hello.rb
with the following contents:Facter.add(:hello) do setcode do "Hello, world" end end
site.pp
file as follows:node 'cookbook' { notify { $::hello: } }
[root@cookbook ~]# puppet agent -t Notice: /File[/var/lib/puppet/lib/facter/hello.rb]/ensure: defined content as '{md5}f66d5e290459388c5ffb3694dd22388b' Info: Loading facts Info: Caching catalog for cookbook.example.com Info: Applying configuration version '1416205745' Notice: Hello, world Notice: /Stage[main]/Main/Node[cookbook]/Notify[Hello, world]/message: defined 'message' as 'Hello, world' Notice: Finished catalog run in 0.53 seconds
Facter facts are defined in Ruby files that are distributed with facter. Puppet can add additional facts to facter by creating files within the lib/facter
subdirectory of a module. These files are then transferred to client nodes as we saw earlier with the puppetlabs-stdlib
module. To have the command-line facter use these puppet
facts, append the -p
option to facter as shown in the following command line:
To reference the fact in your manifests, just use its name like a built-in fact:
The name of the Ruby file that holds the fact definition is irrelevant. You can name this file whatever you wish; the name of the fact comes from the Facter.add()
function call. You may also call this function several times within a single Ruby file to define multiple facts as necessary. For instance, you could grep
the /proc/meminfo
file and return several facts based on memory information as shown in the meminfo.rb
file in the following code snippet:
You can extend the use of facts to build a completely nodeless Puppet configuration; in other words, Puppet can decide what resources to apply to a machine, based solely on the results of facts. Jordan Sissel has written about this approach at http://www.semicomplete.com/blog/geekery/puppet-nodeless-configuration.html.
You can find out more about custom facts, including how to make sure that OS-specific facts work only on the relevant systems, and how to weigh facts so that they're evaluated in a specific order at the puppetlabs website:
- The Importing dynamic information recipe in Chapter 3, Writing Better Manifests
- The Configuring Hiera recipe in Chapter 2, Puppet Infrastructure
The name of the Ruby file that holds the fact definition is irrelevant. You can name this file whatever you wish; the name of the fact comes from the Facter.add()
function call. You may also call this function several times within a single Ruby file to define multiple facts as necessary. For instance, you could grep
the /proc/meminfo
file and return several facts based on memory information as shown in the meminfo.rb
file in the following code snippet:
You can extend the use of facts to build a completely nodeless Puppet configuration; in other words, Puppet can decide what resources to apply to a machine, based solely on the results of facts. Jordan Sissel has written about this approach at http://www.semicomplete.com/blog/geekery/puppet-nodeless-configuration.html.
You can find out more about custom facts, including how to make sure that OS-specific facts work only on the relevant systems, and how to weigh facts so that they're evaluated in a specific order at the puppetlabs website:
- The Importing dynamic information recipe in Chapter 3, Writing Better Manifests
- The Configuring Hiera recipe in Chapter 2, Puppet Infrastructure
Facter.add()
function call. You may also call this function several times within a single Ruby file to define multiple facts as necessary. For instance, you could grep
the /proc/meminfo
file and return several facts based on memory information as shown in the meminfo.rb
file
in the following code snippet:
You can extend the use of facts to build a completely nodeless Puppet configuration; in other words, Puppet can decide what resources to apply to a machine, based solely on the results of facts. Jordan Sissel has written about this approach at http://www.semicomplete.com/blog/geekery/puppet-nodeless-configuration.html.
You can find out more about custom facts, including how to make sure that OS-specific facts work only on the relevant systems, and how to weigh facts so that they're evaluated in a specific order at the puppetlabs website:
- The Importing dynamic information recipe in Chapter 3, Writing Better Manifests
- The Configuring Hiera recipe in Chapter 2, Puppet Infrastructure
The Creating custom facts recipe describes how to add extra facts written in Ruby. You can also create facts from simple text files or scripts with external facts instead.
Here's what you need to do to prepare your system to add external facts:
- You'll need Facter Version 1.7 or higher to use external facts, so look up the value of
facterversion
or usefacter -v
:[root@cookbook ~]# facter facterversion 2.3.0 [root@cookbook ~]# facter -v 2.3.0
- You'll also need to create the external facts directory, using the following command:
[root@cookbook ~]# mkdir -p /etc/facter/facts.d
- Create the file
/etc/facter/facts.d/local.txt
with the following contents:model=ED-209
- Run the following command:
[root@cookbook ~]# facter model ED-209
Well, that was easy! You can add more facts to the same file, or other files, of course, as follows:
model=ED-209 builder=OCP directives=4
However, what if you need to compute a fact in some way, for example, the number of logged-in users? You can create executable facts to do this.
- Create the file
/etc/facter/facts.d/users.sh
with the following contents:#!/bin/sh echo users=`who |wc -l`
- Make this file executable with the following command:
[root@cookbook ~]# chmod a+x /etc/facter/facts.d/users.sh
- Now check the
users
value with the following command:[root@cookbook ~]# facter users 2
In this example, we'll create an external fact by creating files on the node. We'll also show how to override a previously defined fact.
- Current versions of Facter will look into
/etc/facter/facts.d
for files of type.txt
,.json
, or.yaml
. If facter finds a text file, it will parse the file forkey=value
pairs and add the key as a new fact:[root@cookbook ~]# facter model ED-209
- If the file is a YAML or JSON file, then facter will parse the file for
key=value
pairs in the respective format. For YAML, for instance:--- registry: NCC-68814 class: Andromeda shipname: USS Prokofiev
- The resulting output will be as follows:
[root@cookbook ~]# facter registry class shipname class => Andromeda registry => NCC-68814 shipname => USS Prokofiev
- In the case of executable files, Facter will assume that their output is a list of
key=value
pairs. It will execute all the files in thefacts.d
directory and add their output to the internal fact hash. - In the
users
example, Facter will execute theusers.sh
script, which results in the following output:users=2
- It will then search this output for
users
and return the matching value:[root@cookbook ~]# facter users 2
- If there are multiple matches for the key you specified, Facter determines which fact to return based on a weight property. In my version of facter, the weight of external facts is 10,000 (defined in
facter/util/directory_loader.rb
asEXTERNAL_FACT_WEIGHT
). This high value is to ensure that the facts you define can override the supplied facts. For example:[root@cookbook ~]# facter architecture x86_64 [root@cookbook ~]# echo "architecture=ppc64">>/etc/facter/facts.d/myfacts.txt [root@cookbook ~]# facter architecture ppc64
If you're having trouble getting Facter to recognize your external facts, run Facter in debug mode to see what's happening:
The X
JSON file was parsed but returned an empty data set error, which means Facter didn't find any key=value
pairs in the file or (in the case of an executable fact) in its output.
- The Importing dynamic information recipe in Chapter 3, Writing Better Manifests
- The Configuring Hiera recipe in Chapter 2, Puppet Infrastructure
- The Creating custom facts recipe in this chapter
facterversion
or use facter -v
:[root@cookbook ~]# facter facterversion 2.3.0 [root@cookbook ~]# facter -v 2.3.0
[root@cookbook ~]# mkdir -p /etc/facter/facts.d
- Create the file
/etc/facter/facts.d/local.txt
with the following contents:model=ED-209
- Run the following command:
[root@cookbook ~]# facter model ED-209
Well, that was easy! You can add more facts to the same file, or other files, of course, as follows:
model=ED-209 builder=OCP directives=4
However, what if you need to compute a fact in some way, for example, the number of logged-in users? You can create executable facts to do this.
- Create the file
/etc/facter/facts.d/users.sh
with the following contents:#!/bin/sh echo users=`who |wc -l`
- Make this file executable with the following command:
[root@cookbook ~]# chmod a+x /etc/facter/facts.d/users.sh
- Now check the
users
value with the following command:[root@cookbook ~]# facter users 2
In this example, we'll create an external fact by creating files on the node. We'll also show how to override a previously defined fact.
- Current versions of Facter will look into
/etc/facter/facts.d
for files of type.txt
,.json
, or.yaml
. If facter finds a text file, it will parse the file forkey=value
pairs and add the key as a new fact:[root@cookbook ~]# facter model ED-209
- If the file is a YAML or JSON file, then facter will parse the file for
key=value
pairs in the respective format. For YAML, for instance:--- registry: NCC-68814 class: Andromeda shipname: USS Prokofiev
- The resulting output will be as follows:
[root@cookbook ~]# facter registry class shipname class => Andromeda registry => NCC-68814 shipname => USS Prokofiev
- In the case of executable files, Facter will assume that their output is a list of
key=value
pairs. It will execute all the files in thefacts.d
directory and add their output to the internal fact hash. - In the
users
example, Facter will execute theusers.sh
script, which results in the following output:users=2
- It will then search this output for
users
and return the matching value:[root@cookbook ~]# facter users 2
- If there are multiple matches for the key you specified, Facter determines which fact to return based on a weight property. In my version of facter, the weight of external facts is 10,000 (defined in
facter/util/directory_loader.rb
asEXTERNAL_FACT_WEIGHT
). This high value is to ensure that the facts you define can override the supplied facts. For example:[root@cookbook ~]# facter architecture x86_64 [root@cookbook ~]# echo "architecture=ppc64">>/etc/facter/facts.d/myfacts.txt [root@cookbook ~]# facter architecture ppc64
If you're having trouble getting Facter to recognize your external facts, run Facter in debug mode to see what's happening:
The X
JSON file was parsed but returned an empty data set error, which means Facter didn't find any key=value
pairs in the file or (in the case of an executable fact) in its output.
- The Importing dynamic information recipe in Chapter 3, Writing Better Manifests
- The Configuring Hiera recipe in Chapter 2, Puppet Infrastructure
- The Creating custom facts recipe in this chapter
/etc/facter/facts.d/local.txt
with the following contents:model=ED-209
[root@cookbook ~]# facter model ED-209
Well, that was easy! You can add more facts to the same file, or other files, of course, as follows:
model=ED-209 builder=OCP directives=4
However, what if you need to compute a fact in some way, for example, the number of logged-in users? You can create executable facts to do this.
/etc/facter/facts.d/users.sh
with the following contents:#!/bin/sh echo users=`who |wc -l`
[root@cookbook ~]# chmod a+x /etc/facter/facts.d/users.sh
users
value with the following command:[root@cookbook ~]# facter users 2
In this example, we'll create an external fact by creating files on the node. We'll also show how to override a previously defined fact.
- Current versions of Facter will look into
/etc/facter/facts.d
for files of type.txt
,.json
, or.yaml
. If facter finds a text file, it will parse the file forkey=value
pairs and add the key as a new fact:[root@cookbook ~]# facter model ED-209
- If the file is a YAML or JSON file, then facter will parse the file for
key=value
pairs in the respective format. For YAML, for instance:--- registry: NCC-68814 class: Andromeda shipname: USS Prokofiev
- The resulting output will be as follows:
[root@cookbook ~]# facter registry class shipname class => Andromeda registry => NCC-68814 shipname => USS Prokofiev
- In the case of executable files, Facter will assume that their output is a list of
key=value
pairs. It will execute all the files in thefacts.d
directory and add their output to the internal fact hash. - In the
users
example, Facter will execute theusers.sh
script, which results in the following output:users=2
- It will then search this output for
users
and return the matching value:[root@cookbook ~]# facter users 2
- If there are multiple matches for the key you specified, Facter determines which fact to return based on a weight property. In my version of facter, the weight of external facts is 10,000 (defined in
facter/util/directory_loader.rb
asEXTERNAL_FACT_WEIGHT
). This high value is to ensure that the facts you define can override the supplied facts. For example:[root@cookbook ~]# facter architecture x86_64 [root@cookbook ~]# echo "architecture=ppc64">>/etc/facter/facts.d/myfacts.txt [root@cookbook ~]# facter architecture ppc64
If you're having trouble getting Facter to recognize your external facts, run Facter in debug mode to see what's happening:
The X
JSON file was parsed but returned an empty data set error, which means Facter didn't find any key=value
pairs in the file or (in the case of an executable fact) in its output.
- The Importing dynamic information recipe in Chapter 3, Writing Better Manifests
- The Configuring Hiera recipe in Chapter 2, Puppet Infrastructure
- The Creating custom facts recipe in this chapter
- Current versions of Facter will look into
/etc/facter/facts.d
for files of type.txt
,.json
, or.yaml
. If facter finds a text file, it will parse the file forkey=value
pairs and add the key as a new fact:[root@cookbook ~]# facter model ED-209
- If the file is a YAML or JSON file, then facter will parse the file for
key=value
pairs in the respective format. For YAML, for instance:--- registry: NCC-68814 class: Andromeda shipname: USS Prokofiev
- The resulting output will be as follows:
[root@cookbook ~]# facter registry class shipname class => Andromeda registry => NCC-68814 shipname => USS Prokofiev
- In the case of executable files, Facter will assume that their output is a list of
key=value
pairs. It will execute all the files in thefacts.d
directory and add their output to the internal fact hash. - In the
users
example, Facter will execute theusers.sh
script, which results in the following output:users=2
- It will then search this output for
users
and return the matching value:[root@cookbook ~]# facter users 2
- If there are multiple matches for the key you specified, Facter determines which fact to return based on a weight property. In my version of facter, the weight of external facts is 10,000 (defined in
facter/util/directory_loader.rb
asEXTERNAL_FACT_WEIGHT
). This high value is to ensure that the facts you define can override the supplied facts. For example:[root@cookbook ~]# facter architecture x86_64 [root@cookbook ~]# echo "architecture=ppc64">>/etc/facter/facts.d/myfacts.txt [root@cookbook ~]# facter architecture ppc64
If you're having trouble getting Facter to recognize your external facts, run Facter in debug mode to see what's happening:
The X
JSON file was parsed but returned an empty data set error, which means Facter didn't find any key=value
pairs in the file or (in the case of an executable fact) in its output.
- The Importing dynamic information recipe in Chapter 3, Writing Better Manifests
- The Configuring Hiera recipe in Chapter 2, Puppet Infrastructure
- The Creating custom facts recipe in this chapter
/etc/facter/facts.d
directory sets their precedence (with the last one encountered having the highest precedence). To create a fact that will be favored over another, you'll need to have it created in a file that comes last alphabetically:
If you're having trouble getting Facter to recognize your external facts, run Facter in debug mode to see what's happening:
The X
JSON file was parsed but returned an empty data set error, which means Facter didn't find any key=value
pairs in the file or (in the case of an executable fact) in its output.
- The Importing dynamic information recipe in Chapter 3, Writing Better Manifests
- The Configuring Hiera recipe in Chapter 2, Puppet Infrastructure
- The Creating custom facts recipe in this chapter
The X
JSON file was parsed but returned an empty data set error, which means Facter didn't find any key=value
pairs in the file or (in the case of an executable fact) in its output.
- The Importing dynamic information recipe in Chapter 3, Writing Better Manifests
- The Configuring Hiera recipe in Chapter 2, Puppet Infrastructure
- The Creating custom facts recipe in this chapter
- The Importing dynamic information recipe in Chapter 3, Writing Better Manifests
- The Configuring Hiera recipe in Chapter 2, Puppet Infrastructure
- The Creating custom facts recipe in this chapter
Another handy way to get information into Puppet and Facter is to pass it using environment variables. Any environment variable whose name starts with FACTER_
will be interpreted as a fact. For example, ask facter the value of hello using the following command:
Now override the value with an environment variable and ask again:
It works just as well with Puppet, so let's run through an example.
In this example we'll set a fact using an environment variable:
- Keep the node definition for cookbook the same as our last example:
node cookbook { notify {"$::hello": } }
- Run the following command:
[root@cookbook ~]# FACTER_hello="Hallo Welt" puppet agent -t Info: Caching catalog for cookbook.example.com Info: Applying configuration version '1416212026' Notice: Hallo Welt Notice: /Stage[main]/Main/Node[cookbook]/Notify[Hallo Welt]/message: defined 'message' as 'Hallo Welt' Notice: Finished catalog run in 0.27 seconds
set a fact using an environment variable:
- Keep the node definition for cookbook the same as our last example:
node cookbook { notify {"$::hello": } }
- Run the following command:
[root@cookbook ~]# FACTER_hello="Hallo Welt" puppet agent -t Info: Caching catalog for cookbook.example.com Info: Applying configuration version '1416212026' Notice: Hallo Welt Notice: /Stage[main]/Main/Node[cookbook]/Notify[Hallo Welt]/message: defined 'message' as 'Hallo Welt' Notice: Finished catalog run in 0.27 seconds
If you have a server that is already configured as it needs to be, or nearly so, you can capture that configuration as a Puppet manifest. The Puppet resource command generates Puppet manifests from the existing configuration of a system. For example, you can have puppet resource
generate a manifest that creates all the users found on the system. This is very useful to take a snapshot of a working system and get its configuration quickly into Puppet.
Here are some examples of using puppet resource
to get data from a running system:
- To generate the manifest for a particular user, run the following command:
[root@cookbook ~]# puppet resource user thomas user { 'thomas': ensure => 'present', comment => 'thomas Admin User', gid => '1001', groups => ['bin', 'wheel'], home => '/home/thomas', password => '!!', password_max_age => '99999', password_min_age => '0', shell => '/bin/bash', uid => '1001', }
- For a particular service, run the following command:
[root@cookbook ~]# puppet resource service sshd service { 'sshd': ensure => 'running', enable => 'true', }
- For a package, run the following command:
[root@cookbook ~]# puppet resource package kernel package { 'kernel': ensure => '2.6.32-431.23.3.el6', }
You can use puppet resource
to examine each of the resource types available in Puppet. In the preceding examples, we generated a manifest for a specific instance of the resource type, but you can also use puppet resource
to dump all instances of the resource:
This will output the state of each service on the system; this is because each service is an enumerable resource. When you try the same command with a resource that is not enumerable, you get an error message:
Asking Puppet to describe each file on the system will not work; that's something best left to an audit tool such as tripwire
(a system designed to look for changes on every file on the system, http://www.tripwire.com).
puppet resource
to get data from a running system:
[root@cookbook ~]# puppet resource user thomas user { 'thomas': ensure => 'present', comment => 'thomas Admin User', gid => '1001', groups => ['bin', 'wheel'], home => '/home/thomas', password => '!!', password_max_age => '99999', password_min_age => '0', shell => '/bin/bash', uid => '1001', }
You can use puppet resource
to examine each of the resource types available in Puppet. In the preceding examples, we generated a manifest for a specific instance of the resource type, but you can also use puppet resource
to dump all instances of the resource:
This will output the state of each service on the system; this is because each service is an enumerable resource. When you try the same command with a resource that is not enumerable, you get an error message:
Asking Puppet to describe each file on the system will not work; that's something best left to an audit tool such as tripwire
(a system designed to look for changes on every file on the system, http://www.tripwire.com).
puppet resource
to examine each of the
This will output the state of each service on the system; this is because each service is an enumerable resource. When you try the same command with a resource that is not enumerable, you get an error message:
Asking Puppet to describe each file on the system will not work; that's something best left to an audit tool such as tripwire
(a system designed to look for changes on every file on the system, http://www.tripwire.com).
If you want to quickly capture the complete configuration of a running system as a Puppet manifest, there are a couple of tools available to help. In this example, we'll look at Blueprint, which is designed to examine a machine and dump its state as Puppet code.
These steps will show you how to run Blueprint:
- Run the following commands:
[root@cookbook ~]# mkdir blueprint && cd blueprint [root@cookbook blueprint]# blueprint create -P blueprint_test # [blueprint] searching for APT packages to exclude # [blueprint] searching for Yum packages to exclude # [blueprint] caching excluded Yum packages # [blueprint] parsing blueprintignore(5) rules # [blueprint] searching for npm packages # [blueprint] searching for configuration files # [blueprint] searching for APT packages # [blueprint] searching for PEAR/PECL packages # [blueprint] searching for Python packages # [blueprint] searching for Ruby gems # [blueprint] searching for software built from source # [blueprint] searching for Yum packages # [blueprint] searching for service dependencies blueprint_test/manifests/init.pp
- Read the
blueprint_test/manifests/init.pp
file to see the generated code:# # Automatically generated by blueprint(7). Edit at your own risk. # class blueprint_test { Exec { path => '/usr/lib64/qt-3.3/bin:/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/root/bin', } Class['sources'] -> Class['files'] -> Class['packages'] class files { file { '/etc': ensure => directory; '/etc/aliases.db': content => template('blueprint_test/etc/aliases.db'), ensure => file, group => root, mode => 0644, owner => root; '/etc/audit': ensure => directory; '/etc/audit/audit.rules': content => template('blueprint_test/etc/audit/audit.rules'), ensure => file, group => root, mode => 0640, owner => root; '/etc/blkid': ensure => directory; '/etc/cron.hourly': ensure => directory; '/etc/cron.hourly/run-backup': content => template('blueprint_test/etc/cron.hourly/run-backup'), ensure => file, group => root, mode => 0755, owner => root; '/etc/crypttab': content => template('blueprint_test/etc/crypttab'), ensure => file, group => root, mode => 0644, owner => root;
Blueprint just takes a snapshot of the system as it stands; it makes no intelligent decisions, and Blueprint captures all the files on the system and all the packages. It will generate a configuration much larger than you may actually require. For instance, when configuring a server, you may specify that you want the Apache package installed. The dependencies for the Apache package will be installed automatically and you need to specify them. When generating the configuration with a tool such as Blueprint, you will capture all those dependencies and lock the versions that are installed on your system currently. Looking at our generated Blueprint code, we can see that this is the case:
puppet resource
here to change the state of the python-pip
package:
These steps will show you how to run Blueprint:
- Run the following commands:
[root@cookbook ~]# mkdir blueprint && cd blueprint [root@cookbook blueprint]# blueprint create -P blueprint_test # [blueprint] searching for APT packages to exclude # [blueprint] searching for Yum packages to exclude # [blueprint] caching excluded Yum packages # [blueprint] parsing blueprintignore(5) rules # [blueprint] searching for npm packages # [blueprint] searching for configuration files # [blueprint] searching for APT packages # [blueprint] searching for PEAR/PECL packages # [blueprint] searching for Python packages # [blueprint] searching for Ruby gems # [blueprint] searching for software built from source # [blueprint] searching for Yum packages # [blueprint] searching for service dependencies blueprint_test/manifests/init.pp
- Read the
blueprint_test/manifests/init.pp
file to see the generated code:# # Automatically generated by blueprint(7). Edit at your own risk. # class blueprint_test { Exec { path => '/usr/lib64/qt-3.3/bin:/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/root/bin', } Class['sources'] -> Class['files'] -> Class['packages'] class files { file { '/etc': ensure => directory; '/etc/aliases.db': content => template('blueprint_test/etc/aliases.db'), ensure => file, group => root, mode => 0644, owner => root; '/etc/audit': ensure => directory; '/etc/audit/audit.rules': content => template('blueprint_test/etc/audit/audit.rules'), ensure => file, group => root, mode => 0640, owner => root; '/etc/blkid': ensure => directory; '/etc/cron.hourly': ensure => directory; '/etc/cron.hourly/run-backup': content => template('blueprint_test/etc/cron.hourly/run-backup'), ensure => file, group => root, mode => 0755, owner => root; '/etc/crypttab': content => template('blueprint_test/etc/crypttab'), ensure => file, group => root, mode => 0644, owner => root;
Blueprint just takes a snapshot of the system as it stands; it makes no intelligent decisions, and Blueprint captures all the files on the system and all the packages. It will generate a configuration much larger than you may actually require. For instance, when configuring a server, you may specify that you want the Apache package installed. The dependencies for the Apache package will be installed automatically and you need to specify them. When generating the configuration with a tool such as Blueprint, you will capture all those dependencies and lock the versions that are installed on your system currently. Looking at our generated Blueprint code, we can see that this is the case:
show you how to run Blueprint:
- Run the following commands:
[root@cookbook ~]# mkdir blueprint && cd blueprint [root@cookbook blueprint]# blueprint create -P blueprint_test # [blueprint] searching for APT packages to exclude # [blueprint] searching for Yum packages to exclude # [blueprint] caching excluded Yum packages # [blueprint] parsing blueprintignore(5) rules # [blueprint] searching for npm packages # [blueprint] searching for configuration files # [blueprint] searching for APT packages # [blueprint] searching for PEAR/PECL packages # [blueprint] searching for Python packages # [blueprint] searching for Ruby gems # [blueprint] searching for software built from source # [blueprint] searching for Yum packages # [blueprint] searching for service dependencies blueprint_test/manifests/init.pp
- Read the
blueprint_test/manifests/init.pp
file to see the generated code:# # Automatically generated by blueprint(7). Edit at your own risk. # class blueprint_test { Exec { path => '/usr/lib64/qt-3.3/bin:/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/root/bin', } Class['sources'] -> Class['files'] -> Class['packages'] class files { file { '/etc': ensure => directory; '/etc/aliases.db': content => template('blueprint_test/etc/aliases.db'), ensure => file, group => root, mode => 0644, owner => root; '/etc/audit': ensure => directory; '/etc/audit/audit.rules': content => template('blueprint_test/etc/audit/audit.rules'), ensure => file, group => root, mode => 0640, owner => root; '/etc/blkid': ensure => directory; '/etc/cron.hourly': ensure => directory; '/etc/cron.hourly/run-backup': content => template('blueprint_test/etc/cron.hourly/run-backup'), ensure => file, group => root, mode => 0755, owner => root; '/etc/crypttab': content => template('blueprint_test/etc/crypttab'), ensure => file, group => root, mode => 0644, owner => root;
Blueprint just takes a snapshot of the system as it stands; it makes no intelligent decisions, and Blueprint captures all the files on the system and all the packages. It will generate a configuration much larger than you may actually require. For instance, when configuring a server, you may specify that you want the Apache package installed. The dependencies for the Apache package will be installed automatically and you need to specify them. When generating the configuration with a tool such as Blueprint, you will capture all those dependencies and lock the versions that are installed on your system currently. Looking at our generated Blueprint code, we can see that this is the case:
When Puppet runs on a node, it needs to know which classes should be applied to that node. For example, if it is a web server node, it might need to include an apache
class. The normal way to map nodes to classes is in the Puppet manifest itself, for example, in your site.pp
file:
An ENC could be a simple shell script, for example, or a wrapper around a more complicated program or API that can decide how to map nodes to classes. The ENC provided by Puppet enterprise and The Foreman (http://theforeman.org/) are both simple scripts, which connect to the web API of their respective systems.
In this example, we'll build the most simple of ENCs, a shell script that simply prints a list of classes to include. We'll start by including an enc
class, which defines notify
that will print a top-scope variable $enc
.
- Create the file
/etc/puppet/cookbook.sh
with the following contents:#!/bin/bash cat <<EOF --- classes: enc: parameters: enc: $0 EOF
- Run the following command:
root@puppet:/etc/puppet# chmod a+x cookbook.sh
- Modify your
/etc/puppet/puppet.conf
file as follows:[main] node_terminus = exec external_nodes = /etc/puppet/cookbook.sh
- Restart Apache (restart the master) to make the change effective.
- Ensure your
site.pp
file has the following empty definition for the default node:node default {}
- Run Puppet:
[root@cookbook ~]# puppet agent -t Info: Caching catalog for cookbook.example.com Info: Applying configuration version '1416376937' Notice: We defined this from /etc/puppet/cookbook.sh Notice: /Stage[main]/Enc/Notify[We defined this from /etc/puppet/cookbook.sh]/message: defined 'message' as 'We defined this from /etc/puppet/cookbook.sh' Notice: Finished catalog run in 0.17 seconds
Obviously this script is not terribly useful; a more sophisticated script might check a database to find the class list, or look up the node in a hash, or an external text file or database (often an organization's configuration management database, CMDB). Hopefully, this example is enough to get you started with writing your own external node classifier. Remember that you can write your script in any language you prefer.
enc
class to include with the enc
script:
t@mylaptop ~/puppet $ mkdir -p modules/enc/manifests
modules/enc/manifests/init.pp
with the following contents:class enc { notify {"We defined this from $enc": } }
- Create the file
/etc/puppet/cookbook.sh
with the following contents:#!/bin/bash cat <<EOF --- classes: enc: parameters: enc: $0 EOF
- Run the following command:
root@puppet:/etc/puppet# chmod a+x cookbook.sh
- Modify your
/etc/puppet/puppet.conf
file as follows:[main] node_terminus = exec external_nodes = /etc/puppet/cookbook.sh
- Restart Apache (restart the master) to make the change effective.
- Ensure your
site.pp
file has the following empty definition for the default node:node default {}
- Run Puppet:
[root@cookbook ~]# puppet agent -t Info: Caching catalog for cookbook.example.com Info: Applying configuration version '1416376937' Notice: We defined this from /etc/puppet/cookbook.sh Notice: /Stage[main]/Enc/Notify[We defined this from /etc/puppet/cookbook.sh]/message: defined 'message' as 'We defined this from /etc/puppet/cookbook.sh' Notice: Finished catalog run in 0.17 seconds
Obviously this script is not terribly useful; a more sophisticated script might check a database to find the class list, or look up the node in a hash, or an external text file or database (often an organization's configuration management database, CMDB). Hopefully, this example is enough to get you started with writing your own external node classifier. Remember that you can write your script in any language you prefer.
/etc/puppet/cookbook.sh
with the following contents:#!/bin/bash cat <<EOF --- classes: enc: parameters: enc: $0 EOF
- following command:
root@puppet:/etc/puppet# chmod a+x cookbook.sh
- Modify your
/etc/puppet/puppet.conf
file as follows:[main] node_terminus = exec external_nodes = /etc/puppet/cookbook.sh
- Restart Apache (restart the master) to make the change effective.
- Ensure your
site.pp
file has the following empty definition for the default node:node default {}
- Run Puppet:
[root@cookbook ~]# puppet agent -t Info: Caching catalog for cookbook.example.com Info: Applying configuration version '1416376937' Notice: We defined this from /etc/puppet/cookbook.sh Notice: /Stage[main]/Enc/Notify[We defined this from /etc/puppet/cookbook.sh]/message: defined 'message' as 'We defined this from /etc/puppet/cookbook.sh' Notice: Finished catalog run in 0.17 seconds
Obviously this script is not terribly useful; a more sophisticated script might check a database to find the class list, or look up the node in a hash, or an external text file or database (often an organization's configuration management database, CMDB). Hopefully, this example is enough to get you started with writing your own external node classifier. Remember that you can write your script in any language you prefer.
puppet.conf
, Puppet will call the specified program with the node's fqdn (technically, the certname variable) as the first command-line argument. In our example script, this argument is ignored, and it just outputs a fixed list of classes (actually, just one class).
As you know, Puppet has a bunch of useful built-in resource types: packages, files, users, and so on. Usually, you can do everything you need to do by using either combinations of these built-in resources, or define
, which you can use more or less in the same way as a resource (see Chapter 3, Writing Better Manifests for information on definitions).
In the early days of Puppet, creating your own resource type was more common as the list of core resources was shorter than it is today. Before you consider creating your own resource type, I suggest searching the Forge for alternative solutions. Even if you can find a project that only partially solves your problem, you will be better served by extending and helping out that project, rather than trying to create your own. However, if you need to create your own resource type, Puppet makes it quite easy. The native types are written in Ruby, and you will need a basic familiarity with Ruby in order to create your own.
Let's refresh our memory on the distinction between types and providers. A type describes a resource and the parameters it can have (for example, the package
type). A provider tells Puppet how to implement a resource type for a particular platform or situation (for example, the apt/dpkg
providers implement the package
type for Debian-like systems).
A single type (package
) can have many providers (APT, YUM, Fink, and so on). If you don't specify a provider when declaring a resource, Puppet will choose the most appropriate one given the environment.
We'll use Ruby in this section; if you are not familiar with Ruby try visiting http://www.ruby-doc.org/docs/Tutorial/ or http://www.codecademy.com/tracks/ruby/.
Custom types can live in any module, in a lib/puppet/type
subdirectory and in a file named for the type (in our example, that's modules/cookbook/lib/puppet/type/gitrepo.rb
).
The first line of gitrepo.rb
tells Puppet to register a new type named gitrepo
:
Finally, we tell Puppet that the type accepts the path
parameter:
When deciding whether or not you should create a custom type, you should ask a few questions about the resource you are trying to describe such as:
- Is the resource enumerable? Can you easily obtain a list of all the instances of the resource on the system?
- Is the resource atomic? Can you ensure that only one copy of the resource exists on the system (this is particularly important when you want to use
ensure=>absent
on the resource)? - Is there any other resource that describes this resource? In such a case, a defined type based on the existing resource would, in most cases, be a simpler solution.
Our example is deliberately simple, but when you move on to developing real custom types for your production environment, you should add documentation strings to describe what the type and its parameters do, for example:
modules/cookbook/lib/puppet/type/gitrepo.rb
with the following contents:
Custom types can live in any module, in a lib/puppet/type
subdirectory and in a file named for the type (in our example, that's modules/cookbook/lib/puppet/type/gitrepo.rb
).
The first line of gitrepo.rb
tells Puppet to register a new type named gitrepo
:
Finally, we tell Puppet that the type accepts the path
parameter:
When deciding whether or not you should create a custom type, you should ask a few questions about the resource you are trying to describe such as:
- Is the resource enumerable? Can you easily obtain a list of all the instances of the resource on the system?
- Is the resource atomic? Can you ensure that only one copy of the resource exists on the system (this is particularly important when you want to use
ensure=>absent
on the resource)? - Is there any other resource that describes this resource? In such a case, a defined type based on the existing resource would, in most cases, be a simpler solution.
Our example is deliberately simple, but when you move on to developing real custom types for your production environment, you should add documentation strings to describe what the type and its parameters do, for example:
The first line of gitrepo.rb
tells Puppet to register a new type named gitrepo
:
Finally, we tell Puppet that the type accepts the path
parameter:
When deciding whether or not you should create a custom type, you should ask a few questions about the resource you are trying to describe such as:
- Is the resource enumerable? Can you easily obtain a list of all the instances of the resource on the system?
- Is the resource atomic? Can you ensure that only one copy of the resource exists on the system (this is particularly important when you want to use
ensure=>absent
on the resource)? - Is there any other resource that describes this resource? In such a case, a defined type based on the existing resource would, in most cases, be a simpler solution.
Our example is deliberately simple, but when you move on to developing real custom types for your production environment, you should add documentation strings to describe what the type and its parameters do, for example:
resource you are trying to describe such as:
- Is the resource enumerable? Can you easily obtain a list of all the instances of the resource on the system?
- Is the resource atomic? Can you ensure that only one copy of the resource exists on the system (this is particularly important when you want to use
ensure=>absent
on the resource)? - Is there any other resource that describes this resource? In such a case, a defined type based on the existing resource would, in most cases, be a simpler solution.
Our example is deliberately simple, but when you move on to developing real custom types for your production environment, you should add documentation strings to describe what the type and its parameters do, for example:
In the previous section, we created a new custom type called gitrepo
and told Puppet that it takes two parameters, source
and path
. However, so far, we haven't told Puppet how to actually check out the repo; in other words, how to create a specific instance of this type. That's where the provider comes in.
We'll add the git
provider, and create an instance of a gitrepo
resource to check that it all works. You'll need Git installed for this to work, but if you're using the Git-based manifest management setup described in Chapter 2, Puppet Infrastructure, we can safely assume that Git is available.
- Create the file
modules/cookbook/lib/puppet/provider/gitrepo/git.rb
with the following contents:require 'fileutils' Puppet::Type.type(:gitrepo).provide(:git) do commands :git => "git" def create git "clone", resource[:source], resource[:path] end def exists? File.directory? resource[:path] end end
- Modify your
site.pp
file as follows:node 'cookbook' { gitrepo { 'https://github.com/puppetlabs/puppetlabs-git': ensure => present, path => '/tmp/puppet', } }
- Run Puppet:
[root@cookbook ~]# puppet agent -t Notice: /File[/var/lib/puppet/lib/puppet/type/gitrepo.rb]/ensure: defined content as '{md5}6471793fe2b4372d40289ad4b614fe0b' Notice: /File[/var/lib/puppet/lib/puppet/provider/gitrepo]/ensure: created Notice: /File[/var/lib/puppet/lib/puppet/provider/gitrepo/git.rb]/ensure: defined content as '{md5}f860388234d3d0bdb3b3ec98bbf5115b' Info: Caching catalog for cookbook.example.com Info: Applying configuration version '1416378876' Notice: /Stage[main]/Main/Node[cookbook]/Gitrepo[https://github.com/puppetlabs/puppetlabs-git]/ensure: created Notice: Finished catalog run in 2.59 seconds
Custom providers can live in any module, in a lib/puppet/provider/TYPE_NAME
subdirectory in a file named after the provider. (The provider is the actual program that is run on the system; in our example, the program is Git and the provider is in modules/cookbook/lib/puppet/provider/gitrepo/git.rb
. Note that the name of the module is irrelevant.)
The method has access to the instance's parameters, which we need to know where to check out the repo from, and which directory to create it in. We get this by looking at resource[:source]
and resource[:path]
.
Our example was very simple, and there is much more to learn about writing your own types. If you're going to distribute your code for others to use, or even if you aren't, it's a good idea to include tests with it. puppetlabs has a useful page on the interface between custom types and providers:
http://docs.puppetlabs.com/guides/custom_types.html
on implementing providers:
http://docs.puppetlabs.com/guides/provider_development.html
and a complete worked example of developing a custom type and provider, a little more advanced than that presented in this book:
http://docs.puppetlabs.com/guides/complete_resource_example.html
git
provider, and create an instance of a gitrepo
resource to check that it all works. You'll need Git installed for this to work, but if you're using the Git-based manifest management setup described in
Chapter 2, Puppet Infrastructure, we can safely assume that Git is available.
- Create the file
modules/cookbook/lib/puppet/provider/gitrepo/git.rb
with the following contents:require 'fileutils' Puppet::Type.type(:gitrepo).provide(:git) do commands :git => "git" def create git "clone", resource[:source], resource[:path] end def exists? File.directory? resource[:path] end end
- Modify your
site.pp
file as follows:node 'cookbook' { gitrepo { 'https://github.com/puppetlabs/puppetlabs-git': ensure => present, path => '/tmp/puppet', } }
- Run Puppet:
[root@cookbook ~]# puppet agent -t Notice: /File[/var/lib/puppet/lib/puppet/type/gitrepo.rb]/ensure: defined content as '{md5}6471793fe2b4372d40289ad4b614fe0b' Notice: /File[/var/lib/puppet/lib/puppet/provider/gitrepo]/ensure: created Notice: /File[/var/lib/puppet/lib/puppet/provider/gitrepo/git.rb]/ensure: defined content as '{md5}f860388234d3d0bdb3b3ec98bbf5115b' Info: Caching catalog for cookbook.example.com Info: Applying configuration version '1416378876' Notice: /Stage[main]/Main/Node[cookbook]/Gitrepo[https://github.com/puppetlabs/puppetlabs-git]/ensure: created Notice: Finished catalog run in 2.59 seconds
Custom providers can live in any module, in a lib/puppet/provider/TYPE_NAME
subdirectory in a file named after the provider. (The provider is the actual program that is run on the system; in our example, the program is Git and the provider is in modules/cookbook/lib/puppet/provider/gitrepo/git.rb
. Note that the name of the module is irrelevant.)
The method has access to the instance's parameters, which we need to know where to check out the repo from, and which directory to create it in. We get this by looking at resource[:source]
and resource[:path]
.
Our example was very simple, and there is much more to learn about writing your own types. If you're going to distribute your code for others to use, or even if you aren't, it's a good idea to include tests with it. puppetlabs has a useful page on the interface between custom types and providers:
http://docs.puppetlabs.com/guides/custom_types.html
on implementing providers:
http://docs.puppetlabs.com/guides/provider_development.html
and a complete worked example of developing a custom type and provider, a little more advanced than that presented in this book:
http://docs.puppetlabs.com/guides/complete_resource_example.html
The method has access to the instance's parameters, which we need to know where to check out the repo from, and which directory to create it in. We get this by looking at resource[:source]
and resource[:path]
.
Our example was very simple, and there is much more to learn about writing your own types. If you're going to distribute your code for others to use, or even if you aren't, it's a good idea to include tests with it. puppetlabs has a useful page on the interface between custom types and providers:
http://docs.puppetlabs.com/guides/custom_types.html
on implementing providers:
http://docs.puppetlabs.com/guides/provider_development.html
and a complete worked example of developing a custom type and provider, a little more advanced than that presented in this book:
http://docs.puppetlabs.com/guides/complete_resource_example.html
define
statements and exec
resources, you may want to consider replacing these with a custom type. However, as stated previously, it's worth looking around to see if someone else has already done this before implementing your own.
http://docs.puppetlabs.com/guides/custom_types.html
on implementing providers:
http://docs.puppetlabs.com/guides/provider_development.html
and a complete worked example of developing a custom type and provider, a little more advanced than that presented in this book:
http://docs.puppetlabs.com/guides/complete_resource_example.html
If you've read the recipe Using GnuPG to encrypt secrets in Chapter 4, Working with Files and Packages, then you've already seen an example of a custom function (in that example, we created a secret
function, which shelled out to GnuPG). Let's look at custom
functions in a little more detail now and build an example.
If you've read the recipe Distributing cron jobs efficiently in Chapter 6, Managing Resources and Files, you might remember that we used the inline_template
function to set a random time for cron jobs to run, based on the hostname of the node. In this example, we'll take that idea and turn it into a custom function called random_minute
:
- Create the file
modules/cookbook/lib/puppet/parser/functions/random_minute.rb
with the following contents:module Puppet::Parser::Functions newfunction(:random_minute, :type => :rvalue) do |args| lookupvar('hostname').sum % 60 end end
- Modify your
site.pp
file as follows:node 'cookbook' { cron { 'randomised cron job': command => '/bin/echo Hello, world >>/tmp/hello.txt', hour => '*', minute => random_minute(), } }
- Run Puppet:
[root@cookbook ~]# puppet agent -t Info: Retrieving pluginfacts Info: Retrieving plugin Notice: /File[/var/lib/puppet/lib/puppet/parser/functions/random_minute.rb]/ensure: defined content as '{md5}e6ff40165e74677e5837027bb5610744' Info: Loading facts Info: Caching catalog for cookbook.example.com Info: Applying configuration version '1416379652' Notice: /Stage[main]/Main/Node[cookbook]/Cron[custom fuction example job]/ensure: created Notice: Finished catalog run in 0.41 seconds
- Check
crontab
with the following command:[root@cookbook ~]# crontab -l # HEADER: This file was autogenerated at Wed Nov 19 01:48:11 -0500 2014 by puppet. # HEADER: While it can still be managed manually, it is definitely not recommended. # HEADER: Note particularly that the comments starting with 'Puppet Name' should # HEADER: not be deleted, as doing so could cause duplicate cron jobs. # Puppet Name: run-backup 0 15 * * * /usr/local/bin/backup # Puppet Name: custom fuction example job 15 * * * * /bin/echo Hallo, welt >>/tmp/hallo.txt
Custom functions can live in any module, in the lib/puppet/parser/functions
subdirectory in a file named after the function (in our example, random_minute.rb
).
The function code goes inside a module ... end
block like this:
The :rvalue
bit simply means that this function returns a value.
recipe Distributing cron jobs efficiently in Chapter 6, Managing Resources and Files, you might remember that we used the inline_template
function to set a random time for cron jobs to run, based on the hostname of the node. In this example, we'll take that idea and turn it into a custom function called random_minute
:
- Create the file
modules/cookbook/lib/puppet/parser/functions/random_minute.rb
with the following contents:module Puppet::Parser::Functions newfunction(:random_minute, :type => :rvalue) do |args| lookupvar('hostname').sum % 60 end end
- Modify your
site.pp
file as follows:node 'cookbook' { cron { 'randomised cron job': command => '/bin/echo Hello, world >>/tmp/hello.txt', hour => '*', minute => random_minute(), } }
- Run Puppet:
[root@cookbook ~]# puppet agent -t Info: Retrieving pluginfacts Info: Retrieving plugin Notice: /File[/var/lib/puppet/lib/puppet/parser/functions/random_minute.rb]/ensure: defined content as '{md5}e6ff40165e74677e5837027bb5610744' Info: Loading facts Info: Caching catalog for cookbook.example.com Info: Applying configuration version '1416379652' Notice: /Stage[main]/Main/Node[cookbook]/Cron[custom fuction example job]/ensure: created Notice: Finished catalog run in 0.41 seconds
- Check
crontab
with the following command:[root@cookbook ~]# crontab -l # HEADER: This file was autogenerated at Wed Nov 19 01:48:11 -0500 2014 by puppet. # HEADER: While it can still be managed manually, it is definitely not recommended. # HEADER: Note particularly that the comments starting with 'Puppet Name' should # HEADER: not be deleted, as doing so could cause duplicate cron jobs. # Puppet Name: run-backup 0 15 * * * /usr/local/bin/backup # Puppet Name: custom fuction example job 15 * * * * /bin/echo Hallo, welt >>/tmp/hallo.txt
Custom functions can live in any module, in the lib/puppet/parser/functions
subdirectory in a file named after the function (in our example, random_minute.rb
).
The function code goes inside a module ... end
block like this:
The :rvalue
bit simply means that this function returns a value.
The function code goes inside a module ... end
block like this:
The :rvalue
bit simply means that this function returns a value.
lookupvar
as shown in the example. You can also work on arguments, for example, a general purpose hashing function that takes two arguments: the size of the hash table and optionally the thing to hash. Create modules/cookbook/lib/puppet/parser/functions/hashtable.rb
with the following contents:
It would be great if we could verify that our Puppet manifests satisfy certain expectations without even having to run Puppet. The rspec-puppet
tool is a nifty tool to do this. Based on RSpec, a testing framework for Ruby programs, rspec-puppet
lets you write test cases for your Puppet manifests that are especially useful to catch regressions (bugs introduced when fixing another bug), and refactoring problems (bugs introduced when reorganizing your code).
Let's create an example class, thing
, and write some tests for it.
- Define the
thing
class:class thing { service {'thing': ensure => 'running', enable => true, require => Package['thing'], } package {'thing': ensure => 'installed' } file {'/etc/thing.conf': content => 'fubar\n', mode => 0644, require => Package['thing'], notify => Service['thing'], } }
- Run the following commands:
t@mylaptop ~/puppet]$cd modules/thing t@mylaptop~/puppet/modules/thing $ rspec-puppet-init + spec/ + spec/classes/ + spec/defines/ + spec/functions/ + spec/hosts/ + spec/fixtures/ + spec/fixtures/manifests/ + spec/fixtures/modules/ + spec/fixtures/modules/heartbeat/ + spec/fixtures/manifests/site.pp + spec/fixtures/modules/heartbeat/manifests + spec/fixtures/modules/heartbeat/templates + spec/spec_helper.rb + Rakefile
- Create the file
spec/classes/thing_spec.rb
with the following contents:require 'spec_helper' describe 'thing' do it { should create_class('thing') } it { should contain_package('thing') } it { should contain_service('thing').with( 'ensure' => 'running' ) } it { should contain_file('/etc/things.conf') } end
- Run the following commands:
t@mylaptop ~/.puppet/modules/thing $ rspec ...F Failures: 1) thing should contain File[/etc/things.conf] Failure/Error: it { should contain_file('/etc/things.conf') } expected that the catalogue would contain File[/etc/things.conf] # ./spec/classes/thing_spec.rb:9:in `block (2 levels) in <top (required)>' Finished in 1.66 seconds 4 examples, 1 failure Failed examples: rspec ./spec/classes/thing_spec.rb:9 # thing should contain File[/etc/things.conf]
The rspec-puppet-init
command creates a framework of directories for you to put your specs (test programs) in. At the moment, we're just interested in the spec/classes
directory. This is where you'll put your class specs, one per class, named after the class it tests, for example, thing_spec.rb
.
Then, a describe
block follows:
Next, we test for the existence of the thing
service:
The preceding code actually contains two assertions. First, that the class contains a thing
service:
Second, that the service has an ensure
attribute with the value running
:
You can specify any attributes and values you want using the with
method, as a comma-separated list. For example, the following code asserts several attributes of a file
resource:
As you can see, we defined the file in our test as /etc/things.conf
but the file in the manifests is /etc/thing.conf
, so the test fails. Edit thing_spec.rb
and change /etc/things.conf to /etc/thing.conf
:
You can find more information about rspec-puppet
, including complete documentation for the assertions available and a tutorial, at http://rspec-puppet.com/.
When you want to start testing how your code applies to nodes, you'll need to look at another tool, beaker. Beaker works with various virtualization platforms to create temporary virtual machines to which Puppet code is applied. The results are then used for acceptance testing of the Puppet code. This method of testing and developing at the same time is known as Test-driven development (TDD). More information about beaker is available on the GitHub site at https://github.com/puppetlabs/beaker.
- The Checking your manifests with puppet-lint recipe in Chapter 1, Puppet Language and Style
rspec-puppet
.
Let's create an example class, thing
, and write some tests for it.
- Define the
thing
class:class thing { service {'thing': ensure => 'running', enable => true, require => Package['thing'], } package {'thing': ensure => 'installed' } file {'/etc/thing.conf': content => 'fubar\n', mode => 0644, require => Package['thing'], notify => Service['thing'], } }
- Run the following commands:
t@mylaptop ~/puppet]$cd modules/thing t@mylaptop~/puppet/modules/thing $ rspec-puppet-init + spec/ + spec/classes/ + spec/defines/ + spec/functions/ + spec/hosts/ + spec/fixtures/ + spec/fixtures/manifests/ + spec/fixtures/modules/ + spec/fixtures/modules/heartbeat/ + spec/fixtures/manifests/site.pp + spec/fixtures/modules/heartbeat/manifests + spec/fixtures/modules/heartbeat/templates + spec/spec_helper.rb + Rakefile
- Create the file
spec/classes/thing_spec.rb
with the following contents:require 'spec_helper' describe 'thing' do it { should create_class('thing') } it { should contain_package('thing') } it { should contain_service('thing').with( 'ensure' => 'running' ) } it { should contain_file('/etc/things.conf') } end
- Run the following commands:
t@mylaptop ~/.puppet/modules/thing $ rspec ...F Failures: 1) thing should contain File[/etc/things.conf] Failure/Error: it { should contain_file('/etc/things.conf') } expected that the catalogue would contain File[/etc/things.conf] # ./spec/classes/thing_spec.rb:9:in `block (2 levels) in <top (required)>' Finished in 1.66 seconds 4 examples, 1 failure Failed examples: rspec ./spec/classes/thing_spec.rb:9 # thing should contain File[/etc/things.conf]
The rspec-puppet-init
command creates a framework of directories for you to put your specs (test programs) in. At the moment, we're just interested in the spec/classes
directory. This is where you'll put your class specs, one per class, named after the class it tests, for example, thing_spec.rb
.
Then, a describe
block follows:
Next, we test for the existence of the thing
service:
The preceding code actually contains two assertions. First, that the class contains a thing
service:
Second, that the service has an ensure
attribute with the value running
:
You can specify any attributes and values you want using the with
method, as a comma-separated list. For example, the following code asserts several attributes of a file
resource:
As you can see, we defined the file in our test as /etc/things.conf
but the file in the manifests is /etc/thing.conf
, so the test fails. Edit thing_spec.rb
and change /etc/things.conf to /etc/thing.conf
:
You can find more information about rspec-puppet
, including complete documentation for the assertions available and a tutorial, at http://rspec-puppet.com/.
When you want to start testing how your code applies to nodes, you'll need to look at another tool, beaker. Beaker works with various virtualization platforms to create temporary virtual machines to which Puppet code is applied. The results are then used for acceptance testing of the Puppet code. This method of testing and developing at the same time is known as Test-driven development (TDD). More information about beaker is available on the GitHub site at https://github.com/puppetlabs/beaker.
- The Checking your manifests with puppet-lint recipe in Chapter 1, Puppet Language and Style
thing
, and write some tests for it.
thing
class:class thing { service {'thing': ensure => 'running', enable => true, require => Package['thing'], } package {'thing': ensure => 'installed' } file {'/etc/thing.conf': content => 'fubar\n', mode => 0644, require => Package['thing'], notify => Service['thing'], } }
- following commands:
t@mylaptop ~/puppet]$cd modules/thing t@mylaptop~/puppet/modules/thing $ rspec-puppet-init + spec/ + spec/classes/ + spec/defines/ + spec/functions/ + spec/hosts/ + spec/fixtures/ + spec/fixtures/manifests/ + spec/fixtures/modules/ + spec/fixtures/modules/heartbeat/ + spec/fixtures/manifests/site.pp + spec/fixtures/modules/heartbeat/manifests + spec/fixtures/modules/heartbeat/templates + spec/spec_helper.rb + Rakefile
- Create the file
spec/classes/thing_spec.rb
with the following contents:require 'spec_helper' describe 'thing' do it { should create_class('thing') } it { should contain_package('thing') } it { should contain_service('thing').with( 'ensure' => 'running' ) } it { should contain_file('/etc/things.conf') } end
- Run the following commands:
t@mylaptop ~/.puppet/modules/thing $ rspec ...F Failures: 1) thing should contain File[/etc/things.conf] Failure/Error: it { should contain_file('/etc/things.conf') } expected that the catalogue would contain File[/etc/things.conf] # ./spec/classes/thing_spec.rb:9:in `block (2 levels) in <top (required)>' Finished in 1.66 seconds 4 examples, 1 failure Failed examples: rspec ./spec/classes/thing_spec.rb:9 # thing should contain File[/etc/things.conf]
The rspec-puppet-init
command creates a framework of directories for you to put your specs (test programs) in. At the moment, we're just interested in the spec/classes
directory. This is where you'll put your class specs, one per class, named after the class it tests, for example, thing_spec.rb
.
Then, a describe
block follows:
Next, we test for the existence of the thing
service:
The preceding code actually contains two assertions. First, that the class contains a thing
service:
Second, that the service has an ensure
attribute with the value running
:
You can specify any attributes and values you want using the with
method, as a comma-separated list. For example, the following code asserts several attributes of a file
resource:
As you can see, we defined the file in our test as /etc/things.conf
but the file in the manifests is /etc/thing.conf
, so the test fails. Edit thing_spec.rb
and change /etc/things.conf to /etc/thing.conf
:
You can find more information about rspec-puppet
, including complete documentation for the assertions available and a tutorial, at http://rspec-puppet.com/.
When you want to start testing how your code applies to nodes, you'll need to look at another tool, beaker. Beaker works with various virtualization platforms to create temporary virtual machines to which Puppet code is applied. The results are then used for acceptance testing of the Puppet code. This method of testing and developing at the same time is known as Test-driven development (TDD). More information about beaker is available on the GitHub site at https://github.com/puppetlabs/beaker.
- The Checking your manifests with puppet-lint recipe in Chapter 1, Puppet Language and Style
rspec-puppet-init
command creates
a framework of directories for you to put your specs (test programs) in. At the moment, we're just interested in the spec/classes
directory. This is where you'll put your class specs, one per class, named after the class it tests, for example, thing_spec.rb
.
Then, a describe
block follows:
Next, we test for the existence of the thing
service:
The preceding code actually contains two assertions. First, that the class contains a thing
service:
Second, that the service has an ensure
attribute with the value running
:
You can specify any attributes and values you want using the with
method, as a comma-separated list. For example, the following code asserts several attributes of a file
resource:
As you can see, we defined the file in our test as /etc/things.conf
but the file in the manifests is /etc/thing.conf
, so the test fails. Edit thing_spec.rb
and change /etc/things.conf to /etc/thing.conf
:
You can find more information about rspec-puppet
, including complete documentation for the assertions available and a tutorial, at http://rspec-puppet.com/.
When you want to start testing how your code applies to nodes, you'll need to look at another tool, beaker. Beaker works with various virtualization platforms to create temporary virtual machines to which Puppet code is applied. The results are then used for acceptance testing of the Puppet code. This method of testing and developing at the same time is known as Test-driven development (TDD). More information about beaker is available on the GitHub site at https://github.com/puppetlabs/beaker.
- The Checking your manifests with puppet-lint recipe in Chapter 1, Puppet Language and Style
contain_<resource type>
(title). In addition to verifying your classes will apply correctly, you can also test functions and definitions by using the appropriate subdirectories within the spec directory (classes, defines, or functions).
rspec-puppet
, including complete documentation for the assertions available
and a tutorial, at http://rspec-puppet.com/.
When you want to start testing how your code applies to nodes, you'll need to look at another tool, beaker. Beaker works with various virtualization platforms to create temporary virtual machines to which Puppet code is applied. The results are then used for acceptance testing of the Puppet code. This method of testing and developing at the same time is known as Test-driven development (TDD). More information about beaker is available on the GitHub site at https://github.com/puppetlabs/beaker.
- The Checking your manifests with puppet-lint recipe in Chapter 1, Puppet Language and Style
- Chapter 1, Puppet Language and Style
When you begin to include modules from the forge in your Puppet infrastructure, keeping track of which versions you installed and ensuring consistency between all your testing areas can become a bit of a problem. Luckily, the tools we will discuss in the next two sections can bring order to your system. We will first begin with librarian-puppet, which uses a special configuration file named Puppetfile to specify the source location of your various modules.
We'll install librarian-puppet to work through the example.
Install librarian-puppet
on your Puppet master, using Puppet of course:
We'll use librarian-puppet to download and install a module in this example:
- Create a working directory for yourself; librarian-puppet will overwrite your modules directory by default, so we'll work in a temporary location for now:
root@puppet:~# mkdir librarian root@puppet:~# cd librarian
- Create a new Puppetfile with the following contents:
#!/usr/bin/env ruby #^syntax detection forge "https://forgeapi.puppetlabs.com" # A module from the Puppet Forge mod 'puppetlabs-stdlib'
- Now, run librarian-puppet to download and install the
puppetlabs-stdlib
module in themodules
directory:root@puppet:~/librarian# librarian-puppet install root@puppet:~/librarian # ls modules Puppetfile Puppetfile.lock root@puppet:~/librarian # ls modules stdlib
The first line of the Puppetfile
makes the Puppetfile
appear to be a Ruby source file. These are completely optional but coerces editors into treating the file as though it was written in Ruby (which it is):
Now, we added a line to include the puppetlabs-stdlib
module:
The Puppetfile
allows you to pull in modules from sources other than the forge. You may use a local Git url or even a GitHub url to download modules that are not on the Forge. More information on librarian-puppet can be found on the GitHub website at https://github.com/rodjek/librarian-puppet.
Note that librarian-puppet will create the modules directory and remove any modules you placed in there by default. Most installations using librarian-puppet opt to place their local modules in a /local
subdirectory (/dist
or /companyname
are also used).
In the next section, we'll talk about r10k, which goes one step further than librarian and manages your entire environment directory.
librarian-puppet
on your Puppet master, using Puppet of course:
We'll use librarian-puppet to download and install a module in this example:
- Create a working directory for yourself; librarian-puppet will overwrite your modules directory by default, so we'll work in a temporary location for now:
root@puppet:~# mkdir librarian root@puppet:~# cd librarian
- Create a new Puppetfile with the following contents:
#!/usr/bin/env ruby #^syntax detection forge "https://forgeapi.puppetlabs.com" # A module from the Puppet Forge mod 'puppetlabs-stdlib'
- Now, run librarian-puppet to download and install the
puppetlabs-stdlib
module in themodules
directory:root@puppet:~/librarian# librarian-puppet install root@puppet:~/librarian # ls modules Puppetfile Puppetfile.lock root@puppet:~/librarian # ls modules stdlib
The first line of the Puppetfile
makes the Puppetfile
appear to be a Ruby source file. These are completely optional but coerces editors into treating the file as though it was written in Ruby (which it is):
Now, we added a line to include the puppetlabs-stdlib
module:
The Puppetfile
allows you to pull in modules from sources other than the forge. You may use a local Git url or even a GitHub url to download modules that are not on the Forge. More information on librarian-puppet can be found on the GitHub website at https://github.com/rodjek/librarian-puppet.
Note that librarian-puppet will create the modules directory and remove any modules you placed in there by default. Most installations using librarian-puppet opt to place their local modules in a /local
subdirectory (/dist
or /companyname
are also used).
In the next section, we'll talk about r10k, which goes one step further than librarian and manages your entire environment directory.
root@puppet:~# mkdir librarian root@puppet:~# cd librarian
#!/usr/bin/env ruby #^syntax detection forge "https://forgeapi.puppetlabs.com" # A module from the Puppet Forge mod 'puppetlabs-stdlib'
The first line of the Puppetfile
makes the Puppetfile
appear to be a Ruby source file. These are completely optional but coerces editors into treating the file as though it was written in Ruby (which it is):
Now, we added a line to include the puppetlabs-stdlib
module:
The Puppetfile
allows you to pull in modules from sources other than the forge. You may use a local Git url or even a GitHub url to download modules that are not on the Forge. More information on librarian-puppet can be found on the GitHub website at https://github.com/rodjek/librarian-puppet.
Note that librarian-puppet will create the modules directory and remove any modules you placed in there by default. Most installations using librarian-puppet opt to place their local modules in a /local
subdirectory (/dist
or /companyname
are also used).
In the next section, we'll talk about r10k, which goes one step further than librarian and manages your entire environment directory.
The Puppetfile
allows you to pull in modules from sources other than the forge. You may use a local Git url or even a GitHub url to download modules that are not on the Forge. More information on librarian-puppet can be found on the GitHub website at https://github.com/rodjek/librarian-puppet.
Note that librarian-puppet will create the modules directory and remove any modules you placed in there by default. Most installations using librarian-puppet opt to place their local modules in a /local
subdirectory (/dist
or /companyname
are also used).
In the next section, we'll talk about r10k, which goes one step further than librarian and manages your entire environment directory.
Puppetfile
allows
you to pull in modules from sources other than the forge. You may use a local Git url or even a GitHub url to download modules that are not on the Forge. More information on librarian-puppet can be found on the GitHub website at https://github.com/rodjek/librarian-puppet.
Note that librarian-puppet will create the modules directory and remove any modules you placed in there by default. Most installations using librarian-puppet opt to place their local modules in a /local
subdirectory (/dist
or /companyname
are also used).
In the next section, we'll talk about r10k, which goes one step further than librarian and manages your entire environment directory.
The Puppetfile
is a very good format to describe which modules you wish to include in your environment. Building upon the Puppetfile
is another tool, r10k. r10k is a total environment management tool. You can use r10k to clone a local Git repository into your environmentpath
and then place the modules specified in your Puppetfile
into that directory. The local Git repository is known as the master repository; it is where r10k expects to find your Puppetfile
. r10k also understands Puppet environments and will clone Git branches into subdirectories of your environmentpath
, simplifying the deployment of multiple environments. What makes r10k particularly useful is its use of a local cache directory to speed up deployments. Using a configuration file, r10k.yaml
, you can specify where to store this cache and also where your master repository is held.
We'll install r10k on our controlling machine (usually the master). This is where we will control all the modules downloaded and installed.
- Install r10k on your puppet master, or on whichever machine you wish to manage your
environmentpath
directory:root@puppet:~# puppet resource package r10k ensure=installed provider=gem Notice: /Package[r10k]/ensure: created package { 'r10k': ensure => ['1.3.5'], }
- Make a new copy of your Git repository (optional, do this on your Git server):
[git@git repos]$ git clone --bare puppet.git puppet-r10k.git Initialized empty Git repository in /home/git/repos/puppet-r10k.git/
- Check out the new Git repository (on your local machine) and move the existing modules directory to a new location. We'll use
/local
in this example:t@mylaptop ~ $ git clone [email protected]:repos/puppet-r10k.git Cloning into 'puppet-r10k'... remote: Counting objects: 2660, done. remote: Compressing objects: 100% (2136/2136), done. remote: Total 2660 (delta 913), reused 1049 (delta 238) Receiving objects: 100% (2660/2660), 738.20 KiB | 0 bytes/s, done. Resolving deltas: 100% (913/913), done. Checking connectivity... done. t@mylaptop ~ $ cd puppet-r10k/ t@mylaptop ~/puppet-r10k $ git checkout production Branch production set up to track remote branch production from origin. Switched to a new branch 'production' t@mylaptop ~/puppet-r10k $ git mv modules local t@mylaptop ~/puppet-r10k $ git commit -m "moving modules in preparation for r10k" [master c96d0dc] moving modules in preparation for r10k 9 files changed, 0 insertions(+), 0 deletions(-) rename {modules => local}/base (100%) rename {modules => local}/puppet/files/papply.sh (100%) rename {modules => local}/puppet/files/pull-updates.sh (100%) rename {modules => local}/puppet/manifests/init.pp (100%)
We'll create a Puppetfile to control r10k and install modules on our master.
- Create a
Puppetfile
into the new Git repository with the following contents:forge "http://forge.puppetlabs.com" mod 'puppetlabs/puppetdb', '3.0.0' mod 'puppetlabs/stdlib', '3.2.0' mod 'puppetlabs/concat' mod 'puppetlabs/firewall'
- Add the
Puppetfile
to your new repository:t@mylaptop ~/puppet-r10k $ git add Puppetfile t@mylaptop ~/puppet-r10k $ git commit -m "adding Puppetfile" [production d42481f] adding Puppetfile 1 file changed, 7 insertions(+) create mode 100644 Puppetfile t@mylaptop ~/puppet-r10k $ git push Counting objects: 7, done. Delta compression using up to 4 threads. Compressing objects: 100% (5/5), done. Writing objects: 100% (5/5), 589 bytes | 0 bytes/s, done. Total 5 (delta 2), reused 0 (delta 0) To [email protected]:repos/puppet-r10k.git cf8dfb9..d42481f production -> production
- Back to your master, create
/etc/r10k.yaml
with the following contents:--- :cachedir: '/var/cache/r10k' :sources: :plops: remote: '[email protected]:repos/puppet-r10k.git' basedir: '/etc/puppet/environments'
- Run r10k to have the
/etc/puppet/environments
directory populated (hint: create a backup of your/etc/puppet/environments
directory first):root@puppet:~# r10k deploy environment -p
- Verify that your
/etc/puppet/environments
directory has a production subdirectory. Within that directory, the/local
directory will exist and the modules directory will have all the modules listed in thePuppetfile
:root@puppet:/etc/puppet/environments# tree -L 2 . ├── master │ ├── manifests │ ├── modules │ └── README └── production ├── environment.conf ├── local ├── manifests ├── modules ├── Puppetfile └── README
We started by creating a copy of our Git repository; this was only done to preserve the earlier work and is not required. The important thing to remember with r10k and librarian-puppet is that they both assume they are in control of the /modules
subdirectory. We need to move our modules out of the way and create a new location for the modules.
You can automate the deployment of your environments using r10k
. The command we used to run r10k
and populate our environments directory can be easily placed inside a Git hook to automatically update your environment. There is also a marionette collective (mcollective) plugin (https://github.com/acidprime/r10k), which can be used to have r10k
run on an arbitrary set of servers.
Using either of these tools will help keep your site consistent, even if you are not taking advantage of the various modules available on the Forge.
- Install r10k on your puppet master, or on whichever machine you wish to manage your
environmentpath
directory:root@puppet:~# puppet resource package r10k ensure=installed provider=gem Notice: /Package[r10k]/ensure: created package { 'r10k': ensure => ['1.3.5'], }
- Make a new copy of your Git repository (optional, do this on your Git server):
[git@git repos]$ git clone --bare puppet.git puppet-r10k.git Initialized empty Git repository in /home/git/repos/puppet-r10k.git/
- Check out the new Git repository (on your local machine) and move the existing modules directory to a new location. We'll use
/local
in this example:t@mylaptop ~ $ git clone [email protected]:repos/puppet-r10k.git Cloning into 'puppet-r10k'... remote: Counting objects: 2660, done. remote: Compressing objects: 100% (2136/2136), done. remote: Total 2660 (delta 913), reused 1049 (delta 238) Receiving objects: 100% (2660/2660), 738.20 KiB | 0 bytes/s, done. Resolving deltas: 100% (913/913), done. Checking connectivity... done. t@mylaptop ~ $ cd puppet-r10k/ t@mylaptop ~/puppet-r10k $ git checkout production Branch production set up to track remote branch production from origin. Switched to a new branch 'production' t@mylaptop ~/puppet-r10k $ git mv modules local t@mylaptop ~/puppet-r10k $ git commit -m "moving modules in preparation for r10k" [master c96d0dc] moving modules in preparation for r10k 9 files changed, 0 insertions(+), 0 deletions(-) rename {modules => local}/base (100%) rename {modules => local}/puppet/files/papply.sh (100%) rename {modules => local}/puppet/files/pull-updates.sh (100%) rename {modules => local}/puppet/manifests/init.pp (100%)
We'll create a Puppetfile to control r10k and install modules on our master.
- Create a
Puppetfile
into the new Git repository with the following contents:forge "http://forge.puppetlabs.com" mod 'puppetlabs/puppetdb', '3.0.0' mod 'puppetlabs/stdlib', '3.2.0' mod 'puppetlabs/concat' mod 'puppetlabs/firewall'
- Add the
Puppetfile
to your new repository:t@mylaptop ~/puppet-r10k $ git add Puppetfile t@mylaptop ~/puppet-r10k $ git commit -m "adding Puppetfile" [production d42481f] adding Puppetfile 1 file changed, 7 insertions(+) create mode 100644 Puppetfile t@mylaptop ~/puppet-r10k $ git push Counting objects: 7, done. Delta compression using up to 4 threads. Compressing objects: 100% (5/5), done. Writing objects: 100% (5/5), 589 bytes | 0 bytes/s, done. Total 5 (delta 2), reused 0 (delta 0) To [email protected]:repos/puppet-r10k.git cf8dfb9..d42481f production -> production
- Back to your master, create
/etc/r10k.yaml
with the following contents:--- :cachedir: '/var/cache/r10k' :sources: :plops: remote: '[email protected]:repos/puppet-r10k.git' basedir: '/etc/puppet/environments'
- Run r10k to have the
/etc/puppet/environments
directory populated (hint: create a backup of your/etc/puppet/environments
directory first):root@puppet:~# r10k deploy environment -p
- Verify that your
/etc/puppet/environments
directory has a production subdirectory. Within that directory, the/local
directory will exist and the modules directory will have all the modules listed in thePuppetfile
:root@puppet:/etc/puppet/environments# tree -L 2 . ├── master │ ├── manifests │ ├── modules │ └── README └── production ├── environment.conf ├── local ├── manifests ├── modules ├── Puppetfile └── README
We started by creating a copy of our Git repository; this was only done to preserve the earlier work and is not required. The important thing to remember with r10k and librarian-puppet is that they both assume they are in control of the /modules
subdirectory. We need to move our modules out of the way and create a new location for the modules.
You can automate the deployment of your environments using r10k
. The command we used to run r10k
and populate our environments directory can be easily placed inside a Git hook to automatically update your environment. There is also a marionette collective (mcollective) plugin (https://github.com/acidprime/r10k), which can be used to have r10k
run on an arbitrary set of servers.
Using either of these tools will help keep your site consistent, even if you are not taking advantage of the various modules available on the Forge.
Puppetfile
into the new Git repository with the following contents:forge "http://forge.puppetlabs.com" mod 'puppetlabs/puppetdb', '3.0.0' mod 'puppetlabs/stdlib', '3.2.0' mod 'puppetlabs/concat' mod 'puppetlabs/firewall'
Puppetfile
to your new repository:t@mylaptop ~/puppet-r10k $ git add Puppetfile t@mylaptop ~/puppet-r10k $ git commit -m "adding Puppetfile" [production d42481f] adding Puppetfile 1 file changed, 7 insertions(+) create mode 100644 Puppetfile t@mylaptop ~/puppet-r10k $ git push Counting objects: 7, done. Delta compression using up to 4 threads. Compressing objects: 100% (5/5), done. Writing objects: 100% (5/5), 589 bytes | 0 bytes/s, done. Total 5 (delta 2), reused 0 (delta 0) To [email protected]:repos/puppet-r10k.git cf8dfb9..d42481f production -> production
- your master, create
/etc/r10k.yaml
with the following contents:--- :cachedir: '/var/cache/r10k' :sources: :plops: remote: '[email protected]:repos/puppet-r10k.git' basedir: '/etc/puppet/environments'
- Run r10k to have the
/etc/puppet/environments
directory populated (hint: create a backup of your/etc/puppet/environments
directory first):root@puppet:~# r10k deploy environment -p
- Verify that your
/etc/puppet/environments
directory has a production subdirectory. Within that directory, the/local
directory will exist and the modules directory will have all the modules listed in thePuppetfile
:root@puppet:/etc/puppet/environments# tree -L 2 . ├── master │ ├── manifests │ ├── modules │ └── README └── production ├── environment.conf ├── local ├── manifests ├── modules ├── Puppetfile └── README
We started by creating a copy of our Git repository; this was only done to preserve the earlier work and is not required. The important thing to remember with r10k and librarian-puppet is that they both assume they are in control of the /modules
subdirectory. We need to move our modules out of the way and create a new location for the modules.
You can automate the deployment of your environments using r10k
. The command we used to run r10k
and populate our environments directory can be easily placed inside a Git hook to automatically update your environment. There is also a marionette collective (mcollective) plugin (https://github.com/acidprime/r10k), which can be used to have r10k
run on an arbitrary set of servers.
Using either of these tools will help keep your site consistent, even if you are not taking advantage of the various modules available on the Forge.
You can automate the deployment of your environments using r10k
. The command we used to run r10k
and populate our environments directory can be easily placed inside a Git hook to automatically update your environment. There is also a marionette collective (mcollective) plugin (https://github.com/acidprime/r10k), which can be used to have r10k
run on an arbitrary set of servers.
Using either of these tools will help keep your site consistent, even if you are not taking advantage of the various modules available on the Forge.
r10k
. The command we used to run r10k
and populate our environments directory can be easily placed inside a Git hook to automatically update your environment. There is also a marionette collective (mcollective) plugin
(https://github.com/acidprime/r10k), which can be used to have r10k
run on an arbitrary set of servers.
Using either of these tools will help keep your site consistent, even if you are not taking advantage of the various modules available on the Forge.