While the other resource types we've seen so far (file
, package
, service
, user
, ssh_authorized_key
, and cron
) have modeled some concrete piece of state on the node, such as a file, the exec
resource is a little different. An exec
allows you to run any arbitrary command on the node. This might create or modify the state, or it might not; anything you can run from the command line, you can run via an exec
resource.
The most common use for an
exec
resource is to simulate manual interaction on the command line. For example, some older software is not packaged for modern operating systems, and need to be compiled and installed from source, which requires you to run certain commands. The authors of some software have also not realized, or don't care, that users may be trying to install their product automatically, and have install scripts which prompt for user input. This can require the use of exec
resources to work around the problem.
The following example shows an exec
resource for building and installing an imaginary piece of software (exec.pp
):
exec { 'install-cat-picture-generator': cwd => '/tmp/cat-picture-generator', command => '/tmp/cat-picture/generator/configure && /usr/bin/make install', creates => '/usr/local/bin/cat-picture-generator', }
The title of the resource can be anything you like, though as usual with Puppet resources, it must be unique. I tend to name exec
resources after the problem they're trying to solve, as in this example.
The cwd
attribute sets the working directory where the command will be run (current working directory). When installing software, this is generally the software source directory.
The command
attribute gives the command to run. This must be the full path to the command, but you can chain several commands together using the &&
shell operator. This executes the next command only if the previous one succeeded. So in the example, if the configure
command completes successfully, Puppet will go on to run make install
command; otherwise, it will stop with an error.
The creates
attribute specifies a file which should exist after the command has been run. If this file is present, Puppet will not run the command again. This is very useful because, without a creates
attribute, an exec
resource will run every time Puppet runs, which is generally not what you want. The creates
attribute tells Puppet, in effect, Run the
exec
only if this file doesn't exist.
Let's see how this works, imagining that this exec
is being run for the first time. We assume that the /tmp/cat-picture
directory exists and contains the source of the cat-picture-generator
application:
creates
attribute and sees that the /usr/local/bin/cat-picture-generator
file is not present, therefore the exec
must be run. /tmp/cat-picture-generator/configure && /usr/bin/make install
command. As a side-effect of this command, the /usr/local/bin/cat-picture-generator
file is created.creates
attribute. This time, /usr/local/bin/cat-picture-generator
exists, so Puppet does nothing. This exec
will never be applied again, so long as the file specified in the creates
attribute exists. You can test this by deleting the file and applying Puppet again. The exec
will be triggered, and the file recreated.
Note that building and installing software from source is not a recommended practice for production systems. It's better to build the software on a dedicated build server (perhaps using Puppet code similar to this example), create a system package for it, and then use Puppet to install that package on the production nodes.
If you don't specify a user
attribute for the exec
resource, Puppet will run the command as the root
user. This is often appropriate for installing system software or making changes to the system configuration. However, if you need the command to run as a particular user, specify the user
attribute, as in the following example (exec_user.pp
):
exec { 'say-hello': command => '/bin/echo Hello, this is `whoami` >/tmp/hello-vagrant.txt', user => 'vagrant', creates => '/tmp/hello-vagrant.txt', }
This will run the specified command as the vagrant
user. The whoami
command returns the name of the user running it, so when you apply this manifest, the file /tmp/hello-vagrant.txt
will be created with the following contents:
Hello, this is vagrant
As with the earlier example, the creates
attribute prevents Puppet from running this command more than once.
Suppose you only want an exec
resource to be applied under certain conditions. For example, a command which processes incoming data files only needs to run if there are data files waiting to be processed. In this case, it's no good adding a creates
attribute; we want the existence of a certain file to trigger the exec
, not prevent it.
The onlyif
attribute is a good way to solve this problem. It specifies a command for Puppet to run, and the exit status from this command determines whether or not the exec
will be applied. On UNIX-like systems, commands generally return an exit status of zero to indicate success, and a non-zero value for failure. The following example shows how to use onlyif
in this way (exec_onlyif.pp
):
exec { 'process-incoming-cat-pictures': command => '/usr/local/bin/cat-picture-generator --import /tmp/incoming/*', onlyif => '/bin/ls /tmp/incoming/*', }
The exact command
isn't important here, but let's assume it's something that we would only want to run if there are any files in the /tmp/incoming
directory.
The onlyif
attribute specifies the check
command which Puppet should run first, to determine whether or not the exec
needs to be applied. If there is nothing in the /tmp/incoming
directory, then ls /tmp/incoming/*
will return a non-zero exit status. Puppet interprets this as a failure, so does not apply the exec
.
On the other hand, if there are files in the /tmp/incoming
directory, the ls
command will return a successful exit status. This tells Puppet that the exec
must be applied, so it proceeds to run the /usr/local/bin/cat-picture-generator
command (and we can assume this command deletes the incoming files after processing).
You can think of the onlyif
attribute as telling Puppet to Run the
exec
only if this command succeeds.
The unless
attribute is exactly the same as onlyif
, but with the opposite sense. If you specify a command to the unless
attribute, the exec
will always be run unless the command returns a zero exit status. You can think of unless
as telling Puppet to Run the
exec
unless this command succeeds.
When you run Puppet, if you see an exec
running every time which shouldn't be, check whether it specifies a creates
, unless
, or onlyif
attribute. If so, the creates
attribute may be looking for the wrong file, or the unless
or onlyif
check command may not be returning what you expect. You can see what command is being run, and what output it generates, by running sudo puppet apply
with the -d
(debug) flag:
sudo puppet apply -d exec_onlyif.pp
Debug: Exec[process-incoming-cat-pictures](provider=posix): Executing check '/bin/ls /tmp/incoming/*'
Debug: Executing: '/bin/ls /tmp/incoming/*'
Debug: /Stage[main]/Main/Exec[process-incoming-cat-pictures]/onlyif: /tmp/incoming/foo
It's quite common to use exec
resources for one-off commands, such as rebuilding a database, or setting a system tunable parameter. These generally only need to be triggered once, when a package is installed, or occasionally, when a config file is updated. If an exec
needs to run only when some other Puppet resource is changed, we can use the refreshonly
attribute to do this.
If refreshonly
is true
, the exec
will never be applied unless another resource triggers it with notify
. In the following example, Puppet manages the /etc/aliases
file (which maps local usernames to e-mail addresses), and a change to this file triggers the execution of the newaliases
command, which rebuilds the system alias database (exec_refreshonly.pp
):
file { '/etc/aliases': content => 'root: [email protected]', notify => Exec['newaliases'], } exec { 'newaliases': command => '/usr/bin/newaliases', refreshonly => true, }
When this manifest is applied for the first time, the /etc/aliases
resource causes a change to the file's contents, so Puppet sends a notify
message to the exec
resource. This causes the newaliases
command to be run. If you apply the manifest again, you will see that the aliases
file is not changed, so the exec
is not run.
While the refreshonly
attribute is occasionally extremely useful, overuse of it can make your Puppet manifests hard to understand and debug, and it can also be rather fragile. Felix Frank makes this point in a blog post:
Friends don't let friends use
refreshonly
:
With the
exec
resource type considered the last ditch, its
refreshonly
parameter should be seen as especially outrageous. To make an
exec
resource fit into Puppet's model better, you should use [the
creates
,
onlyif
, or
unless
] parameters instead.
(http://ffrank.github.io/misc/2015/05/26/friends-don't-let-friends-use-refreshonly/)
Note that you don't need to use the refreshonly
attribute in order to make the exec
notifiable by other resources. Any resource can notify an exec
in order to make it run; however, if you don't want it to run unless it's notified, use refreshonly
.
(By the way, if you actually want to manage mail aliases on a node, use Puppet's built-in mailalias
resource. The previous example is just to demonstrate the use of refreshonly
.)
When Puppet runs shell commands via an
exec
resource, the output is normally hidden from us. However, if the command doesn't seem to be working properly, it can be very useful to see what output it produced, as this usually tells us why it didn't work.
The logoutput
attribute determines whether Puppet will log the output of the exec
command along with the usual informative Puppet output. It can take three values: true
, false
, or on_failure
.
If logoutput
is set to on_failure
(which is the default), Puppet will only log command output when the command fails (that is, returns a non-zero exit status). If you never want to see command output, set it to false
.
Sometimes, however, the command returns a successful exit status, but does not appear to do anything. Setting logoutput
to true
will force Puppet to log the command output regardless of the exit status, which should help you figure out what's going on.
Sometimes commands can take a long time to run, or never terminate at all. By default, Puppet allows an exec
command to run for 300 seconds, at which point Puppet terminates it if it has not finished. If you need to allow a little longer for the command to complete, you can use the timeout
attribute to set this. The value is the maximum execution time for the command in seconds.
Setting a timeout
value of 0
disables the automatic timeout altogether, and allows the command to run forever. This should be a last resort, as a command which blocks or hangs could stop Puppet's automatic runs altogether if no timeout is set. To find a suitable value for timeout
, try running the command a few times, and choose a value which is perhaps twice as long as a typical run. This should avoid failures caused by slow network conditions, for example, but not block Puppet from running altogether.
The exec
resource can do anything to the system that you could do from the command line. As you can imagine, such a powerful tool can be misused. In theory, Puppet is a declarative language: the manifest specifies the way things should be, and it is up to Puppet to take the necessary actions to make them so. Manifests are therefore what computer scientists call idempotent: the system is always in the same state after the catalog is applied, and however many times you apply it, it will always be in that state.
The exec
resource rather spoils this theoretical picture, by allowing Puppet manifests to have side-effects. Since your exec
command can do anything, it could, for example, create a new 1GB file on disk with a random name, and since this will happen every time Puppet runs, you could rapidly run out of disk space. It's best to avoid commands with side-effects like this. In general, there's no way to know from within Puppet exactly what changes to a system were caused by an exec
resource.
Commands run via exec
are also sometimes used to bypass Puppet's existing resources. For example, if the user
resource doesn't quite do what you want for some reason, you could create a user by running the adduser
command directly from an exec
. This is also a bad idea, since by doing this you lose the declarative and cross-platform nature of Puppet's built-in resources. The exec
resources potentially change the state of the node in a way that's invisible to Puppet's catalog.
In general, if you need to manage a concrete aspect of system state which isn't supported by Puppet's built-in resource types, you should think about creating a custom resource type and provider to do what you want. This extends Puppet to add a new resource type, which you can then use to model the state of that resource in your manifests.
Creating custom types and providers is an advanced topic and not covered in this book, but if you want to know more, consult the Puppet documentation at
You should also think twice before running complex commands via exec
, especially commands which use loops or conditionals. It's a better idea to put any complicated logic in a shell script (or, even better, in a real programming language), which you can then deploy and run with Puppet (avoiding, as we've said, unnecessary side-effects).
3.133.159.198