Exec resources

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.

Automating manual interaction

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.

Attributes of the exec resource

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.

Tip

If you apply this example, Puppet will give you an error like the following:

Error: /Stage[main]/Main/Exec[install-cat-picture-generator]/returns: change from notrun to 0 failed: Could not find command '/tmp/cat-picture/generator/configure'

Tip

That's expected, because the specified command does not in fact exist. In your own manifests, you may see this error if you give the wrong path to a command, or if the package that provides the command hasn't been installed yet.

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:

  1. Puppet checks the creates attribute and sees that the /usr/local/bin/cat-picture-generator file is not present, therefore the exec must be run.
  2. Puppet runs the /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.
  3. Next time Puppet runs, it again checks the 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.

Tip

Make sure that your exec resources always include a creates attribute (or a similar control attribute such as onlyif or unless, which we'll look at later in this chapter). Without this, the exec command will be run every time Puppet runs, which is almost certainly not what you want.

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.

The user attribute

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.

The onlyif and unless attributes

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

The refreshonly attribute

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.

Tip

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.)

The logoutput attribute

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.

The timeout attribute

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.

How not to misuse exec resources

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.

Tip

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

https://docs.puppet.com/guides/custom_types.html

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).

Tip

As a matter of good Puppet style, every exec resource should have at least one of creates, onlyif, unless, or refreshonly specified, to stop it being applied on every Puppet run. If you find yourself using exec just to run a command every time Puppet runs, make it a cron job instead.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.133.159.198