Managing Infrastructure with Puppet - James Loope [5]
package { 'ntp': ensure => '1:4.2.6.p2+dfsg-1ubuntu5' }
file { '/etc/ntp.conf':
mode => 640
owner => root,
group => root,
source => '/mnt/nfs/configs/ntp.conf',
require => Package[ntp],
}
service { "ntp":
ensure => running,
enable => true,
pattern => 'ntpd',
subscribe => [Package["ntp"], File["/etc/ntp.conf"]],
}
* * *
Warning
Make sure to test the behavior of the service you are managing. It may be innocuous to restart ntp when the config changes, but it’s an ugly mess when you push a change that, unforeseen, restarts your production database.
* * *
Exec and Notify
Subscribing a service to a file is very convenient, but what if we need to do something more explicit when a file resource changes? I’ll use a postfix transport map as an example. When this file is updated, I want to run postmap to compile the transport.db file.
In this example, I’ve specified an exec resource. This is the “brute force” resource in Puppet. You can use it to execute commands and shell scripts of your choosing, but there is an important caveat. The command must be idempotent. This means that your system configuration must be able to cope with having the command run over and over again. An exec type resource will generally be run on every Puppet run. The following example specifies that the command should not run unless the subscription to the /etc/transport file is changed and a refresh is triggered. This is accomplished with the refreshonly parameter. Any exec can be refreshed either by a subscription or a notification. Notification works in the reverse of a subscription:
file { "/etc/postfix/transport":
mode => 640
owner => root,
group => postfix,
source => '/mnt/postfix/configs/transport',
}
exec { "postmap /etc/postfix/transport":
subscribe => File["/etc/postfix/transport"],
refreshonly => true,
}
Here we have the file resource notifying the exec of a change. Note that notify implies the behavior that would be seen with a before parameter and subscribe implies the ordering of a require parameter. In this example, the file will be created before the exec is run, and in the former example, the exec requires that the file be run first:
file { "/etc/postfix/transport":
mode => 640
owner => root,
group => postfix,
source => '/mnt/postfix/configs/transport',
notify => Exec["postmap /etc/postfix/transport"],
}
exec { "postmap /etc/postfix/transport":
refreshonly => true,
}
There are a couple of scenarios where you might want to use an exec, but only when some other condition requires it. Exec can be used to generate a file; for example, if I wish to fetch a configuration file that I’ve published on a web server.
In the first example, Puppet understands that the result of the exec is to create the file listed in the creates parameter. This exec will only be run if that file doesn’t exist. The second example has the same effect, but it does so using a more customizable condition. The command will only be run if the exit status of the command in the onlyif parameter is zero. Nonzero status will cause the exec to be skipped:
exec { 'curl http://example.com/config/my.conf -o "/etc/myapp/my.conf"':
creates => "/etc/myapp/my.conf",
}
exec { 'curl http://example.com/config/my.conf -o "/etc/myapp/my.conf"':
onlyif => "test ! -e /etc/myapp/my.conf",
}
* * *
Note
Exec is very powerful and it has plenty of appropriate uses. It is not advisable, however, to treat every problem as a potential nail for this particular hammer. An exec is difficult to make platform-agnostic, and it generally solves only one particular problem. In a case where no existing Puppet abstraction does what you need, it might be more useful to dig around in the community modules for an adaptable function. You could even write your own.
* * *
Facts, Conditional Statements, and Logging
It’s time to begin talking about what Puppet is doing when it executes these definitions. Each type has a set of “provider” backends