Chapter 3. Writing Better Manifests
"Measuring programming progress by lines of code is like measuring aircraft building progress by weight." | ||
--Bill Gates |
In this chapter, we will cover:
- Using arrays of resources
- Using resource defaults
- Using defined types
- Using tags
- Using run stages
- Using roles and profiles
- Passing parameters to classes
- Passing parameters from Hiera
- Writing reusable, cross-platform manifests
- Getting information about the environment
- Importing dynamic information
- Passing arguments to shell commands
Introduction
Your Puppet manifests are the living documentation for your entire infrastructure. Keeping them tidy and well organized is a great way to make it easier to maintain and understand. Puppet gives you a number of tools to do this, as follows:
- Arrays
- Defaults
- Defined types
- Dependencies
- Class parameters
We'll see how to use all of these and more. As you read through the chapter, try out the examples and look through your own manifests to see where these features might help you simplify and improve your Puppet code.
Using arrays of resources
Anything that you can do to a resource, you can do to an array of resources. Use this idea to refactor your manifests to make them shorter and clearer.
How to do it…
Here are the steps to refactor using arrays of resources:
- Identify a class in your manifest where you have several instances of the same kind of resource, for example, packages:
package { 'sudo' : ensure => installed } package { 'unzip' : ensure => installed } package { 'locate' : ensure => installed } package { 'lsof' : ensure => installed } package { 'cron' : ensure => installed } package { 'rubygems' : ensure => installed }
- Group them together and replace them with a single package resource using an array:
package { [ 'cron', 'locate', 'lsof', 'rubygems', 'sudo', 'unzip' ]: ensure => installed, }
How it works…
Most of Puppet's resource types can accept an array instead of a single name, and will create one instance for each of the elements in the array. All the parameters you provide for the resource (for example, ensure => installed
) will be assigned to each of the new resource instances. This shorthand will only work when all the resources have the same attributes.
See also
- The Iterating over multiple items recipe in Chapter 1, Puppet Language and Style
package { 'sudo' : ensure => installed } package { 'unzip' : ensure => installed } package { 'locate' : ensure => installed } package { 'lsof' : ensure => installed } package { 'cron' : ensure => installed } package { 'rubygems' : ensure => installed }
package { [ 'cron', 'locate', 'lsof', 'rubygems', 'sudo', 'unzip' ]: ensure => installed, }
How it works…
Most of Puppet's resource types can accept an array instead of a single name, and will create one instance for each of the elements in the array. All the parameters you provide for the resource (for example, ensure => installed
) will be assigned to each of the new resource instances. This shorthand will only work when all the resources have the same attributes.
See also
- The Iterating over multiple items recipe in Chapter 1, Puppet Language and Style
resource types can accept an array instead of a single name, and will create one instance for each of the elements in the array. All the parameters you provide for the resource (for example, ensure => installed
) will be assigned to each of the new resource instances. This shorthand will only work when all the resources have the same attributes.
See also
- The Iterating over multiple items recipe in Chapter 1, Puppet Language and Style
- Chapter 1, Puppet Language and Style
Using resource defaults
A Puppet module is a group of related resources, usually grouped to configure a specific service. Within a module, you may define multiple resources; resource defaults allow you to specify the default attribute values for a resource. In this example, we'll show you how to specify a resource default for the File
type.
How to do it...
To show you how to use resource defaults, we'll create an apache module. Within this module we will specify that the default owner and group are the apache
user as follows:
- Create an apache module and create a resource default for the
File
type:class apache { File { owner => 'apache', group => 'apache', mode => 0644, } }
- Create html files within the
/var/www/html
directory:file {'/var/www/html/index.html': content => "<html><body><h1><a href='cookbook.html'>Cookbook! </a></h1></body></html>\n", } file {'/var/www/html/cookbook.html': content => "<html><body><h2>PacktPub</h2></body></html>\n", }
- Add this class to your default node definition, or use
puppet apply
to apply the module to your node. I will use the method we configured in the previous chapter, pushing our code to the Git repository and using a Git hook to have the code deployed to the Puppet master as follows:t@mylaptop ~/puppet $ git pull origin production From git.example.com:repos/puppet * branch production -> FETCH_HEAD Already up-to-date. t@mylaptop ~/puppet $ cd modules t@mylaptop ~/puppet/modules $ mkdir -p apache/manifests t@mylaptop ~/puppet/modules $ vim apache/manifests/init.pp t@mylaptop ~/puppet/modules $ cd .. t@mylaptop ~/puppet $ vim manifests/site.pp t@mylaptop ~/puppet $ git status On branch production Changes not staged for commit: modified: manifests/site.pp Untracked files: modules/apache/ t@mylaptop ~/puppet $ git add manifests/site.pp modules/apache t@mylaptop ~/puppet $ git commit -m 'adding apache module' [production d639a86] adding apache module 2 files changed, 14 insertions(+) create mode 100644 modules/apache/manifests/init.pp t@mylaptop ~/puppet $ git push origin production Counting objects: 13, done. Delta compression using up to 4 threads. Compressing objects: 100% (6/6), done. Writing objects: 100% (8/8), 885 bytes | 0 bytes/s, done. Total 8 (delta 0), reused 0 (delta 0) remote: To puppet@puppet.example.com:/etc/puppet/environments/puppet.git remote: 832f6a9..d639a86 production -> production remote: Already on 'production' remote: From /etc/puppet/environments/puppet remote: 832f6a9..d639a86 production -> origin/production remote: Updating 832f6a9..d639a86 remote: Fast-forward remote: manifests/site.pp | 1 + remote: modules/apache/manifests/init.pp | 13 +++++++++++++ remote: 2 files changed, 14 insertions(+) remote: create mode 100644 modules/apache/manifests/init.pp To git@git.example.com:repos/puppet.git 832f6a9..d639a86 production -> production
- Apply the module to a node or run Puppet:
Notice: /Stage[main]/Apache/File[/var/www/html/cookbook.html]/ensure: defined content as '{md5}493473fb5bde778ca93d034900348c5d' Notice: /Stage[main]/Apache/File[/var/www/html/index.html]/ensure: defined content as '{md5}184f22c181c5632b86ebf9a0370685b3' Notice: Finished catalog run in 2.00 seconds [root@hiera-test ~]# ls -l /var/www/html total 8 -rw-r--r--. 1 apache apache 44 Sep 15 12:00 cookbook.html -rw-r--r--. 1 apache apache 73 Sep 15 12:00 index.html
How it works...
The resource default we defined specifies the owner, group, and mode for all file resources within this class (also known as within this scope). Unless you specifically override a resource default, the value for an attribute will be taken from the default.
There's more...
You can specify resource defaults for any resource type. You can also specify resource defaults in site.pp
. I find it useful to specify the default action for Package
and Service
resources as follows:
Package { ensure => 'installed' }
Service {
hasrestart => true,
enable => true,
ensure => true,
}
With these defaults, whenever you specify a package, the package will be installed. Whenever you specify a service, the service will be started and enabled to run at boot. These are the usual reasons you specify packages and services, most of the time these defaults will do what you prefer and your code will be cleaner. When you need to disable a service, simply override the defaults.
resource defaults, we'll create an apache module. Within this module we will specify that the default owner and group are the apache
user as follows:
- Create an apache module and create a resource default for the
File
type:class apache { File { owner => 'apache', group => 'apache', mode => 0644, } }
- Create html files within the
/var/www/html
directory:file {'/var/www/html/index.html': content => "<html><body><h1><a href='cookbook.html'>Cookbook! </a></h1></body></html>\n", } file {'/var/www/html/cookbook.html': content => "<html><body><h2>PacktPub</h2></body></html>\n", }
- Add this class to your default node definition, or use
puppet apply
to apply the module to your node. I will use the method we configured in the previous chapter, pushing our code to the Git repository and using a Git hook to have the code deployed to the Puppet master as follows:t@mylaptop ~/puppet $ git pull origin production From git.example.com:repos/puppet * branch production -> FETCH_HEAD Already up-to-date. t@mylaptop ~/puppet $ cd modules t@mylaptop ~/puppet/modules $ mkdir -p apache/manifests t@mylaptop ~/puppet/modules $ vim apache/manifests/init.pp t@mylaptop ~/puppet/modules $ cd .. t@mylaptop ~/puppet $ vim manifests/site.pp t@mylaptop ~/puppet $ git status On branch production Changes not staged for commit: modified: manifests/site.pp Untracked files: modules/apache/ t@mylaptop ~/puppet $ git add manifests/site.pp modules/apache t@mylaptop ~/puppet $ git commit -m 'adding apache module' [production d639a86] adding apache module 2 files changed, 14 insertions(+) create mode 100644 modules/apache/manifests/init.pp t@mylaptop ~/puppet $ git push origin production Counting objects: 13, done. Delta compression using up to 4 threads. Compressing objects: 100% (6/6), done. Writing objects: 100% (8/8), 885 bytes | 0 bytes/s, done. Total 8 (delta 0), reused 0 (delta 0) remote: To puppet@puppet.example.com:/etc/puppet/environments/puppet.git remote: 832f6a9..d639a86 production -> production remote: Already on 'production' remote: From /etc/puppet/environments/puppet remote: 832f6a9..d639a86 production -> origin/production remote: Updating 832f6a9..d639a86 remote: Fast-forward remote: manifests/site.pp | 1 + remote: modules/apache/manifests/init.pp | 13 +++++++++++++ remote: 2 files changed, 14 insertions(+) remote: create mode 100644 modules/apache/manifests/init.pp To git@git.example.com:repos/puppet.git 832f6a9..d639a86 production -> production
- Apply the module to a node or run Puppet:
Notice: /Stage[main]/Apache/File[/var/www/html/cookbook.html]/ensure: defined content as '{md5}493473fb5bde778ca93d034900348c5d' Notice: /Stage[main]/Apache/File[/var/www/html/index.html]/ensure: defined content as '{md5}184f22c181c5632b86ebf9a0370685b3' Notice: Finished catalog run in 2.00 seconds [root@hiera-test ~]# ls -l /var/www/html total 8 -rw-r--r--. 1 apache apache 44 Sep 15 12:00 cookbook.html -rw-r--r--. 1 apache apache 73 Sep 15 12:00 index.html
How it works...
The resource default we defined specifies the owner, group, and mode for all file resources within this class (also known as within this scope). Unless you specifically override a resource default, the value for an attribute will be taken from the default.
There's more...
You can specify resource defaults for any resource type. You can also specify resource defaults in site.pp
. I find it useful to specify the default action for Package
and Service
resources as follows:
Package { ensure => 'installed' }
Service {
hasrestart => true,
enable => true,
ensure => true,
}
With these defaults, whenever you specify a package, the package will be installed. Whenever you specify a service, the service will be started and enabled to run at boot. These are the usual reasons you specify packages and services, most of the time these defaults will do what you prefer and your code will be cleaner. When you need to disable a service, simply override the defaults.
There's more...
You can specify resource defaults for any resource type. You can also specify resource defaults in site.pp
. I find it useful to specify the default action for Package
and Service
resources as follows:
Package { ensure => 'installed' }
Service {
hasrestart => true,
enable => true,
ensure => true,
}
With these defaults, whenever you specify a package, the package will be installed. Whenever you specify a service, the service will be started and enabled to run at boot. These are the usual reasons you specify packages and services, most of the time these defaults will do what you prefer and your code will be cleaner. When you need to disable a service, simply override the defaults.
resource type. You can also specify resource defaults in site.pp
. I find it useful to specify the default action for Package
and Service
resources as follows:
Package { ensure => 'installed' }
Service {
hasrestart => true,
enable => true,
ensure => true,
}
With these defaults, whenever you specify a package, the package will be installed. Whenever you specify a service, the service will be started and enabled to run at boot. These are the usual reasons you specify packages and services, most of the time these defaults will do what you prefer and your code will be cleaner. When you need to disable a service, simply override the defaults.
Using defined types
In the previous example, we saw how to reduce redundant code by grouping identical resources into arrays. However, this technique is limited to resources where all the parameters are the same. When you have a set of resources that have some parameters in common, you need to use a defined type to group them together.
How to do it…
The following steps will show you how to create a definition:
- Add the following code to your manifest:
define tmpfile() { file { "/tmp/${name}": content => "Hello, world\n", } } tmpfile { ['a', 'b', 'c']: }
- Run Puppet:
[root@hiera-test ~]# vim tmp.pp [root@hiera-test ~]# puppet apply tmp.pp Notice: Compiled catalog for hiera-test.example.com in environment production in 0.11 seconds Notice: /Stage[main]/Main/Tmpfile[a]/File[/tmp/a]/ensure: defined content as '{md5}a7966bf58e23583c9a5a4059383ff850' Notice: /Stage[main]/Main/Tmpfile[b]/File[/tmp/b]/ensure: defined content as '{md5}a7966bf58e23583c9a5a4059383ff850' Notice: /Stage[main]/Main/Tmpfile[c]/File[/tmp/c]/ensure: defined content as '{md5}a7966bf58e23583c9a5a4059383ff850' Notice: Finished catalog run in 0.09 seconds [root@hiera-test ~]# cat /tmp/{a,b,c} Hello, world Hello, world Hello, world
How it works…
You can think of a defined type (introduced with the define
keyword) as a cookie-cutter. It describes a pattern that Puppet can use to create lots of similar resources. Any time you declare a tmpfile
instance in your manifest, Puppet will insert all the resources contained in the tmpfile
definition.
In our example, the definition of tmpfile
contains a single file
resource whose content is Hello, world\n
and whose path is /tmp/${name}
. If you declared an instance of tmpfile
with the name foo
:
tmpfile { 'foo': }
Puppet will create a file with the path /tmp/foo
. In other words, ${name}
in the definition will be replaced by the name
of any actual instance that Puppet is asked to create. It's almost as though we created a new kind of resource: tmpfile
, which has one parameter—its name
.
Just like with regular resources, we don't have to pass just one title; as in the preceding example, we can provide an array of titles and Puppet will create as many resources as required.
Tip
A word on name, the namevar: Every resource you create must have a unique name, the namevar. This is different than the title, which is how puppet refers to the resource internally (although they are often the same).
There's more…
In the example, we created a definition where the only parameter that varies between instances is the name
parameter. But we can add whatever parameters we want, so long as we declare them in the definition in parentheses after the name
parameter, as follows:
define tmpfile($greeting) {
file { "/tmp/${name}": content => $greeting,
}
}
Next, pass values to them when we declare an instance of the resource:
tmpfile{ 'foo':
greeting => "Good Morning\n",
}
You can declare multiple parameters as a comma-separated list:
define webapp($domain,$path,$platform) {
...
}
webapp { 'mywizzoapp':
domain => 'mywizzoapp.com',
path => '/var/www/apps/mywizzoapp',
platform => 'Rails',
}
You can also declare default values for any parameters that aren't supplied, thus making them optional:
define tmpfile($greeting,$mode='0644') {
...
}
This is a powerful technique for abstracting out everything that's common to certain resources, and keeping it in one place so that you don't repeat yourself. In the preceding example, there might be many individual resources contained within webapp
: packages, config files, source code checkouts, virtual hosts, and so on. But all of them are the same for every instance of webapp
except the parameters we provide. These might be referenced in a template, for example, to set the domain for a virtual host.
See also
- The Passing parameters to classes recipe, in this chapter
define tmpfile() { file { "/tmp/${name}": content => "Hello, world\n", } } tmpfile { ['a', 'b', 'c']: }
[root@hiera-test ~]# vim tmp.pp [root@hiera-test ~]# puppet apply tmp.pp Notice: Compiled catalog for hiera-test.example.com in environment production in 0.11 seconds Notice: /Stage[main]/Main/Tmpfile[a]/File[/tmp/a]/ensure: defined content as '{md5}a7966bf58e23583c9a5a4059383ff850' Notice: /Stage[main]/Main/Tmpfile[b]/File[/tmp/b]/ensure: defined content as '{md5}a7966bf58e23583c9a5a4059383ff850' Notice: /Stage[main]/Main/Tmpfile[c]/File[/tmp/c]/ensure: defined content as '{md5}a7966bf58e23583c9a5a4059383ff850' Notice: Finished catalog run in 0.09 seconds [root@hiera-test ~]# cat /tmp/{a,b,c} Hello, world Hello, world Hello, world
How it works…
You can think of a defined type (introduced with the define
keyword) as a cookie-cutter. It describes a pattern that Puppet can use to create lots of similar resources. Any time you declare a tmpfile
instance in your manifest, Puppet will insert all the resources contained in the tmpfile
definition.
In our example, the definition of tmpfile
contains a single file
resource whose content is Hello, world\n
and whose path is /tmp/${name}
. If you declared an instance of tmpfile
with the name foo
:
tmpfile { 'foo': }
Puppet will create a file with the path /tmp/foo
. In other words, ${name}
in the definition will be replaced by the name
of any actual instance that Puppet is asked to create. It's almost as though we created a new kind of resource: tmpfile
, which has one parameter—its name
.
Just like with regular resources, we don't have to pass just one title; as in the preceding example, we can provide an array of titles and Puppet will create as many resources as required.
Tip
A word on name, the namevar: Every resource you create must have a unique name, the namevar. This is different than the title, which is how puppet refers to the resource internally (although they are often the same).
There's more…
In the example, we created a definition where the only parameter that varies between instances is the name
parameter. But we can add whatever parameters we want, so long as we declare them in the definition in parentheses after the name
parameter, as follows:
define tmpfile($greeting) {
file { "/tmp/${name}": content => $greeting,
}
}
Next, pass values to them when we declare an instance of the resource:
tmpfile{ 'foo':
greeting => "Good Morning\n",
}
You can declare multiple parameters as a comma-separated list:
define webapp($domain,$path,$platform) {
...
}
webapp { 'mywizzoapp':
domain => 'mywizzoapp.com',
path => '/var/www/apps/mywizzoapp',
platform => 'Rails',
}
You can also declare default values for any parameters that aren't supplied, thus making them optional:
define tmpfile($greeting,$mode='0644') {
...
}
This is a powerful technique for abstracting out everything that's common to certain resources, and keeping it in one place so that you don't repeat yourself. In the preceding example, there might be many individual resources contained within webapp
: packages, config files, source code checkouts, virtual hosts, and so on. But all of them are the same for every instance of webapp
except the parameters we provide. These might be referenced in a template, for example, to set the domain for a virtual host.
See also
- The Passing parameters to classes recipe, in this chapter
type (introduced with the define
keyword) as a cookie-cutter. It describes a pattern that Puppet can use to create lots of similar resources. Any time you declare a tmpfile
instance in your manifest, Puppet will insert all the resources contained in the tmpfile
definition.
In our example, the definition of tmpfile
contains a single file
resource whose content is Hello, world\n
and whose path is /tmp/${name}
. If you declared an instance of tmpfile
with the name foo
:
tmpfile { 'foo': }
Puppet will create a file with the path /tmp/foo
. In other words, ${name}
in the definition will be replaced by the name
of any actual instance that Puppet is asked to create. It's almost as though we created a new kind of resource: tmpfile
, which has one parameter—its name
.
Just like with regular resources, we don't have to pass just one title; as in the preceding example, we can provide an array of titles and Puppet will create as many resources as required.
Tip
A word on name, the namevar: Every resource you create must have a unique name, the namevar. This is different than the title, which is how puppet refers to the resource internally (although they are often the same).
There's more…
In the example, we created a definition where the only parameter that varies between instances is the name
parameter. But we can add whatever parameters we want, so long as we declare them in the definition in parentheses after the name
parameter, as follows:
define tmpfile($greeting) {
file { "/tmp/${name}": content => $greeting,
}
}
Next, pass values to them when we declare an instance of the resource:
tmpfile{ 'foo':
greeting => "Good Morning\n",
}
You can declare multiple parameters as a comma-separated list:
define webapp($domain,$path,$platform) {
...
}
webapp { 'mywizzoapp':
domain => 'mywizzoapp.com',
path => '/var/www/apps/mywizzoapp',
platform => 'Rails',
}
You can also declare default values for any parameters that aren't supplied, thus making them optional:
define tmpfile($greeting,$mode='0644') {
...
}
This is a powerful technique for abstracting out everything that's common to certain resources, and keeping it in one place so that you don't repeat yourself. In the preceding example, there might be many individual resources contained within webapp
: packages, config files, source code checkouts, virtual hosts, and so on. But all of them are the same for every instance of webapp
except the parameters we provide. These might be referenced in a template, for example, to set the domain for a virtual host.
See also
- The Passing parameters to classes recipe, in this chapter
name
parameter. But we can add whatever parameters we want, so long as we declare them in the definition in parentheses after the name
parameter, as follows:
for abstracting out everything that's common to certain resources, and keeping it in one place so that you don't repeat yourself. In the preceding example, there might be many individual resources contained within webapp
: packages, config files, source code checkouts, virtual hosts, and so on. But all of them are the same for every instance of webapp
except the parameters we provide. These might be referenced in a template, for example, to set the domain for a virtual host.
See also
- The Passing parameters to classes recipe, in this chapter
Using tags
Sometimes one Puppet class needs to know about another or at least to know whether or not it's present. For example, a class that manages the firewall may need to know whether or not the node is a web server.
Puppet's tagged
function will tell you whether a named class or resource is present in the catalog for this node. You can also apply arbitrary tags to a node or class and check for the presence of these tags. Tags are another metaparameter, similar to require
and notify
we introduced in Chapter 1, Puppet Language and Style. Metaparameters are used in the compilation of the Puppet catalog but are not an attribute of the resource to which they are attached.
How to do it...
To help you find out if you're running on a particular node or class of nodes all nodes are automatically tagged with the node name and the names of any classes they include. Here's an example that shows you how to use tagged
to get this information:
- Add the following code to your
site.pp
file (replacingcookbook
with your machine'shostname
):node 'cookbook' { if tagged('cookbook') { notify { 'tagged cookbook': } } }
- Run Puppet:
root@cookbook:~# puppet agent -vt Info: Caching catalog for cookbook Info: Applying configuration version '1410848350' Notice: tagged cookbook Notice: Finished catalog run in 1.00 seconds
Nodes are also automatically tagged with the names of all the classes they include in addition to several other automatic tags. You can use
tagged
to find out what classes are included on the node.You're not just limited to checking the tags automatically applied by Puppet. You can also add your own. To set an arbitrary tag on a node, use the
tag
function, as in the following example: - Modify your
site.pp
file as follows:node 'cookbook' { tag('tagging') class {'tag_test': } }
- Add a
tag_test
module with the followinginit.pp
(or be lazy and add the following definition to yoursite.pp
):class tag_test { if tagged('tagging') { notify { 'containing node/class was tagged.': } } }
- Run Puppet:
root@cookbook:~# puppet agent -vt Info: Caching catalog for cookbook Info: Applying configuration version '1410851300' Notice: containing node/class was tagged. Notice: Finished catalog run in 0.22 seconds
- You can also use tags to determine which parts of the manifest to apply. If you use the
--tags
option on the Puppet command line, Puppet will apply only those classes or resources tagged with the specific tags you include. For example, we can define ourcookbook
class with two classes:node cookbook { class {'first_class': } class {'second_class': } } class first_class { notify { 'First Class': } } class second_class { notify {'Second Class': } }
- Now when we run
puppet agent
on thecookbook
node, we see both notifies:root@cookbook:~# puppet agent -t Notice: Second Class Notice: First Class Notice: Finished catalog run in 0.22 seconds
- Now apply the
first_class
andadd --tags
function to the command line:root@cookbook:~# puppet agent -t --tags first_class Notice: First Class Notice: Finished catalog run in 0.07 seconds
There's more…
You can use tags to create a collection of resources, and then make the collection a dependency for some other resource. For example, say some service depends on a config file that is built from a number of file snippets, as in the following example:
class firewall::service {
service { 'firewall': ...
}
File <| tag == 'firewall-snippet' |> ~> Service['firewall']
}
class myapp {
file { '/etc/firewall.d/myapp.conf': tag => 'firewall-snippet', ...
}
}
Here, we've specified that the firewall
service should be notified if any file resource tagged firewall-snippet
is updated. All we need to do to add a firewall
config snippet for any particular application or service is to tag it firewall-snippet
, and Puppet will do the rest.
Although we could add a notify => Service["firewall"]
function to each snippet resource if our definition of the firewall
service were ever to change, we would have to hunt down and update all the snippets accordingly. The tag lets us encapsulate the logic in one place, making future maintenance and refactoring much easier.
Note
What's <| tag == 'firewall-snippet' |> syntax
? This is called a resource collector, and it's a way of specifying a group of resources by searching for some piece of data about them; in this case, the value of a tag. You can find out more about resource collectors and the <| |>
operator (sometimes known as the spaceship operator) on the Puppet Labs website: http://docs.puppetlabs.com/puppet/3/reference/lang_collectors.html.
tagged
to get this information:
site.pp
file (replacing cookbook
with your machine's hostname
):node 'cookbook' { if tagged('cookbook') { notify { 'tagged cookbook': } } }
root@cookbook:~# puppet agent -vt Info: Caching catalog for cookbook Info: Applying configuration version '1410848350' Notice: tagged cookbook Notice: Finished catalog run in 1.00 seconds
also automatically tagged with the names of all the classes they include in addition to several other automatic tags. You can use
tagged
to find out what classes are included on the node.You're not just limited to checking the tags automatically applied by Puppet. You can also add your own. To set an arbitrary tag on a node, use the
tag
function, as in the following example:- Modify your
site.pp
file as follows:node 'cookbook' { tag('tagging') class {'tag_test': } }
- Add a
tag_test
module with the followinginit.pp
(or be lazy and add the following definition to yoursite.pp
):class tag_test { if tagged('tagging') { notify { 'containing node/class was tagged.': } } }
- Run Puppet:
root@cookbook:~# puppet agent -vt Info: Caching catalog for cookbook Info: Applying configuration version '1410851300' Notice: containing node/class was tagged. Notice: Finished catalog run in 0.22 seconds
- You can also use tags to determine which parts of the manifest to apply. If you use the
--tags
option on the Puppet command line, Puppet will apply only those classes or resources tagged with the specific tags you include. For example, we can define ourcookbook
class with two classes:node cookbook { class {'first_class': } class {'second_class': } } class first_class { notify { 'First Class': } } class second_class { notify {'Second Class': } }
- Now when we run
puppet agent
on thecookbook
node, we see both notifies:root@cookbook:~# puppet agent -t Notice: Second Class Notice: First Class Notice: Finished catalog run in 0.22 seconds
- Now apply the
first_class
andadd --tags
function to the command line:root@cookbook:~# puppet agent -t --tags first_class Notice: First Class Notice: Finished catalog run in 0.07 seconds
There's more…
You can use tags to create a collection of resources, and then make the collection a dependency for some other resource. For example, say some service depends on a config file that is built from a number of file snippets, as in the following example:
class firewall::service {
service { 'firewall': ...
}
File <| tag == 'firewall-snippet' |> ~> Service['firewall']
}
class myapp {
file { '/etc/firewall.d/myapp.conf': tag => 'firewall-snippet', ...
}
}
Here, we've specified that the firewall
service should be notified if any file resource tagged firewall-snippet
is updated. All we need to do to add a firewall
config snippet for any particular application or service is to tag it firewall-snippet
, and Puppet will do the rest.
Although we could add a notify => Service["firewall"]
function to each snippet resource if our definition of the firewall
service were ever to change, we would have to hunt down and update all the snippets accordingly. The tag lets us encapsulate the logic in one place, making future maintenance and refactoring much easier.
Note
What's <| tag == 'firewall-snippet' |> syntax
? This is called a resource collector, and it's a way of specifying a group of resources by searching for some piece of data about them; in this case, the value of a tag. You can find out more about resource collectors and the <| |>
operator (sometimes known as the spaceship operator) on the Puppet Labs website: http://docs.puppetlabs.com/puppet/3/reference/lang_collectors.html.
firewall
service should be notified if any file resource tagged firewall-snippet
is updated. All we need to do to add a firewall
config snippet for any particular application or service is to tag it firewall-snippet
, and Puppet will do the rest.
add a notify => Service["firewall"]
function to each snippet resource if our definition of the firewall
service were ever to change, we would have to hunt down and update all the snippets accordingly. The tag lets us encapsulate the logic in one place, making future maintenance and refactoring much easier.
Note
What's <| tag == 'firewall-snippet' |> syntax
? This is called a resource collector, and it's a way of specifying a group of resources by searching for some piece of data about them; in this case, the value of a tag. You can find out more about resource collectors and the <| |>
operator (sometimes known as the spaceship operator) on the Puppet Labs website: http://docs.puppetlabs.com/puppet/3/reference/lang_collectors.html.
Using run stages
A common requirement is to apply a certain group of resources before other groups (for example, installing a package repository or a custom Ruby version), or after others (for example, deploying an application once its dependencies are installed). Puppet's run stages feature allows you to do this.
By default, all resources in your manifest are applied in a single stage named main
. If you need a resource to be applied before all others, you can assign it to a new run stage that is specified to come before main
. Similarly, you could define a run stage that comes after main
. In fact, you can define as many run stages as you need and tell Puppet which order they should be applied in.
In this example, we'll use stages to ensure one class is applied first and another last.
How to do it…
Here are the steps to create an example of using run stages
:
- Create the file
modules/admin/manifests/stages.pp
with the following contents:class admin::stages { stage { 'first': before => Stage['main'] } stage { 'last': require => Stage['main'] } class me_first { notify { 'This will be done first': } } class me_last { notify { 'This will be done last': } } class { 'me_first': stage => 'first', } class { 'me_last': stage => 'last', } }
- Modify your
site.pp
file as follows:node 'cookbook' { class {'first_class': } class {'second_class': } include admin::stages }
- Run Puppet:
root@cookbook:~# puppet agent -t Info: Applying configuration version '1411019225' Notice: This will be done first Notice: Second Class Notice: First Class Notice: This will be done last Notice: Finished catalog run in 0.43 seconds
How it works…
Let's examine this code in detail to see what's happening. First, we declare the run stages first
and last
, as follows:
stage { 'first': before => Stage['main'] }
stage { 'last': require => Stage['main'] }
For the first
stage, we've specified that it should come before main
. That is, every resource marked as being in the first
stage will be applied before any resource in the main
stage (the default stage).
The last
stage requires the main
stage, so no resource in the last
stage can be applied until after every resource in the main
stage.
We then declare some classes that we'll later assign to these run stages:
class me_first {
notify { 'This will be done first': }
}
class me_last {
notify { 'This will be done last': }
}
We can now put it all together and include these classes on the node, specifying the run stages for each as we do so:
class { 'me_first': stage => 'first',
}
class { 'me_last': stage => 'last',
}
Note that in the class
declarations for me_first
and me_last
, we didn't have to specify that they take a stage
parameter. The stage
parameter is another metaparameter, which means it can be applied to any class or resource without having to be explicitly declared. When we ran puppet agent
on our Puppet node, the notify from the me_first
class was applied before the notifies from first_class
and second_class
. The notify from me_last
was applied after the main
stage, so it comes after the two notifies from first_class
and second_class
. If you run puppet agent
multiple times, you will see that the notifies from first_class
and second_class
may not always appear in the same order but the me_first
class will always come first and the me_last
class will always come last.
There's more…
You can define as many run stages as you like, and set up any ordering for them. This can greatly simplify a complicated manifest that would otherwise require lots of explicit dependencies between resources. Beware of accidentally introducing dependency cycles, though; when you assign something to a run stage you're automatically making it dependent on everything in prior stages.
You may like to define your stages in the site.pp
file instead, so that at the top level of the manifest, it's easy to see what stages are available.
Gary Larizza has written a helpful introduction to using run stages, with some real-world examples, on his website:
http://garylarizza.com/blog/2011/03/11/using-run-stages-with-puppet/
A caveat: many people don't like to use run stages, feeling that Puppet already provides sufficient resource ordering control, and that using run stages indiscriminately can make your code very hard to follow. The use of run stages should be kept to a minimum wherever possible. There are a few key examples where the use of stages creates less complexity. The most notable is when a resource modifies the system used to install packages on the system. It helps to have a package management stage that comes before the main stage. When packages are defined in the main
(default) stage, your manifests can count on the updated package management configuration information being present. For instance, for a Yum-based system, you would create a yumrepos
stage that comes before main
. You can specify this dependency using chaining arrows as shown in the following code snippet:
stage {'yumrepos': }
Stage['yumrepos'] -> Stage['main']
We can then create a class that creates a Yum repository (yumrepo
) resource and assign it to the yumrepos
stage as follows:
class {'yums': stage => 'yumrepos',
}
class yums {
notify {'always before the rest': }
yumrepo {'testrepo': baseurl => 'file:///var/yum', ensure => 'present',
}
}
For Apt-based systems, the same example would be a stage where Apt sources are defined. The key with stages is to keep their definitions in your site.pp
file where they are highly visible and to only use them sparingly where you can guarantee that you will not introduce dependency cycles.
See also
- The Using tags recipe, in this chapter
- The Drawing dependency graphs recipe in Chapter 10, Monitoring, Reporting, and Troubleshooting
stages
:
modules/admin/manifests/stages.pp
with the following contents:class admin::stages { stage { 'first': before => Stage['main'] } stage { 'last': require => Stage['main'] } class me_first { notify { 'This will be done first': } } class me_last { notify { 'This will be done last': } } class { 'me_first': stage => 'first', } class { 'me_last': stage => 'last', } }
site.pp
file as follows:node 'cookbook' { class {'first_class': } class {'second_class': } include admin::stages }
- Puppet:
root@cookbook:~# puppet agent -t Info: Applying configuration version '1411019225' Notice: This will be done first Notice: Second Class Notice: First Class Notice: This will be done last Notice: Finished catalog run in 0.43 seconds
How it works…
Let's examine this code in detail to see what's happening. First, we declare the run stages first
and last
, as follows:
stage { 'first': before => Stage['main'] }
stage { 'last': require => Stage['main'] }
For the first
stage, we've specified that it should come before main
. That is, every resource marked as being in the first
stage will be applied before any resource in the main
stage (the default stage).
The last
stage requires the main
stage, so no resource in the last
stage can be applied until after every resource in the main
stage.
We then declare some classes that we'll later assign to these run stages:
class me_first {
notify { 'This will be done first': }
}
class me_last {
notify { 'This will be done last': }
}
We can now put it all together and include these classes on the node, specifying the run stages for each as we do so:
class { 'me_first': stage => 'first',
}
class { 'me_last': stage => 'last',
}
Note that in the class
declarations for me_first
and me_last
, we didn't have to specify that they take a stage
parameter. The stage
parameter is another metaparameter, which means it can be applied to any class or resource without having to be explicitly declared. When we ran puppet agent
on our Puppet node, the notify from the me_first
class was applied before the notifies from first_class
and second_class
. The notify from me_last
was applied after the main
stage, so it comes after the two notifies from first_class
and second_class
. If you run puppet agent
multiple times, you will see that the notifies from first_class
and second_class
may not always appear in the same order but the me_first
class will always come first and the me_last
class will always come last.
There's more…
You can define as many run stages as you like, and set up any ordering for them. This can greatly simplify a complicated manifest that would otherwise require lots of explicit dependencies between resources. Beware of accidentally introducing dependency cycles, though; when you assign something to a run stage you're automatically making it dependent on everything in prior stages.
You may like to define your stages in the site.pp
file instead, so that at the top level of the manifest, it's easy to see what stages are available.
Gary Larizza has written a helpful introduction to using run stages, with some real-world examples, on his website:
http://garylarizza.com/blog/2011/03/11/using-run-stages-with-puppet/
A caveat: many people don't like to use run stages, feeling that Puppet already provides sufficient resource ordering control, and that using run stages indiscriminately can make your code very hard to follow. The use of run stages should be kept to a minimum wherever possible. There are a few key examples where the use of stages creates less complexity. The most notable is when a resource modifies the system used to install packages on the system. It helps to have a package management stage that comes before the main stage. When packages are defined in the main
(default) stage, your manifests can count on the updated package management configuration information being present. For instance, for a Yum-based system, you would create a yumrepos
stage that comes before main
. You can specify this dependency using chaining arrows as shown in the following code snippet:
stage {'yumrepos': }
Stage['yumrepos'] -> Stage['main']
We can then create a class that creates a Yum repository (yumrepo
) resource and assign it to the yumrepos
stage as follows:
class {'yums': stage => 'yumrepos',
}
class yums {
notify {'always before the rest': }
yumrepo {'testrepo': baseurl => 'file:///var/yum', ensure => 'present',
}
}
For Apt-based systems, the same example would be a stage where Apt sources are defined. The key with stages is to keep their definitions in your site.pp
file where they are highly visible and to only use them sparingly where you can guarantee that you will not introduce dependency cycles.
See also
- The Using tags recipe, in this chapter
- The Drawing dependency graphs recipe in Chapter 10, Monitoring, Reporting, and Troubleshooting
first
and last
, as follows:
first
stage, we've specified that it should come before main
. That is, every resource marked as being in the first
stage will be applied before any resource in the main
stage (the default stage).
last
stage requires the main
stage, so no resource in the last
stage can be applied until after every resource in the main
stage.
all together and include these classes on the node, specifying the run stages for each as we do so:
class { 'me_first': stage => 'first',
}
class { 'me_last': stage => 'last',
}
Note that in the class
declarations for me_first
and me_last
, we didn't have to specify that they take a stage
parameter. The stage
parameter is another metaparameter, which means it can be applied to any class or resource without having to be explicitly declared. When we ran puppet agent
on our Puppet node, the notify from the me_first
class was applied before the notifies from first_class
and second_class
. The notify from me_last
was applied after the main
stage, so it comes after the two notifies from first_class
and second_class
. If you run puppet agent
multiple times, you will see that the notifies from first_class
and second_class
may not always appear in the same order but the me_first
class will always come first and the me_last
class will always come last.
There's more…
You can define as many run stages as you like, and set up any ordering for them. This can greatly simplify a complicated manifest that would otherwise require lots of explicit dependencies between resources. Beware of accidentally introducing dependency cycles, though; when you assign something to a run stage you're automatically making it dependent on everything in prior stages.
You may like to define your stages in the site.pp
file instead, so that at the top level of the manifest, it's easy to see what stages are available.
Gary Larizza has written a helpful introduction to using run stages, with some real-world examples, on his website:
http://garylarizza.com/blog/2011/03/11/using-run-stages-with-puppet/
A caveat: many people don't like to use run stages, feeling that Puppet already provides sufficient resource ordering control, and that using run stages indiscriminately can make your code very hard to follow. The use of run stages should be kept to a minimum wherever possible. There are a few key examples where the use of stages creates less complexity. The most notable is when a resource modifies the system used to install packages on the system. It helps to have a package management stage that comes before the main stage. When packages are defined in the main
(default) stage, your manifests can count on the updated package management configuration information being present. For instance, for a Yum-based system, you would create a yumrepos
stage that comes before main
. You can specify this dependency using chaining arrows as shown in the following code snippet:
stage {'yumrepos': }
Stage['yumrepos'] -> Stage['main']
We can then create a class that creates a Yum repository (yumrepo
) resource and assign it to the yumrepos
stage as follows:
class {'yums': stage => 'yumrepos',
}
class yums {
notify {'always before the rest': }
yumrepo {'testrepo': baseurl => 'file:///var/yum', ensure => 'present',
}
}
For Apt-based systems, the same example would be a stage where Apt sources are defined. The key with stages is to keep their definitions in your site.pp
file where they are highly visible and to only use them sparingly where you can guarantee that you will not introduce dependency cycles.
See also
- The Using tags recipe, in this chapter
- The Drawing dependency graphs recipe in Chapter 10, Monitoring, Reporting, and Troubleshooting
site.pp
file instead, so that at the top level of the manifest, it's easy to see what stages are available.
some real-world examples, on his website:
http://garylarizza.com/blog/2011/03/11/using-run-stages-with-puppet/
A caveat: many people don't like to use run stages, feeling that Puppet already provides sufficient resource ordering control, and that using run stages indiscriminately can make your code very hard to follow. The use of run stages should be kept to a minimum wherever possible. There are a few key examples where the use of stages creates less complexity. The most notable is when a resource modifies the system used to install packages on the system. It helps to have a package management stage that comes before the main stage. When packages are defined in the main
(default) stage, your manifests can count on the updated package management configuration information being present. For instance, for a Yum-based system, you would create a yumrepos
stage that comes before main
. You can specify this dependency using chaining arrows as shown in the following code snippet:
stage {'yumrepos': }
Stage['yumrepos'] -> Stage['main']
We can then create a class that creates a Yum repository (yumrepo
) resource and assign it to the yumrepos
stage as follows:
class {'yums': stage => 'yumrepos',
}
class yums {
notify {'always before the rest': }
yumrepo {'testrepo': baseurl => 'file:///var/yum', ensure => 'present',
}
}
For Apt-based systems, the same example would be a stage where Apt sources are defined. The key with stages is to keep their definitions in your site.pp
file where they are highly visible and to only use them sparingly where you can guarantee that you will not introduce dependency cycles.
See also
- The Using tags recipe, in this chapter
- The Drawing dependency graphs recipe in Chapter 10, Monitoring, Reporting, and Troubleshooting
- Chapter 10, Monitoring, Reporting, and Troubleshooting
Using roles and profiles
Well organized Puppet manifests are easy to read; the purpose of a module should be evident in its name. The purpose of a node should be defined in a single class. This single class should include all classes that are required to perform that purpose. Craig Dunn wrote a post about such a classification system, which he dubbed "roles and profiles" (http://www.craigdunn.org/2012/05/239/). In this model, roles are the single purpose of a node, a node may only have one role, a role may contain more than one profile, and a profile contains all the resources related to a single service. In this example, we will create a web server role that uses several profiles.
How to do it…
We'll create two modules to store our roles and profiles. Roles will contain one or more profiles. Each role or profile will be defined as a subclass, such as profile::base
- Decide on a naming strategy for your roles and profiles. In our example, we will create two modules,
roles
andprofiles
that will contain our roles and profiles respectively:$ puppet module generate thomas-profiles $ ln -s thomas-profiles profiles $ puppet module generate thomas-roles $ ln -s thomas-roles roles
- Begin defining the constituent parts of our
webserver
role as profiles. To keep this example simple, we will create two profiles. First, abase
profile to include our basic server configuration classes. Second, anapache
class to install and configure the apache web server (httpd
) as follows:$ vim profiles/manifests/base.pp class profiles::base { include base } $ vim profiles/manifests/apache.pp class profiles::apache { $apache = $::osfamily ? { 'RedHat' => 'httpd', 'Debian' => 'apache2', } service { "$apache": enable => true, ensure => true, } package { "$apache": ensure => 'installed', } }
- Define a
roles::webserver
class for ourwebserver
role as follows:$ vim roles/manifests/webserver.pp class roles::webserver { include profiles::apache include profiles::base }
- Apply the
roles::webserver
class to a node. In a centralized installation, you would use either an External Node Classifier (ENC) to apply the class to the node, or you would use Hiera to define the role:node 'webtest' { include roles::webserver }
How it works…
Breaking down the parts of the web server configuration into different profiles allows us to apply those parts independently. We created a base profile that we can expand to include all the resources we would like applied to all nodes. Our roles::webserver
class simply includes the base
and apache
classes.
There's more…
As we'll see in the next section, we can pass parameters to classes to alter how they work. In our roles::webserver
class, we can use the class instantiation syntax instead of include
, and override it with parameters
in the classes. For instance, to pass a parameter to the base
class, we would use:
class {'profiles::base':
parameter => 'newvalue'
}
where we previously used:
include profiles::base
Tip
In previous versions of this book, node and class inheritance were used to achieve a similar goal, code reuse. Node inheritance is deprecated in Puppet Version 3.7 and higher. Node and class inheritance should be avoided. Using roles and profiles achieves the same level of readability and is much easier to follow.
profile::base
roles
and profiles
that will contain our roles and profiles respectively:$ puppet module generate thomas-profiles $ ln -s thomas-profiles profiles $ puppet module generate thomas-roles $ ln -s thomas-roles roles
webserver
role as profiles. To keep this example simple, we will create two profiles. First, a base
profile to include our basic server configuration classes. Second, an apache
class to install and configure the apache web server (httpd
) as follows:$ vim profiles/manifests/base.pp class profiles::base { include base } $ vim profiles/manifests/apache.pp class profiles::apache { $apache = $::osfamily ? { 'RedHat' => 'httpd', 'Debian' => 'apache2', } service { "$apache": enable => true, ensure => true, } package { "$apache": ensure => 'installed', } }
roles::webserver
class for our webserver
role as follows:$ vim roles/manifests/webserver.pp class roles::webserver { include profiles::apache include profiles::base }
roles::webserver
class to a node. In a centralized installation, you would use either an External Node Classifier (ENC)
- to apply the class to the node, or you would use Hiera to define the role:
node 'webtest' { include roles::webserver }
How it works…
Breaking down the parts of the web server configuration into different profiles allows us to apply those parts independently. We created a base profile that we can expand to include all the resources we would like applied to all nodes. Our roles::webserver
class simply includes the base
and apache
classes.
There's more…
As we'll see in the next section, we can pass parameters to classes to alter how they work. In our roles::webserver
class, we can use the class instantiation syntax instead of include
, and override it with parameters
in the classes. For instance, to pass a parameter to the base
class, we would use:
class {'profiles::base':
parameter => 'newvalue'
}
where we previously used:
include profiles::base
Tip
In previous versions of this book, node and class inheritance were used to achieve a similar goal, code reuse. Node inheritance is deprecated in Puppet Version 3.7 and higher. Node and class inheritance should be avoided. Using roles and profiles achieves the same level of readability and is much easier to follow.
down the parts of the web server configuration into different profiles allows us to apply those parts independently. We created a base profile that we can expand to include all the resources we would like applied to all nodes. Our roles::webserver
class simply includes the base
and apache
classes.
There's more…
As we'll see in the next section, we can pass parameters to classes to alter how they work. In our roles::webserver
class, we can use the class instantiation syntax instead of include
, and override it with parameters
in the classes. For instance, to pass a parameter to the base
class, we would use:
class {'profiles::base':
parameter => 'newvalue'
}
where we previously used:
include profiles::base
Tip
In previous versions of this book, node and class inheritance were used to achieve a similar goal, code reuse. Node inheritance is deprecated in Puppet Version 3.7 and higher. Node and class inheritance should be avoided. Using roles and profiles achieves the same level of readability and is much easier to follow.
roles::webserver
class, we can use the class instantiation syntax instead of include
, and override it with parameters
in the classes. For instance, to pass a parameter to the base
class, we would use:
Tip
In previous versions of this book, node and class inheritance were used to achieve a similar goal, code reuse. Node inheritance is deprecated in Puppet Version 3.7 and higher. Node and class inheritance should be avoided. Using roles and profiles achieves the same level of readability and is much easier to follow.
Passing parameters to classes
Sometimes it's very useful to parameterize some aspect of a class. For example, you might need to manage different versions of a gem
package, and rather than making separate classes for each that differ only in the version number, you can pass in the version number as a parameter.
How to do it…
In this example, we'll create a definition that accepts parameters:
- Declare the parameter as a part of the class definition:
class eventmachine($version) { package { 'eventmachine': provider => gem, ensure => $version, } }
- Use the following syntax to include the class on a node:
class { 'eventmachine': version => '1.0.3', }
How it works…
The class definition class eventmachine($version) {
is just like a normal class definition except it specifies that the class takes one parameter: $version
. Inside the class, we've defined a package
resource:
package { 'eventmachine':
provider => gem,
ensure => $version,
}
This is a gem
package, and we're requesting to install version $version
.
Include the class on a node, instead of the usual include
syntax:
include eventmachine
On doing so, there will be a class
statement:
class { 'eventmachine':
version => '1.0.3',
}
This has the same effect but also sets a value for the parameter as version
.
There's more…
You can specify multiple parameters for a class as:
class mysql($package, $socket, $port) {
Then supply them in the same way:
class { 'mysql':
package => 'percona-server-server-5.5',
socket => '/var/run/mysqld/mysqld.sock',
port => '3306',
}
Specifying default values
You can also give default values for some of your parameters. When you include the class without setting a parameter, the default value will be used. For instance, if we created a mysql
class with three parameters, we could provide default values for any or all of the parameters as shown in the code snippet:
class mysql($package, $socket, $port='3306') {
or all:
class mysql(
package = percona-server-server-5.5",
socket = '/var/run/mysqld/mysqld.sock',
port = '3306') {
Defaults allow you to use a default value and override that default where you need it.
Unlike a definition, only one instance of a parameterized class can exist on a node. So where you need to have several different instances of the resource, use define
instead.
class eventmachine($version) { package { 'eventmachine': provider => gem, ensure => $version, } }
class { 'eventmachine': version => '1.0.3', }
How it works…
The class definition class eventmachine($version) {
is just like a normal class definition except it specifies that the class takes one parameter: $version
. Inside the class, we've defined a package
resource:
package { 'eventmachine':
provider => gem,
ensure => $version,
}
This is a gem
package, and we're requesting to install version $version
.
Include the class on a node, instead of the usual include
syntax:
include eventmachine
On doing so, there will be a class
statement:
class { 'eventmachine':
version => '1.0.3',
}
This has the same effect but also sets a value for the parameter as version
.
There's more…
You can specify multiple parameters for a class as:
class mysql($package, $socket, $port) {
Then supply them in the same way:
class { 'mysql':
package => 'percona-server-server-5.5',
socket => '/var/run/mysqld/mysqld.sock',
port => '3306',
}
Specifying default values
You can also give default values for some of your parameters. When you include the class without setting a parameter, the default value will be used. For instance, if we created a mysql
class with three parameters, we could provide default values for any or all of the parameters as shown in the code snippet:
class mysql($package, $socket, $port='3306') {
or all:
class mysql(
package = percona-server-server-5.5",
socket = '/var/run/mysqld/mysqld.sock',
port = '3306') {
Defaults allow you to use a default value and override that default where you need it.
Unlike a definition, only one instance of a parameterized class can exist on a node. So where you need to have several different instances of the resource, use define
instead.
class eventmachine($version) {
is just like a normal class definition except it specifies that the class takes one parameter: $version
. Inside the class, we've defined a package
resource:
gem
package, and we're requesting to install version $version
.
include
syntax:
class
statement:
same effect but also sets a value for the parameter as version
.
There's more…
You can specify multiple parameters for a class as:
class mysql($package, $socket, $port) {
Then supply them in the same way:
class { 'mysql':
package => 'percona-server-server-5.5',
socket => '/var/run/mysqld/mysqld.sock',
port => '3306',
}
Specifying default values
You can also give default values for some of your parameters. When you include the class without setting a parameter, the default value will be used. For instance, if we created a mysql
class with three parameters, we could provide default values for any or all of the parameters as shown in the code snippet:
class mysql($package, $socket, $port='3306') {
or all:
class mysql(
package = percona-server-server-5.5",
socket = '/var/run/mysqld/mysqld.sock',
port = '3306') {
Defaults allow you to use a default value and override that default where you need it.
Unlike a definition, only one instance of a parameterized class can exist on a node. So where you need to have several different instances of the resource, use define
instead.
Specifying default values
You can also give default values for some of your parameters. When you include the class without setting a parameter, the default value will be used. For instance, if we created a mysql
class with three parameters, we could provide default values for any or all of the parameters as shown in the code snippet:
class mysql($package, $socket, $port='3306') {
or all:
class mysql(
package = percona-server-server-5.5",
socket = '/var/run/mysqld/mysqld.sock',
port = '3306') {
Defaults allow you to use a default value and override that default where you need it.
Unlike a definition, only one instance of a parameterized class can exist on a node. So where you need to have several different instances of the resource, use define
instead.
parameters. When you include the class without setting a parameter, the default value will be used. For instance, if we created a mysql
class with three parameters, we could provide default values for any or all of the parameters as shown in the code snippet:
class mysql($package, $socket, $port='3306') {
or all:
class mysql(
package = percona-server-server-5.5",
socket = '/var/run/mysqld/mysqld.sock',
port = '3306') {
Defaults allow you to use a default value and override that default where you need it.
Unlike a definition, only one instance of a parameterized class can exist on a node. So where you need to have several different instances of the resource, use define
instead.
Passing parameters from Hiera
Like the parameter defaults
we introduced in the previous chapter, Hiera may be used to provide default values to classes. This feature requires Puppet Version 3 and higher.
Getting ready
Install and configure hiera
as we did in Chapter 2, Puppet Infrastructure. Create a global or common yaml
file; this will serve as the default for all values.
How to do it...
- Create a class with parameters and no default values:
t@mylaptop ~/puppet $ mkdir -p modules/mysql/manifests t@mylaptop ~/puppet $ vim modules/mysql/manifests/init.pp class mysql ( $port, $socket, $package ) { notify {"Port: $port Socket: $socket Package: $package": } }
- Update your common
.yaml
file in Hiera with the default values for themysql
class:--- mysql::port: 3306 mysql::package: 'mysql-server' mysql::socket: '/var/lib/mysql/mysql.sock'
Apply the class to a node, you can add the mysql class to your default node for now.
node default { class {'mysql': } }
- Run
puppet agent
and verify the output:[root@hiera-test ~]# puppet agent -t Info: Caching catalog for hiera-test.example.com Info: Applying configuration version '1411182251' Notice: Port: 3306 Socket: /var/lib/mysql/mysql.sock Package: mysql-server Notice: /Stage[main]/Mysql/Notify[Port: 3306 Socket: /var/lib/mysql/mysql.sock Package: mysql-server]/message: defined 'message' as 'Port: 3306 Socket: /var/lib/mysql/mysql.sock Package: mysql-server' Notice: Finished catalog run in 1.75 seconds
How it works...
When we instantiate the mysql
class in our manifest, we provided no values for any of the attributes. Puppet knows to look for a value in Hiera that matches class_name::parameter_name:
or ::class_name::parameter_name:
.
When Puppet finds a value, it uses it as the parameter for the class. If Puppet fails to find a value in Hiera and no default is defined, a catalog failure will result in the following command line:
Error: Could not retrieve catalog from remote server: Error 400 on SERVER: Must pass package to Class[Mysql] at /etc/puppet/environments/production/manifests/site.pp:6 on node hiera-test.example.com
This error indicates that Puppet would like a value for the parameter package
.
There's more...
You can define a Hiera hierarchy and supply different values for parameters based on facts. You could, for instance, have %{::osfamily}
in your hierarchy and have different yaml
files based on the osfamily
parameter (RedHat, Suse, and Debian).
hiera
as we did in
Chapter 2, Puppet Infrastructure. Create a global or common yaml
file; this will serve as the default for all values.
How to do it...
- Create a class with parameters and no default values:
t@mylaptop ~/puppet $ mkdir -p modules/mysql/manifests t@mylaptop ~/puppet $ vim modules/mysql/manifests/init.pp class mysql ( $port, $socket, $package ) { notify {"Port: $port Socket: $socket Package: $package": } }
- Update your common
.yaml
file in Hiera with the default values for themysql
class:--- mysql::port: 3306 mysql::package: 'mysql-server' mysql::socket: '/var/lib/mysql/mysql.sock'
Apply the class to a node, you can add the mysql class to your default node for now.
node default { class {'mysql': } }
- Run
puppet agent
and verify the output:[root@hiera-test ~]# puppet agent -t Info: Caching catalog for hiera-test.example.com Info: Applying configuration version '1411182251' Notice: Port: 3306 Socket: /var/lib/mysql/mysql.sock Package: mysql-server Notice: /Stage[main]/Mysql/Notify[Port: 3306 Socket: /var/lib/mysql/mysql.sock Package: mysql-server]/message: defined 'message' as 'Port: 3306 Socket: /var/lib/mysql/mysql.sock Package: mysql-server' Notice: Finished catalog run in 1.75 seconds
How it works...
When we instantiate the mysql
class in our manifest, we provided no values for any of the attributes. Puppet knows to look for a value in Hiera that matches class_name::parameter_name:
or ::class_name::parameter_name:
.
When Puppet finds a value, it uses it as the parameter for the class. If Puppet fails to find a value in Hiera and no default is defined, a catalog failure will result in the following command line:
Error: Could not retrieve catalog from remote server: Error 400 on SERVER: Must pass package to Class[Mysql] at /etc/puppet/environments/production/manifests/site.pp:6 on node hiera-test.example.com
This error indicates that Puppet would like a value for the parameter package
.
There's more...
You can define a Hiera hierarchy and supply different values for parameters based on facts. You could, for instance, have %{::osfamily}
in your hierarchy and have different yaml
files based on the osfamily
parameter (RedHat, Suse, and Debian).
t@mylaptop ~/puppet $ mkdir -p modules/mysql/manifests t@mylaptop ~/puppet $ vim modules/mysql/manifests/init.pp class mysql ( $port, $socket, $package ) { notify {"Port: $port Socket: $socket Package: $package": } }
.yaml
file in Hiera with the default values for the mysql
class:--- mysql::port: 3306 mysql::package: 'mysql-server' mysql::socket: '/var/lib/mysql/mysql.sock'
Apply the class to a node, you can add the mysql class to your default node for now.
node default { class {'mysql': } }
puppet agent
and verify the output:[root@hiera-test ~]# puppet agent -t Info: Caching catalog for hiera-test.example.com Info: Applying configuration version '1411182251' Notice: Port: 3306 Socket: /var/lib/mysql/mysql.sock Package: mysql-server Notice: /Stage[main]/Mysql/Notify[Port: 3306 Socket: /var/lib/mysql/mysql.sock Package: mysql-server]/message: defined 'message' as 'Port: 3306 Socket: /var/lib/mysql/mysql.sock Package: mysql-server' Notice: Finished catalog run in 1.75 seconds
How it works...
When we instantiate the mysql
class in our manifest, we provided no values for any of the attributes. Puppet knows to look for a value in Hiera that matches class_name::parameter_name:
or ::class_name::parameter_name:
.
When Puppet finds a value, it uses it as the parameter for the class. If Puppet fails to find a value in Hiera and no default is defined, a catalog failure will result in the following command line:
Error: Could not retrieve catalog from remote server: Error 400 on SERVER: Must pass package to Class[Mysql] at /etc/puppet/environments/production/manifests/site.pp:6 on node hiera-test.example.com
This error indicates that Puppet would like a value for the parameter package
.
There's more...
You can define a Hiera hierarchy and supply different values for parameters based on facts. You could, for instance, have %{::osfamily}
in your hierarchy and have different yaml
files based on the osfamily
parameter (RedHat, Suse, and Debian).
mysql
class in our manifest, we provided no values for any of the attributes. Puppet knows to look for a value in Hiera that matches class_name::parameter_name:
or ::class_name::parameter_name:
.
value, it uses it as the parameter for the class. If Puppet fails to find a value in Hiera and no default is defined, a catalog failure will result in the following command line:
Error: Could not retrieve catalog from remote server: Error 400 on SERVER: Must pass package to Class[Mysql] at /etc/puppet/environments/production/manifests/site.pp:6 on node hiera-test.example.com
This error indicates that Puppet would like a value for the parameter package
.
There's more...
You can define a Hiera hierarchy and supply different values for parameters based on facts. You could, for instance, have %{::osfamily}
in your hierarchy and have different yaml
files based on the osfamily
parameter (RedHat, Suse, and Debian).
%{::osfamily}
in your hierarchy and have different yaml
files based on the osfamily
parameter (RedHat, Suse, and Debian).
Writing reusable, cross-platform manifests
Every system administrator dreams of a unified, homogeneous infrastructure of identical machines all running the same version of the same OS. As in other areas of life, however, the reality is often messy and doesn't conform to the plan.
You are probably responsible for a bunch of assorted servers of varying age and architecture running different kernels from different OS distributions, often scattered across different data centers and ISPs.
This situation should strike terror into the hearts of the sysadmins of the SSH in a for
loop persuasion, because executing the same commands on every server can have different, unpredictable, and even dangerous results.
We should certainly strive to bring older servers up to date and get working as far as possible on a single reference platform to make administration simpler, cheaper, and more reliable. But until we get there, Puppet makes coping with heterogeneous environments slightly easier.
How to do it…
Here are some examples of how to make your manifests more portable:
- Where you need to apply the same manifest to servers with different OS distributions, the main differences will probably be the names of packages and services, and the location of config files. Try to capture all these differences into a single class by using selectors to set global variables:
$ssh_service = $::operatingsystem? { /Ubuntu|Debian/ => 'ssh', default => 'sshd', }
You needn't worry about the differences in any other part of the manifest; when you refer to something, use the variable with confidence that it will point to the right thing in each environment:
service { $ssh_service: ensure => running, }
- Often we need to cope with mixed architectures; this can affect the paths to shared libraries, and also may require different versions of packages. Again, try to encapsulate all the required settings in a single architecture class that sets global variables:
$libdir = $::architecture ? { /amd64|x86_64/ => '/usr/lib64', default => '/usr/lib', }
Then you can use these wherever an architecture-dependent value is required in your manifests or even in templates:
; php.ini [PHP] ; Directory in which the loadable extensions (modules) reside. extension_dir = <%= @libdir %>/php/modules
How it works...
The advantage of this approach (which could be called top-down) is that you only need to make your choices once. The alternative, bottom-up approach would be to have a selector or case
statement everywhere a setting is used:
service { $::operatingsystem? {
/Ubuntu|Debian/ => 'ssh', default => 'sshd' }: ensure => running,
}
This not only results in lots of duplication, but makes the code harder to read. And when a new operating system is added to the mix, you'll need to make changes throughout the whole manifest, instead of just in one place.
There's more…
If you are writing a module for public distribution (for example, on Puppet Forge), making your module as cross-platform as possible will make it more valuable to the community. As far as you can, test it on many different distributions, platforms, and architectures, and add the appropriate variables so that it works everywhere.
If you use a public module and adapt it to your own environment, consider updating the public version with your changes if you think they might be helpful to other people.
Even if you are not thinking of publishing a module, bear in mind that it may be in production use for a long time and may have to adapt to many changes in the environment. If it's designed to cope with this from the start, it'll make life easier for you or whoever ends up maintaining your code.
"Always code as if the guy who ends up maintaining your code will be a violent psychopath who knows where you live." | ||
--Dave Carhart |
See also
- The Using public modules recipe in Chapter 7, Managing Applications
- The Configuring Hiera recipe in Chapter 2, Puppet Infrastructure
$ssh_service = $::operatingsystem? { /Ubuntu|Debian/ => 'ssh', default => 'sshd', }
You needn't worry about the differences in any other part of the manifest; when you refer to something, use the variable with confidence that it will point to the right thing in each environment:
service { $ssh_service: ensure => running, }
$libdir = $::architecture ? { /amd64|x86_64/ => '/usr/lib64', default => '/usr/lib', }
these wherever an architecture-dependent value is required in your manifests or even in templates:
; php.ini [PHP] ; Directory in which the loadable extensions (modules) reside. extension_dir = <%= @libdir %>/php/modules
How it works...
The advantage of this approach (which could be called top-down) is that you only need to make your choices once. The alternative, bottom-up approach would be to have a selector or case
statement everywhere a setting is used:
service { $::operatingsystem? {
/Ubuntu|Debian/ => 'ssh', default => 'sshd' }: ensure => running,
}
This not only results in lots of duplication, but makes the code harder to read. And when a new operating system is added to the mix, you'll need to make changes throughout the whole manifest, instead of just in one place.
There's more…
If you are writing a module for public distribution (for example, on Puppet Forge), making your module as cross-platform as possible will make it more valuable to the community. As far as you can, test it on many different distributions, platforms, and architectures, and add the appropriate variables so that it works everywhere.
If you use a public module and adapt it to your own environment, consider updating the public version with your changes if you think they might be helpful to other people.
Even if you are not thinking of publishing a module, bear in mind that it may be in production use for a long time and may have to adapt to many changes in the environment. If it's designed to cope with this from the start, it'll make life easier for you or whoever ends up maintaining your code.
"Always code as if the guy who ends up maintaining your code will be a violent psychopath who knows where you live." | ||
--Dave Carhart |
See also
- The Using public modules recipe in Chapter 7, Managing Applications
- The Configuring Hiera recipe in Chapter 2, Puppet Infrastructure
case
statement everywhere a setting is used:
duplication, but makes the code harder to read. And when a new operating system is added to the mix, you'll need to make changes throughout the whole manifest, instead of just in one place.
There's more…
If you are writing a module for public distribution (for example, on Puppet Forge), making your module as cross-platform as possible will make it more valuable to the community. As far as you can, test it on many different distributions, platforms, and architectures, and add the appropriate variables so that it works everywhere.
If you use a public module and adapt it to your own environment, consider updating the public version with your changes if you think they might be helpful to other people.
Even if you are not thinking of publishing a module, bear in mind that it may be in production use for a long time and may have to adapt to many changes in the environment. If it's designed to cope with this from the start, it'll make life easier for you or whoever ends up maintaining your code.
"Always code as if the guy who ends up maintaining your code will be a violent psychopath who knows where you live." | ||
--Dave Carhart |
See also
- The Using public modules recipe in Chapter 7, Managing Applications
- The Configuring Hiera recipe in Chapter 2, Puppet Infrastructure
public distribution (for example, on Puppet Forge), making your module as cross-platform as possible will make it more valuable to the community. As far as you can, test it on many different distributions, platforms, and architectures, and add the appropriate variables so that it works everywhere.
If you use a public module and adapt it to your own environment, consider updating the public version with your changes if you think they might be helpful to other people.
Even if you are not thinking of publishing a module, bear in mind that it may be in production use for a long time and may have to adapt to many changes in the environment. If it's designed to cope with this from the start, it'll make life easier for you or whoever ends up maintaining your code.
"Always code as if the guy who ends up maintaining your code will be a violent psychopath who knows where you live." | ||
--Dave Carhart |
See also
- The Using public modules recipe in Chapter 7, Managing Applications
- The Configuring Hiera recipe in Chapter 2, Puppet Infrastructure
- Chapter 7, Managing Applications
- The Configuring Hiera recipe in Chapter 2, Puppet Infrastructure
Getting information about the environment
Often in a Puppet manifest, you need to know some local information about the machine you're on. Facter is the tool that accompanies Puppet to provide a standard way of getting information (facts) from the environment about things such as these:
- Operating system
- Memory size
- Architecture
- Processor count
To see a complete list of the facts available on your system, run:
$ sudo facter
architecture => amd64
augeasversion => 0.10.0
domain => compute-1.internal
ec2_ami_id => ami-137bcf7a
ec2_ami_launch_index => 0
Note
While it can be handy to get this information from the command line, the real power of Facter lies in being able to access these facts in your Puppet manifests.
Some modules define their own facts; to see any facts that have been defined locally, add the -p (pluginsync)
option to facter as follows:
$ sudo facter -p
How to do it…
Here's an example of using Facter facts in a manifest:
- Reference a Facter fact in your manifest like any other variable. Facts are global variables in Puppet, so they should be prefixed with a double colon (
::
), as in the following code snippet:notify { "This is $::operatingsystem version $::operatingsystemrelease, on $::architecture architecture, kernel version $::kernelversion": }
- When Puppet runs, it will fill in the appropriate values for the current node:
[root@hiera-test ~]# puppet agent -t ... Info: Applying configuration version '1411275985'Notice: This is RedHat version 6.5, on x86_64 architecture, kernel version 2.6.32 ... Notice: Finished catalog run in 0.40 seconds
How it works…
Facter provides a standard way for manifests to get information about the nodes to which they are applied. When you refer to a fact in a manifest, Puppet will query Facter to get the current value and insert it into the manifest. Facter facts are top scope variables.
Tip
Always refer to facts with leading double colons to ensure that you are using the fact and not a local variable:
$::hostname
NOT $hostname
There's more…
You can also use facts in ERB templates. For example, you might want to insert the node's hostname into a file, or change a configuration setting for an application based on the memory size of the node. When you use fact names in templates, remember that they don't need a dollar sign because this is Ruby, not Puppet:
$KLogPath <%= case @kernelversion when '2.6.31' then
'/var/run/rsyslog/kmsg' else '/proc/kmsg' end %>
When referring to facts, use the @
syntax. Variables that are defined at the same scope as the function call to template can also be referenced with the @
syntax. Out of scope variables should use the scope
function. For example, to reference the mysql::port
variable we defined earlier in the mysql
modules, use the following:
MySQL Port = <%= scope['::mysql::port'] %>
Applying this template results in the following file:
[root@hiera-test ~]# puppet agent -t
...
Info: Caching catalog for hiera-test.example.com
Notice: /Stage[main]/Erb/File[/tmp/template-test]/ensure: defined content as '{md5}96edacaf9747093f73084252c7ca7e67'
Notice: Finished catalog run in 0.41 seconds [root@hiera-test ~]# cat /tmp/template-test
MySQL Port = 3306
See also
- The Creating custom facts recipe in Chapter 9, External Tools and the Puppet Ecosystem
::
), as in the following code snippet:notify { "This is $::operatingsystem version $::operatingsystemrelease, on $::architecture architecture, kernel version $::kernelversion": }
[root@hiera-test ~]# puppet agent -t ... Info: Applying configuration version '1411275985'Notice: This is RedHat version 6.5, on x86_64 architecture, kernel version 2.6.32 ... Notice: Finished catalog run in 0.40 seconds
How it works…
Facter provides a standard way for manifests to get information about the nodes to which they are applied. When you refer to a fact in a manifest, Puppet will query Facter to get the current value and insert it into the manifest. Facter facts are top scope variables.
Tip
Always refer to facts with leading double colons to ensure that you are using the fact and not a local variable:
$::hostname
NOT $hostname
There's more…
You can also use facts in ERB templates. For example, you might want to insert the node's hostname into a file, or change a configuration setting for an application based on the memory size of the node. When you use fact names in templates, remember that they don't need a dollar sign because this is Ruby, not Puppet:
$KLogPath <%= case @kernelversion when '2.6.31' then
'/var/run/rsyslog/kmsg' else '/proc/kmsg' end %>
When referring to facts, use the @
syntax. Variables that are defined at the same scope as the function call to template can also be referenced with the @
syntax. Out of scope variables should use the scope
function. For example, to reference the mysql::port
variable we defined earlier in the mysql
modules, use the following:
MySQL Port = <%= scope['::mysql::port'] %>
Applying this template results in the following file:
[root@hiera-test ~]# puppet agent -t
...
Info: Caching catalog for hiera-test.example.com
Notice: /Stage[main]/Erb/File[/tmp/template-test]/ensure: defined content as '{md5}96edacaf9747093f73084252c7ca7e67'
Notice: Finished catalog run in 0.41 seconds [root@hiera-test ~]# cat /tmp/template-test
MySQL Port = 3306
See also
- The Creating custom facts recipe in Chapter 9, External Tools and the Puppet Ecosystem
way for manifests to get information about the nodes to which they are applied. When you refer to a fact in a manifest, Puppet will query Facter to get the current value and insert it into the manifest. Facter facts are top scope variables.
Tip
Always refer to facts with leading double colons to ensure that you are using the fact and not a local variable:
$::hostname
NOT $hostname
There's more…
You can also use facts in ERB templates. For example, you might want to insert the node's hostname into a file, or change a configuration setting for an application based on the memory size of the node. When you use fact names in templates, remember that they don't need a dollar sign because this is Ruby, not Puppet:
$KLogPath <%= case @kernelversion when '2.6.31' then
'/var/run/rsyslog/kmsg' else '/proc/kmsg' end %>
When referring to facts, use the @
syntax. Variables that are defined at the same scope as the function call to template can also be referenced with the @
syntax. Out of scope variables should use the scope
function. For example, to reference the mysql::port
variable we defined earlier in the mysql
modules, use the following:
MySQL Port = <%= scope['::mysql::port'] %>
Applying this template results in the following file:
[root@hiera-test ~]# puppet agent -t
...
Info: Caching catalog for hiera-test.example.com
Notice: /Stage[main]/Erb/File[/tmp/template-test]/ensure: defined content as '{md5}96edacaf9747093f73084252c7ca7e67'
Notice: Finished catalog run in 0.41 seconds [root@hiera-test ~]# cat /tmp/template-test
MySQL Port = 3306
See also
- The Creating custom facts recipe in Chapter 9, External Tools and the Puppet Ecosystem
ERB templates. For example, you might want to insert the node's hostname into a file, or change a configuration setting for an application based on the memory size of the node. When you use fact names in templates, remember that they don't need a dollar sign because this is Ruby, not Puppet:
$KLogPath <%= case @kernelversion when '2.6.31' then
'/var/run/rsyslog/kmsg' else '/proc/kmsg' end %>
When referring to facts, use the @
syntax. Variables that are defined at the same scope as the function call to template can also be referenced with the @
syntax. Out of scope variables should use the scope
function. For example, to reference the mysql::port
variable we defined earlier in the mysql
modules, use the following:
MySQL Port = <%= scope['::mysql::port'] %>
Applying this template results in the following file:
[root@hiera-test ~]# puppet agent -t
...
Info: Caching catalog for hiera-test.example.com
Notice: /Stage[main]/Erb/File[/tmp/template-test]/ensure: defined content as '{md5}96edacaf9747093f73084252c7ca7e67'
Notice: Finished catalog run in 0.41 seconds [root@hiera-test ~]# cat /tmp/template-test
MySQL Port = 3306
See also
- The Creating custom facts recipe in Chapter 9, External Tools and the Puppet Ecosystem
- Chapter 9, External Tools and the Puppet Ecosystem
Importing dynamic information
Even though some system administrators like to wall themselves off from the rest of the office using piles of old printers, we all need to exchange information with other departments from time to time. For example, you may want to insert data into your Puppet manifests that is derived from some outside source. The generate function is ideal for this. Functions are executed on the machine compiling the catalog (the master for centralized deployments); an example like that shown here will only work in a masterless configuration.
Getting ready
Follow these steps to prepare to run the example:
- Create the script
/usr/local/bin/message.rb
with the following contents:#!/usr/bin/env ruby puts "This runs on the master if you are centralized"
- Make the script executable:
$ sudo chmod a+x /usr/local/bin/message.rb
How to do it…
This example calls the external script we created previously and gets its output:
- Create a
message.pp
manifest containing the following:$message = generate('/usr/local/bin/message.rb') notify { $message: }
- Run Puppet:
$ puppet apply message.pp ... Notice: /Stage[main]/Main/Notify[This runs on the master if you are centralized ]/message: defined 'message' as 'This runs on the master if you are centralized
How it works…
The generate
function runs the specified script or program and returns the result, in this case, a cheerful message from Ruby.
This isn't terribly useful as it stands but you get the idea. Anything a script can do, print, fetch, or calculate, for example, the results of a database query, can be brought into your manifest using generate
. You can also, of course, run standard UNIX utilities such as cat
and grep
.
There's more…
If you need to pass arguments to the executable called by generate, add them as extra arguments to the function call:
$message = generate('/bin/cat', '/etc/motd')
Puppet will try to protect you from malicious shell calls by restricting the characters you can use in a call to generate, so shell pipes and redirection aren't allowed, for example. The simplest and safest thing to do is to put all your logic into a script and then call that script.
See also
- The Creating custom facts recipe in Chapter 9, External Tools and the Puppet Ecosystem
- The Configuring Hiera recipe in Chapter 2, Puppet Infrastructure
/usr/local/bin/message.rb
with the following contents:#!/usr/bin/env ruby puts "This runs on the master if you are centralized"
$ sudo chmod a+x /usr/local/bin/message.rb
How to do it…
This example calls the external script we created previously and gets its output:
- Create a
message.pp
manifest containing the following:$message = generate('/usr/local/bin/message.rb') notify { $message: }
- Run Puppet:
$ puppet apply message.pp ... Notice: /Stage[main]/Main/Notify[This runs on the master if you are centralized ]/message: defined 'message' as 'This runs on the master if you are centralized
How it works…
The generate
function runs the specified script or program and returns the result, in this case, a cheerful message from Ruby.
This isn't terribly useful as it stands but you get the idea. Anything a script can do, print, fetch, or calculate, for example, the results of a database query, can be brought into your manifest using generate
. You can also, of course, run standard UNIX utilities such as cat
and grep
.
There's more…
If you need to pass arguments to the executable called by generate, add them as extra arguments to the function call:
$message = generate('/bin/cat', '/etc/motd')
Puppet will try to protect you from malicious shell calls by restricting the characters you can use in a call to generate, so shell pipes and redirection aren't allowed, for example. The simplest and safest thing to do is to put all your logic into a script and then call that script.
See also
- The Creating custom facts recipe in Chapter 9, External Tools and the Puppet Ecosystem
- The Configuring Hiera recipe in Chapter 2, Puppet Infrastructure
message.pp
manifest containing the following:$message = generate('/usr/local/bin/message.rb') notify { $message: }
$ puppet apply message.pp ... Notice: /Stage[main]/Main/Notify[This runs on the master if you are centralized ]/message: defined 'message' as 'This runs on the master if you are centralized
How it works…
The generate
function runs the specified script or program and returns the result, in this case, a cheerful message from Ruby.
This isn't terribly useful as it stands but you get the idea. Anything a script can do, print, fetch, or calculate, for example, the results of a database query, can be brought into your manifest using generate
. You can also, of course, run standard UNIX utilities such as cat
and grep
.
There's more…
If you need to pass arguments to the executable called by generate, add them as extra arguments to the function call:
$message = generate('/bin/cat', '/etc/motd')
Puppet will try to protect you from malicious shell calls by restricting the characters you can use in a call to generate, so shell pipes and redirection aren't allowed, for example. The simplest and safest thing to do is to put all your logic into a script and then call that script.
See also
- The Creating custom facts recipe in Chapter 9, External Tools and the Puppet Ecosystem
- The Configuring Hiera recipe in Chapter 2, Puppet Infrastructure
generate
function runs the specified script or program and returns the result, in this case, a cheerful message from Ruby.
generate
. You can also, of course, run standard UNIX utilities such as cat
and grep
.
There's more…
If you need to pass arguments to the executable called by generate, add them as extra arguments to the function call:
$message = generate('/bin/cat', '/etc/motd')
Puppet will try to protect you from malicious shell calls by restricting the characters you can use in a call to generate, so shell pipes and redirection aren't allowed, for example. The simplest and safest thing to do is to put all your logic into a script and then call that script.
See also
- The Creating custom facts recipe in Chapter 9, External Tools and the Puppet Ecosystem
- The Configuring Hiera recipe in Chapter 2, Puppet Infrastructure
arguments to the executable called by generate, add them as extra arguments to the function call:
$message = generate('/bin/cat', '/etc/motd')
Puppet will try to protect you from malicious shell calls by restricting the characters you can use in a call to generate, so shell pipes and redirection aren't allowed, for example. The simplest and safest thing to do is to put all your logic into a script and then call that script.
See also
- The Creating custom facts recipe in Chapter 9, External Tools and the Puppet Ecosystem
- The Configuring Hiera recipe in Chapter 2, Puppet Infrastructure
- Chapter 9, External Tools and the Puppet Ecosystem
- The Configuring Hiera recipe in Chapter 2, Puppet Infrastructure
Passing arguments to shell commands
If you want to insert values into a command line (to be run by an exec
resource, for example), they often need to be quoted, especially if they contain spaces. The shellquote
function will take any number of arguments, including arrays, and quote each of the arguments and return them all as a space-separated string that you can pass to commands.
In this example, we would like to set up an exec
resource that will rename a file; but both the source and the target name contain spaces, so they need to be correctly quoted in the command line.
How to do it…
Here's an example of using the shellquote
function:
- Create a
shellquote.pp
manifest with the following command:$source = 'Hello Jerry' $target = 'Hello... Newman' $argstring = shellquote($source, $target) $command = "/bin/mv ${argstring}" notify { $command: }
- Run Puppet:
$ puppet apply shellquote.pp ... Notice: /bin/mv "Hello Jerry" "Hello... Newman" Notice: /Stage[main]/Main/Notify[/bin/mv "Hello Jerry" "Hello... Newman"]/message: defined 'message' as '/bin/mv "Hello Jerry" "Hello... Newman"'
How it works…
First we define the $source
and $target
variables, which are the two filenames we want to use in the command line:
$source = 'Hello Jerry'
$target = 'Hello... Newman'
Then we call shellquote
to concatenate these variables into a quoted, space-separated string as follows:
$argstring = shellquote($source, $target)
Then we put together the final command line:
$command = "/bin/mv ${argstring}"
The result will be:
/bin/mv "Hello Jerry" "Hello... Newman"
This command line can now be run with an exec resource. What would happen if we didn't use shellquote
?
$source = 'Hello Jerry'
$target = 'Hello... Newman'
$command = "/bin/mv ${source} ${target}"
notify { $command: }
Notice: /bin/mv Hello Jerry Hello... Newman
This won't work because mv
expects space-separated arguments, so it will interpret this as a request to move three files Hello
, Jerry
, and Hello...
into a directory named Newman
, which probably isn't what we want.
shellquote
function:
shellquote.pp
manifest with the following command:$source = 'Hello Jerry' $target = 'Hello... Newman' $argstring = shellquote($source, $target) $command = "/bin/mv ${argstring}" notify { $command: }
$ puppet apply shellquote.pp ... Notice: /bin/mv "Hello Jerry" "Hello... Newman" Notice: /Stage[main]/Main/Notify[/bin/mv "Hello Jerry" "Hello... Newman"]/message: defined 'message' as '/bin/mv "Hello Jerry" "Hello... Newman"'
How it works…
First we define the $source
and $target
variables, which are the two filenames we want to use in the command line:
$source = 'Hello Jerry'
$target = 'Hello... Newman'
Then we call shellquote
to concatenate these variables into a quoted, space-separated string as follows:
$argstring = shellquote($source, $target)
Then we put together the final command line:
$command = "/bin/mv ${argstring}"
The result will be:
/bin/mv "Hello Jerry" "Hello... Newman"
This command line can now be run with an exec resource. What would happen if we didn't use shellquote
?
$source = 'Hello Jerry'
$target = 'Hello... Newman'
$command = "/bin/mv ${source} ${target}"
notify { $command: }
Notice: /bin/mv Hello Jerry Hello... Newman
This won't work because mv
expects space-separated arguments, so it will interpret this as a request to move three files Hello
, Jerry
, and Hello...
into a directory named Newman
, which probably isn't what we want.
$source
and $target
variables, which are the two filenames we want to use in the command line:
shellquote
to
concatenate these variables into a quoted, space-separated string as follows:
$argstring = shellquote($source, $target)
Then we put together the final command line:
$command = "/bin/mv ${argstring}"
The result will be:
/bin/mv "Hello Jerry" "Hello... Newman"
This command line can now be run with an exec resource. What would happen if we didn't use shellquote
?
$source = 'Hello Jerry'
$target = 'Hello... Newman'
$command = "/bin/mv ${source} ${target}"
notify { $command: }
Notice: /bin/mv Hello Jerry Hello... Newman
This won't work because mv
expects space-separated arguments, so it will interpret this as a request to move three files Hello
, Jerry
, and Hello...
into a directory named Newman
, which probably isn't what we want.