
Overriding resource parameters
Both exported and virtual resources are declared once, and are then collected in different contexts. The syntax is very similar, as are the concepts.
Sometimes, a central definition of a resource cannot be safely realized on all of your nodes, though; for example, consider the set of all your user
resources. You will most likely wish to manage the user ID that is assigned to each account in order to make them consistent across your networks.
Even if all accounts on almost all machines will be able to use their designated ID, there are likely to be some exceptions. On a few older machines, some IDs are probably being used for other purposes already, which cannot be changed easily. On such machines, creating users with these IDs will fail.
Fortunately, Puppet has a convenient way to express such exceptions. To give the nonstandard UID, 2066
, to the user, felix
, realize the resource with an attribute value specification:
User<| title == 'felix' |> { uid => '2066' }
You can pass any property, parameter, or metaparameter that applies to the resource type in question. A value that you specify this way is final and cannot be overridden again.
This language feature is more powerful than the preceding example lets on. This is because the override is not limited to virtual and exported resources. You can override any resource from anywhere in your manifest. This allows for some remarkable constructs and shortcuts.
Consider, for example, the Cacti module that you created during the previous chapter. It declares a package
resource in order to make sure that the software is installed. To that end, it specifies ensure => installed
. If any user of your module needs Puppet to keep their packages up to date, this is not adequate though. The clean solution for this case is to add some parameters to the module's classes, which allow the user to choose the ensure
property value for the package and other resources. However, this is not really practical. Complex modules can manage hundreds of properties, and exposing them all through parameters would form a horribly confusing interface.
The override syntax can provide a simple and elegant workaround here. The manifest that achieves the desired result is very straightforward:
include cacti Package<| title == 'cacti' |> { ensure => 'latest' }
For all its simplicity, this manifest will be hard to decipher for collaborators who are not familiar with the collector/override syntax. This is not the only problem with overrides. You cannot override the same attribute multiple times. This is actually a good thing, because any rules that resolve such conflicting overrides make it extremely difficult to predict the actual semantics of a manifest that contains multiple overrides of this kind.
Relying on this override syntax too much will make your manifests prone to conflicts. Combining the wrong classes will make the compiler stop creating the catalog. Even if you manage to avoid all conflicts, the manifests will become rather chaotic. It can be difficult to locate all active overrides for a given node. The resulting behavior of any class or define becomes hard to predict.
All things considered, it's safest to use overrides very sparingly.
Note
Please note that collectors are especially dangerous when used without a selector expression:
Package<| |> { before => Exec['send-software-list'] }
Not only will it realize all virtual resources of the given type. It will also force surprising attribute values on both virtual and regular resources of the same type.
Saving redundancy using resource defaults
The final language construct that this chapter introduces can save you quite some typing, or rather, it saves you from copying and pasting. Writing a long, repetitive manifest is not what costs you lots of time, of course. However, a briefer manifest is often more readable, and hence, more maintainable. You achieve this by defining resource defaults—attribute values that are used for resources that don't choose their own:
Mysql_grant { options => ['GRANT'], privileges => ['ALL'], tables => '*.*', } mysql_grant { 'root': ensure => 'present', user => 'root@localhost', } mysql_grant { 'apache': ensure => 'present', user => 'apache@10.0.1.%', tables => 'application.*', } mysql_grant { 'wordpress': ensure => 'present', user => 'wordpress@10.0.5.1', tables => 'wordpress.*', } mysql_grant { 'backup': ensure => 'present', user => 'backup@localhost', privileges => [ 'SELECT', 'LOCK TABLE' ], }
By default, each grant should apply to all databases and comprise all privileges. This allows you to define each actual mysql_grant
resource quite sparsely. Otherwise, you will have to specify the privileges
property for all resources. The options
attribute will be especially repetitive, because they are identical for all grants in this example.
Note that the ensure property is repetitive as well, but it was not included. It is considered good practice to exempt this attribute from resource defaults.
Despite the convenience that this approach offers, it should not be used at each apparent opportunity. It has some downsides that you should keep in mind:
- The defaults can be surprising if they apply to resources that are declared at a lexical distance from the defaults' definition (such as several screens further down the manifest file)
- The defaults transcend the inclusion of classes and instantiation of defines
These two aspects form a dangerous combination. Defaults from a composite class can affect very distant parts of a manifest:
class webserver { File { owner => 'www-data' } include apache, nginx, firewall, logging_client file { ... } }
Files declared in the webserver
class should belong to a default user. However, this default takes effect recursively in the included classes as well. The owner
attribute is a property: a resource that defines no value for it just ignores its current state. A value that is specified in the manifest will be enforced by the agent. Often, you do not care about the owner of a managed file:
file { '/etc/motd': content => '...' }
However, because of the default owner
attribute, Puppet will now mandate that this file belongs to www-data
. To avoid this, you will have to unset the default by overwriting it with undef
, which is Puppet's analog to the nil
value:
File { owner => undef }
This can also be done in individual resources:
file { '/etc/motd': content => '...', owner => undef }
However, doing this constantly is hardly feasible. The latter option is especially unattractive, because it leads to more complexity in the manifest code instead of simplifying it. After all, not defining a default owner
attribute will be the cleaner way here.
Note
The semantics that make defaults take effect in so many manifest areas is known as dynamic scoping. It used to apply to variable values as well and is generally considered harmful. One of the most decisive changes in Puppet 3.0 was the removal of dynamic variable scoping, in fact. Resource defaults still use it, but it is expected that this will change in a future release as well.
Resource defaults should be used with consideration and care. For some properties such as file mode
, owner
, and group
, they should usually be avoided.