Understanding the type system
Being one of the cornerstones of the Puppet model, resources were introduced quite early in Chapter 1, Writing Your First Manifests. Remember how each resource represents a piece of state on the agent system. It has a resource type, a name (or a title), and a list of attributes. An attribute can either be property
or parameter
. Between the two of them, properties represent distinct pieces of state, and parameters merely influence Puppet's actions upon the property
values.
Let's examine resource types in more detail and understand their inner workings. This is not only important when extending Puppet with resource types of your own (which will be demonstrated in Chapter 5, Extending Your Puppet Infrastructure with Modules). It also helps you anticipate the action that Puppet will take, given your manifest, and get a better understanding of both the master and the agent.
First, we take a closer look at the operational structure of Puppet, with its pieces and phases. The agent performs all its work in discreet transactions. A transaction is started under any of the following circumstances:
- The background agent process activates and checks in to the master
- An agent process is started with the
--onetime
or--test
options - A local manifest is compiled using
puppet apply
The transaction always passes several stages. They are as follows:
- Gathering fact values to form the actual catalog request.
- Receiving the compiled catalog from the master.
- Prefetching of current resource states.
- Validation of the catalog's content.
- Synchronization of the system with the
property
values from the catalog.
Facter was explained in the previous section. The resource types become important during compilation and then throughout the rest of the agent transaction. The master loads all resource types to perform some basic checking—it basically makes sure that the types of resources it finds in the manifests do exist and that the attribute names fit the respective type.
The resource type's life cycle on the agent side
Once the compilation has succeeded, the master hands out the catalog and the agent enters the catalog validation phase. Each resource type can define some Ruby methods that ensure that the passed values make sense. This happens on two levels of granularity: each attribute can validate its input value, and then the resource as a whole can be checked for consistency.
One example for attribute value validation can be found in the ssh_authorized_key
resource type. A resource of this type fails if its key
value contains a whitespace character, because SSH keys cannot comprise multiple strings.
Validation of whole resources happens with the cron
type, for example. It makes sure that the time
fields make sense together. The following resource would not pass, because special times, such as midgnight
, cannot be combined with numeric fields:
cron { 'invalid-resource': command => 'apt-get update', special => 'midnight', weekday => [ '2', '5' ], }
Another task during this phase is the transformation of input values to more suitable internal representations. The resource type code refers to this as a munge
action. Typical examples of munging are the removal of leading and trailing whitespace from string values, or the conversion of array values to an appropriate string format—this can be a comma-separated list, but for search paths, the separator should be a colon instead. Other kinds of values will use different representations.
Next up is the prefetching phase. Some resource types allow the agent to create an internal list of resource instances that are present on the system. These types are referred to as being enumerable types. For example, this is possible (and makes sense) for installed packages—Puppet can just invoke the package manager to produce the list. For other types, such as file
, this would not be prudent. Creating a list of all reachable paths in the whole filesystem can be arbitrarily expensive, depending on the system on which the agent is running.
Tip
The prefetching can be simulated by running puppet resource <resource type> <title>
on the command line as follows:
# puppet resource user root user { 'root': ensure => 'present', comment => 'root', gid => '0', home => '/root', password => '$6$17/7FtU/$TvYEDtFgGr0SaS7xOVloWXVTqQxxDUgH.eBKJ7bgHJ.hdoc03Xrvm2ru0HFKpu1QSpVW/7o.rLdk/9MZANEGt/', password_max_age => '99999', password_min_age => '0', shell => '/bin/bash', uid => '0', }
Finally, the agent starts walking its internal graph of interdependent resources. Each resource is brought in sync, if necessary. This happens separately for each individual property, for the most part.
Tip
The ensure
property, for types that support it, is a notable exception. It is expected to manage all other properties on its own—when a resource is changed from absent
to present
through its ensure
property (in other words, the resource is getting newly created), this action should bring all other properties in sync as well.
There are some notable aspects of the whole agent process. For one, attributes are handled independently. Each can define its own methods for the different phases. There are quite a number of hooks, which allow a resource type author to add a lot of flexibility to the model.
It is also worth noting that the whole validation process is performed by the agent, not the master. This is beneficial in terms of performance. The master saves a lot of work, which gets distributed to the network of agents (which scales with your needs automatically).