Chapter 5. Resources

Resources are the heart of Puppet’s state management. Resources model a configurable entity on a node. The vast majority of resources model things you’re quite familiar with, such as:

  • Files

  • Packages

  • Package repositories (yum, apt, chocolatey, etc.)

  • Users

  • cron entries

  • Databases

  • Windows registry keys

Resource types declare the desired state (a model) of the resource, and resource providers converge the target to match the model. Put another way, types are the interface, and providers are pluggable platform-specific implementations.

This chapter reviews the built-in Puppet types and providers, along with popular custom types. We examine how to best use virtual resources, exported resourcesresource relationships, and metaparameters.

Using Resources to Implement Change

Resources evaluate and manage state of their type on nodes. No matter how complex the codebase is, the manifests are ultimately compiled down to a catalog of resource declarations, which is what the Puppet agent uses to evaluate and converge a node. Resources are the only way Puppet can idempotently affect change on your nodes.

Warning

exec resources can also affect node state, but they create untrackable, unknown changes that are antithetical to the purpose of Puppet.

Resource Types Abstract Implementation Details

A Puppet resource type is an abstraction layer and interface between Puppet and the underlying provider. You implement types in Ruby by declaring a child of the Puppet::Type class.

Types attempt to model system resources in a common and abstract way. For example, even though there are dozens of different package managers available, the package provider attempts to implement a platform-independent model of a package, abstracting away the actual implementation details of managing that package, as illustrated in the following example:

package { 'puppet':
  ensure => 'installed',
}

The benefit of this abstraction is consistency. Code with this simple resource declaration works on every platform. Although the underlying package managers on Linux and Windows are wildly different, the package type has backend providers for every package manager. When asked to install this package, it will use the package manager appropriate for the current platform. This simplicity makes our intent clear and the code clean.

Use the Most Specific Resource Type

When declaring resources, consider the scope or impact your resource has, how the resource might conflict with other resources, and how the scope of a particular resource might affect your overall design.

For example, don’t attempt to manage a file in its entirety when a resource with a smaller scope is available. To elaborate, in most cases you would manage /etc/hosts using host resources rather than using a file resource. This allows multiple modules to add entries to the /etc/hosts file without requiring all changes to go through one module that manages the entire file.

Many modern applications support reading configuration files from service.d directories, allowing you to build the configuration using multiple file resources. For example, you can add and remove configurations from Apache by writing to the /etc/httpd/conf.d directory. Modern releases of sudo support a sudoers.d directory using the #includedir keyword. In both cases, this allows multiple classes to drop in their own configuration files without the risk of creating conflicts. The configuration is built by reading each provided file. This allows multiple modules to affect the configuration without sharing a common file resource.

Choosing the appropriate resource type is key: the host resource is great for distributing entries for the critical nodes of your infrastructure. A half-dozen entries can reduce load on your DNS infrastructure and provide a bit of extra reliability. If you need to define thousands of hosts entries, it might be easier to declare DNS nameserver resources that configure the name servers that should be queried.

Examining a Naked Resource

A resource in the Puppet language is a data structure. The type name is data, the resource name is data, and the resource properties are data. Examples 5-1 through 5-3 look at the three most common ways you’ll see resources described.

Example 5-1. The built-in file resource type declared using the Puppet Language
file { '/tmp/example':
  owner   => 'root',
  group   => 'root',
  mode    => '0444',
  content => 'This is an example.
'
}

Any resource’s attributes could be serialized into YAML.

Tip

Serialization is the process of translating data structures or object state into a format that can be stored or transmitted and reconstructed later.

Example 5-2. A file resource seralized into YAML
file:
  - '/tmp/example':
    - owner:   'root'
    - group:   'root'
    - mode:    '0444'
    - content: 'This is an example
'

To build the catalog, the Puppet manifests are parsed and resources are evaluated to build the Puppet catalog. Resources stored in YAML or JSON can be deserialized by create_resource and other methods to add to the Puppet catalog. Variables and conditional logic are evaluated into data values. Ultimately every resource declaration is rendered into a simple data structure in the Puppet catalog. Example 5-3 examines how that looks.

Example 5-3. A file resource from a Puppet catalog
{
  "exported": false,
  "file": "/home/vagrant/example_file.pp",
  "line": 6,
  "parameters": {
    "content": "This is an example.
",
    "group": "root",
    "mode": "0444",
    "owner": "root"
  },
  "tags": [
    "file",
    "class"
  ],
  "title": "/tmp/example",
  "type": "File"
}

What you see in Example 5-3 is the ultimate resolution of all conditional logic, variables, iteration, and all other code tricks. The catalog contains only explicit names and values. This is the final result.

Exploring Resources with Tools

There are a number of tools that you can use to test, document, and play with resources. Use Puppet on your workstation to experiment with types and providers in a safe environment. Let’s take a look at a few of these tools.

puppet describe

The puppet describe command provides documentation for resources. describe is intended to be used as a command-line tool. puppet describe --list will list all installed resource types. puppet describe type will show documentation for a resource type that you specify.

puppet describe shows the inline documentation for both built-in and custom resource types currently installed. It can be invaluable when you have a lot of custom types and providers. It’s also useful at sites running older releases of Puppet for which the latest online documentation might not be correct for the installed release.

Tip

If the custom resource type is not in the production environment, you’ll need to use the --environment option so that describe can find the resource.

puppet-strings

The puppet-strings gem provides automatic creation of formatted documentation from modules. You run the puppet strings command in a module directory to generate HTML documentation in doc/ from the structured comments in your manifests, functions, types, and providers. You can find complete documentation at https://puppet.com/docs/puppet/latest/puppet_strings.html.

Tip

The puppet-strings gem is not included by default, the purpose of which is to minimize package size in operational environments. Follow the instructions on the page listed in the preceding paragraph, or install it by using Puppet itself:

sudo puppet resource package puppet-strings ensure=present 
provider=puppet_gem

Of particular use, puppet-strings can autogenerate the module’s REFERENCE.md in Markdown format. It will even inform you when the inline documentation doesn’t match the code, as shown here:

$ puppet strings generate --format markdown
[warn]: Missing @param tag for 'service_name' near manifests/agent.pp:43.
[warn]: The type of the @param tag for 'value' does not match the
  parameter type specification near manifests/inisetting.pp:42

puppet resource

The puppet resource command allows you to interact with the Puppet resource abstraction layer from a command-line shell. It has two purposes:

  • To query the current state of resources on the node

  • To declare a puppet resource directly from the command line

This can be extremely useful as a learning tool, both for folks who aren’t very familiar with resource syntax and for the investigation and exploration of a complex resource type.

Displaying a resource in Puppet language

When supplied with a type and a resource name, for example, puppet resource host localhost, the provider will query the named resource and return a resource declaration in the Puppet language with the resource’s current properties. If you query a nonexistent resource, the result will show the resource as absent, as demonstrated in the following example:

$ puppet resource host localhost
host { 'localhost':
  ensure => 'present',
  ip     => '127.0.0.1',
  target => '/etc/hosts',
}

$ puppet resource host nothere
host { 'nothere':
  ensure => 'absent',
}

If you are using the resource command to generate resource statements, be aware that some resources have read-only properties that will be returned by the resource command but that should not be included in your resource declarations. For example, a file resource will include output like this:

$ puppet resource file /etc/hosts
file { '/etc/hosts':
  ensure  => 'file',
  content => '{md5}a3f51a033f988bc3c16d343ac53bb25f',
  ctime   => '2018-01-18 20:56:03 -0800',
  group   => '0',
  mode    => '0644',
  mtime   => '2017-06-29 20:42:42 -0700',
  owner   => '0',
  type    => 'file',
}

The content, ctime, and mtime attributes are read-only, and cannot be changed in a resource declaration. It’s a good idea to sanity check resources gathered by the resource command and trim down attributes that are not of concern. If you don’t know whether you should include a parameter or don’t know what it is, you should default to leaving it out.

Another limitation is that you cannot pass parameters when performing a query. For example, you cannot specify the target when performing a query. If you attempt to do so, Puppet will interpret it as an attempt to declare a resource rather than to query a resource.

Displaying all instances of a resource

Use of the resource command to identify an individual resource requires that the resource have a meaningful namevar attribute. The namevar attribute is used to locate the resource and uniquely identify it. For example, the namevar attribute of a file resource is the file’s path attribute, which informs us where the file can be found. This is equally simple for user, group, host, and package resources because the namevar is their name.

Not all resource types have clear and obvious titles. For example, the ini_setting resource title is an arbitrary index value used only for uniqueness in the catalog. You won’t be able to guess it from reading the target resource.

If you are not sure what a resource’s title is, you can ask the resource provider to query the node. When supplied with only a resource type, the puppet resource command will determine the default provider for the type and ask it to provide all instances available, as demonstrated in the following:

C:Program Files (x86)Puppet LabsPuppetbin> puppet resource package
package { '7-Zip 9.38 (x64 edition)':
  ensure => '9.38.00.0',
}
package { 'OpenSSH for Windows 6.7p1-2 (remove only)':
  ensure => 'installed',
}
package { 'Oracle VM VirtualBox Guest Additions 4.3.18':
  ensure => '4.3.18.0',
}

Generally speaking, you can list a resource if there’s a finite amount of all resources of that type. Not all providers provide that capability, in which case the query will return an error indicating that:

$ puppet resource file
Error: Could not run: Listing all file instances is not supported.
    Please specify a file or directory, e.g. puppet resource file /etc

You cannot query some resource types because the resource provider doesn’t implement the method required to query the current resource state, either because it would be too costly to do so (every file on a node) or the resources are not something that can be queried in a practical manner, such as a list of all possible exec resources.

Declare a resource on the command line

As mentioned earlier, the resource command has the ability to declare resources on a node. Any time you supply attributes for a resource, you are declaring it rather than querying it. This can be extremely useful for one-off commands with simple attribute values, such as installing puppet-strings on your development host, as shown in the following:

$ sudo puppet resource package puppet-strings provider=puppet_gem ensure=present
Notice: /Package[puppet-strings]/ensure: created
package { 'puppet-strings':
  ensure => ['2.1.0'],
}

It can also be useful for experimentation; the puppet resource output includes the resource declaration for the resource defined on the command line. You can add the --noop option to generate well-formatted resource declarations.

puppet apply

If you’re interested in performing more involved experiments, consider using puppet apply --execute with small snippets, as shown in the code example that follows. As this runs Puppet through the catalog building and application process, it’s the best way to test out short snippets of puppet code.

$ puppet apply --execute "file { '/tmp/testfile': ensure => present }"
Notice: Compiled catalog in environment production in 0.12 seconds
Notice: /Stage[main]/Main/File[/tmp/testfile]/ensure: created
Notice: Applied catalog in 0.03 seconds

Using puppet apply in this manner is useful when you need to experiment with more complex resource types that tend to require a lot of trial and error. This allows experimentation without committing or pushing code until you’re certain of the syntax.

You can use puppet apply with small manifest files for slightly longer examples:

$ puppet resource user jorhett > test.pp
$ puppet apply test.pp
Notice: Compiled catalog in environment production in 0.12 seconds
Notice: Applied catalog in 0.03 seconds

Because the manifest exactly matches the current state, nothing has changed. But you can now edit this manifest to try out changes. This allows you to test resource defaults, overrides, resource relationships, and any other features that are not available when experimenting with the resource command line.

Resource Declaration

Resource declaration is the act of adding a resource to the catalog. There are a number of ways to declare resources that can be useful in special cases, such as declaration functions, meta resources, virtual resources, and exported resources. In this section, we look at the different approaches to resource declaration and best practices for each approach.

Ensure states

The most fundamental property of a resource is whether that resource exists. Resources are ensurable; that is, they can be made present or absent. Some resources have additional ensure states, such as the file resource that can be ensured to be a file or a directory. A few irregular resources such as exec are not ensurable.

Although most resources have an explicit present or absent state, all resources have a third implicit undefined or unmanaged state. Understanding the unmanaged state is critical. When a resource is removed from our manifests, we intuitively think of that resource as being absent. But in fact, the resource becomes unmanaged; if the resource already exists it will continue to exist, and if the resource doesn’t exist, it will not be created.

Tip

In all seriousness, when a resource is removed from the catalog the Puppet agent loses the ability to know whether the configuration managed by that resource exists or not. It has become Schroedinger’s resource, the state of which is unknowable.

This unmanaged state is the number one cause of configuration drift. Machines that have been around for a long time tend to collect unmanaged resources that do not exist on fresh machines. This creates situations in which machines that should be identical are actually very different.

This unmanaged state complicates rollbacks. Intuitively, we assume that we can return to a previous state by simply reverting a change. With Puppet and other configuration management solutions, rolling back a change requires explicitly declaring the former state; for example, making a newly created file absent. The previous version of our configuration code didn’t mention the new file at all.

Renaming resources in our manifests produces the same result; Puppet has no way to know that the new resource had a previous name. Instead of moving the resource from one name to another, it simply creates the resource with the new name. To make this change correctly, create a new resource and change the ensure value of the old resource to absent.

Use Variables for Data-Driven Declaration

Puppet code works best when the behavior is driven by Hiera data.

Static declaration

It is very easy to read a static declaration because all values are filled in, as shown here:

host { 'localhost':
  host_aliases => ['localhost.localdomain'],
  ip           => '127.0.0.1',
}

It is, however, rare that the Puppet developer will know the absolute values for a resource. This is more often a hint that you have data in the code, which should be avoided unless that data is absolutely, positively internal to the module and will never change.

Interpolation

The best way to write reusable code is to accept data input that instructs the code how to operate. For example, the package name, the configuration file location, and the user Apache runs under changes on different Linux platforms. You can keep the declaration clean and easy to read by using variables in the resource declaration. You can use variables in the resource title and any properties or parameters. These variables will be interpolated into values when the resource is added to the catalog, as illustrated here:

file { $apache_config:
  ensure  => $apache_file_ensure,
  owner   => $apache_user,
  group   => $apache_group,
  mode    => $apache_file_mode,
  content => template('apache/apache.conf.erb'),
}

This allows the platform-specific values to be acquired from the module Hiera data, which contains values appropriate for each supported platform. In addition, this allows the environment layer of Hiera to override the module data with a site-specific value, without changing a single line of code.

Use Arrays for Similar Resources

You can declare multiple resources in a single statement by passing an array to the resource title. This works best for very similar resources:

$web_directories = [
  '/var/www/',
  '/var/www/html',
]

file { $web_directories:
  ensure => 'directory',
  owner  => 'httpd',
  group  => 'webmasters',
  mode   => '2775',
}

Array-style resource declaration is a clean way to declare similar resources. However, there is a limitation; all of the resources must share the same attribute values. For simple resources such as packages and directories, this is typically not an issue.

In many cases, each resource will have its own properties: users have unique IDs, files have unique contents, and so on. In these cases, avoid using array-style resource declaration. Although it’s possible to pass a hash of values to a function call or iterator block (each, map, ...) consider carefully whether this improves the readability of the code.

Tip

Using fancy but difficult-to-read code to declare resources has no positive value because the values are always expanded in the catalog. Easy-to-read code and difficult-to-read code will create the exact same catalog, so focus on making your code easy to read.

You can also use arrays in resource references to establish a relationship with every resource in the list, like so:

service { 'httpd':
  ensure    => 'running',
  subscribe => File[$apache_config_file_list],
}

This example showed how to have the httpd process track a data-driven list of configuration files.

Using Automatic Resource Relationships for Clean Code

Resources often contain code to automatically create relationships with related resources. For example, if you specify the owner of a file resource, it will automatically create a child relationship with the user resource for that owner, as demonstrated here:

user { 'testuser':
  ensure => 'present',
}

file { '/tmp/testfile':
  owner => 'testuser',
  require => User['testuser'],   # redundant and unnecessary!
}

Resource ordering might appear to be an issue with array resource declarations because the attributes are the same for each one. However, the resource’s autorequire system will often handle ordering for you, as it does here:

file { ['/var/www/', '/var/www/html']:
  ensure => 'directory',
}

file { '/var/www/html/index.html':
  source => 'puppet:///modules/example/index.html',
}

In this example, /var/www/html/index.html automatically requires resources /var/www/html and /var/www, because they are parent directories of the file. Autorequired dependencies allow for very clean and easy-to-read code, and leave the dependency management up to the provider.

Avoid complex structures in resource declarations

You should avoid embedding conditional logic such as selectors in your resource statements because doing so tends to produce difficult-to-read code. Assign the result to a variable and then use the variable as the attribute value, as discussed in the documentation. Here’s what the code looks like:

# Long/wrapped lines
$motd = @("END")
Welcome to ${::fqdn}

This host is managed by Puppet.
Unauthorized access is strictly prohbited.
END

file { '/etc/motd':
  content => $motd,
}

This example uses Heredoc syntax (available since Puppet 4) for a multiparagraph string (unfortunately also demonstrating the data in code antipattern).

Resource Declaration by Functions

There are a number of functions that can declare resources. The most notable and common is create_resources, which you can use to create resources from hashes of data.

The create_resources() function

create_resources() is by far the most popular resource declaration function call. It declares resources from serialized data structures. The following example shows user resources serialized in a YAML hash:

example::users:
  'alice':
    ensure: 'present'
    comment: 'Alice'
    group: 'example_users'
    managehome: true,
  'jack':
    ensure: 'absent'
    comment: 'Jack'

The example that follows iterates over this hash of users and declares a new user resource from each entry:

# Get serialized user resources from Hiera
$hash_of_users = lookup('example::users', Hash, 'deep', {})

# Set default values
$user_defaults = {
  shell => '/bin/bash',
}

# Declare user resources with values from the hash
create_resources('user', $hash_of_users, $user_defaults)

For each entry in the hash, create_resources declares a user resource with the hash key as the title. The attributes for the resource will be taken from the hash of values behind the key, combined with any missing attribute values from the $user_defaults hash. For example, the first entry in the hash shown will be declared as a resource like this:

user { 'alice':
  ensure     => 'present',
  comment    => 'Alice',
  gid        => 'examplegroup',
  managehome => true,
  shell      => '/bin/bash',
}

The creation of resources from serialized data enables the separation of code from long lists of data. It enables us to create resources from lists supplied by other parts of our infrastructure, reducing duplication.

Whether this is best practice can be very situational. Here are some guidelines to help you discern:

Data in code?

A list of authorized users in Hiera data helps avoid putting changeable data values (the users to declare) in the code.

Code in data?

The code should never depend on essential parts of the code coming from data. The code should always operate with or without valid external data.

Tip

It’s okay for a module to depend on platform-specific or default data included in the module.

The ensure_ functions

ensure_packages() and ensure_resource() are function calls that you can use to add resources to the catalog. Both function calls are included in the puppetlabs/stdlib module and are not available in a base Puppet install.

Unlike create_resources(), these function calls check whether a resource already exists before inserting the resource into the catalog. This would seem to be a good solution to the duplicate resource declaration problem, but you should use it with extreme caution because it’s parse-order dependent. The following statement will compile and run fine:

file { '/tmp/example.txt':
  ensure => 'file',
}

ensure_resource('file', '/tmp/example.txt', { 'ensure' => 'file'})

This statement, however, will throw an error:

ensure_resource('file', '/tmp/example.txt', { 'ensure' => 'file'})

file { '/tmp/example.txt':
  ensure => 'file',
}

This parse-order dependence can create a huge problem; code that works in one situation can unexpectedly fail in another. A small change in the code can cause resource ordering to change, instantly creating problems not previously visible. As a result of the parse-order problem, you can safely use the ensure_resources() functions only where every instance is declaring that resource with the ensure_resource function.

Note

Functions are subject to parse-order problems, too. The only safe way to avoid the parse-order problem is to place the common resource in its own class. Then, include that class in each place that needs the shared resource.

The same problems are visible with resource declarations inside defined_with_params() and defined() conditional statements.

Resources Metatypes

Resources metatypes are resource types that declare other resources. Metatypes rarely have their own providers; instead, they declare instances of other resource types.

Puppet has two built-in resource metatypes: the tidy resource, which is responsible for removing files, and the resource type, which you can use to purge any type of resource for which the provider can provide a list. As shown in the following example, this is typically used to purge unmanaged users, groups, packages, and so on from a node:

resource { 'host':
  purge => true,
}

When a resource metatype is declared, the resource metatype declaration is added to the catalog and sent to the agent. When the resource metatype is evaluated by the agent, it uses the resource provider to acquire a list of all resources of that type. Any additional resources not already specified in the catalog are inserted into the client’s in-memory copy of the catalog with the ensure value set to absent. The catalog is applied normally. The resulting report will contain the state of the resource metatype as well as the state of each resource it added to the catalog.

In the case of the tidy resource, the process is as follows:

  1. The Puppet parser adds a tidy resource when building the catalog.

  2. The catalog is sent to the agent.

  3. The tidy resource uses the file resource provider to scan the named directory structure.

  4. file resources returned by the provider are compared to criteria.

  5. Each match causes a file resource to be added to the agent’s catalog with ensure => absent.

  6. The catalog is applied with the added file resources.

It’s very important to note that there is no two-way communication between the agent and parser when evaluating resource metatypes. The parsing of the manifests has long been completed and the catalog build has finished. The evaluation of the resource metatype is handled entirely by the node’s agent.

Because metatypes are evaluated after the catalog has been built, parse-order dependencies are not a problem. Unlike function-based resource declarations, resource metatypes will never add conflicting resources to the catalog. This means that if you explicitly declare that a resource should be present, a resource metatype will not attempt to remove it. For example, tidy will never attempt to remove a file that has been explicitly declared as present, and it will not generate a duplicate resource declaration. As a result, resource metatypes are a great way to remove everything except the resources you’ve declared in the parsed catalog.

Note

For more information, refer to the Resource Type: Tidy and Resource Type: Resources documentation.

Resource Metaparameters

Puppet provides metaparameters, or attributes, that can be applied to any resource type. Metaparameters generally control the way Puppet operates rather than enforcing resource state. These are well documented in the Puppet reference materials, so, here, we concern ourselves only with practical uses that might not be obvious.

alias

The alias metaparameter allows you to specify alternative titles for a resource to provide a friendly name for a resource with a long and complex title, as shown here:

file { '/etc/httpd/conf/httpd.conf':
  ensure  => 'file',
  content => template('apache/apache.conf.erb')
  alias   => 'apache_config',
}

You cannot use aliases everywhere that resource titles can be. For example, you cannot use them as identifiers for resource chaining. The safer approach is to use a friendly name in the resource title and specify the longer namevar attribute explicitly, as demonstrated in the following:

file { 'apache_config':
  ensure  => 'file',
  content => template('apache/apache.conf.erb')
  path    => '/etc/httpd/conf/httpd.conf',
}

audit

The audit metaparameter accepts a list of attributes whose state should be tracked by Puppet. This has two purposes:

  • It allows Puppet to report changes on resources that aren’t managed by Puppet.
  • It allows Puppet to generate notification signals when resources have changes outside of Puppet.

Audit works by recording the current state of the resource to an audit log. If the resource changes state after Puppet’s evaluation of the resource, the differences from the current state of the resource will be reported. If a notify or subscribe relationship exists with another resource, a notification will be sent.

It’s safe for some attributes of a resource to be managed while others are audited. Be aware that audited attribute notifications are indistinguishable from managed attribute change notifications.

You can use auditing to create triggers for resources changed by something outside of Puppet, such as when a file is replaced as shown in the following code:

file { '/uploads/myapp.tar.gz':
  audit  => 'contents',
  notify => Exec['extract_myapp']
}

exec { 'extract_myapp':
  command     => "/bin/tar -zxf ${myapp_tarball} -C /opt",
  refreshonly => true,
}

Ordering metaparameters: before and require

before and require allow you to specify dependencies and ordering between resources. They ensure that resources are processed in the correct order and that resources are not processed if their dependencies failed to be applied.

before and require perform the same basic function in opposite directions. Use whichever metaparameter will result in the most maintainable code.

Tip

In many cases, resource relationships are determined implicitly. Puppet will automatically detect and autorequire dependencies between related resources. You can take advantage of this behavior to minimize the number of manually specified resource relationships. See the autorequires documentation for a resource to see what relationships are automatically created.

Notification metaparameters: notify and subscribe

Notifications allow a resource to signal other resources that it has changed during a catalog application. The dependent resource receives a refresh event that it can act upon, if relevant for that resource type. notify and subscribe implement the same functionality as before and require with the addition of sending the refresh event from a resource to its dependency.

Like their counterparts, notify and subscribe implement the same basic function in opposite directions. Use whichever metaparameter will result in the most maintainable code. The code example that follows shows each:

file { '/tmp/foo':
  notify => Service['biff'],
}

file { '/tmp/bar':
  notify => Service['biff'],
}

service { 'biff':
  # redundant with the notify metaparameters
  subscribe => File['/tmp/foo','/tmp/bar'],
}

In this example, when either declares file changes, the biff service will receive a refresh event, causing the service to be restarted before the same catalog application is completed.

Avoid tight relationships whenever possible

Relationships are best implemented observing the philosophy of low coupling and high cohension:

  • Use relationships between resources only within a class (high cohesion).

  • Use relationships between child classes within a module.

  • Use relationships with the main class of unrelated modules (low coupling).

Beyond these simple rules, we can improve module reliability by doubling down on the philosophy:

High cohension

Use resource relationships within a class even when manifest ordering is used. Manifest ordering is an unstated, implicit structure that is often broken by accident. Make a habit of testing classes with random ordering to help identify cases where a necessary relationship is missing.

Low coupling

Resource relationships that cross class boundaries violate the principles of modular and interface-driven design. By creating relationships only with the main class, you can refactor each module without impact or risk to the other.

noop

The noop metaparameter allows you to explicitly enable or disable no-operation mode for a single resource, class, or defined type declaration. In noop mode, the resource will be evaluated and compared to the modeled state,; however, changes to the resource will not be applied. This allows you to report that a resource does not conform to your policy without changing the resource’s state.

Setting this to false could cause resource states to change when the user wants to evaluate potential changes. Setting noop => false on a resource declaration might seem useful for partial enforcement of catalogs (e.g., security-related resources), but it removes one of Puppet’s greatest strengths: the ability to safely simulate changes by running with --noop enabled. You run puppet apply --noop to review the changes, but the resource’s hardcoded value wins over the command-line value, and a change to the resource is applied.

The converse problem exists for hardcoding noop => true in the code, which will cause an attempt to cause a change with --no-noop to be ineffective.

Because you cannot override this metaparameter on the command line or in the configuration, it is useless in general practice. Instead, use tagged runs for partial enforcement, as discussed in “tag”. This allows you to use the --noop command-line option or noop = true configuration value to prevent changes in a way that can be overridden when necessary.

Note that although Puppet will log the notifications and refresh events that would be created, it will not actually send those signals if noop is enabled. If you want to generate signals without enforcing state, consider using the audit metaparameter instead.

schedule

The schedule metaparameter allows you to assign a resource to be evaluated within a specific window, as defined by the schedule resource type. This attribute can be very useful for ensuring that a resource is applied only within a certain window. It can also limit the frequency at which a resource is evaluated. For more information, see Resource Type: schedule.

Warning

A schedule limits evaluation to the named window. It does nothing to ensure that Puppet applies the catalog during the same window. Creating a window that will intersect with Puppet convergence runs is an exercise for the implementor.

One surprising feature of schedule is that a resource evaluated outside of its schedule always succeeds for dependency purposes. If the resource has not been applied because Puppet has not yet run during the schedule window, the dependent resource won’t have the resource it depends on. For this reason, it is necessary when you’re first building a node or when you’re deploying new features to disable the schedules so that the necessary dependencies are created. Thereafter, they will be updated only during the schedule window.

stage

You can apply stage only to entire classes, and it has a number of complications that make it unsuitable for most purposes.

  • You must assign classes to stages using the resource declaration format, which prevents multiple classes from declaring the class and disallows the application of data-driven class assignment from Hiera.

  • You cannot send notifications between resources in different stages.

  • Class containment behaves strangely if any class resides in another stage.

Early Puppet users attempted to use stage for rough ordering; however, there are so many complications with stages that most sites have abandoned their use entirely. No matter how carefully you segregate resources into different stages, at some point during a minor change you’re going to find that a dependency loop between the stages has been created. Fixing that problem will require a major refactoring, possibly of the entire implementation. There are no simple fixes to ordering resources that cross stage boundaries.

tag

Use tag to identify resources for collection (see “Avoid Parse-Order Problems by Using Virtual Resources” and “Exporting Resources”), to perform partial enforcement of a catalog, and for reporting and review purposes with PuppetDB.

A resource acquires tags from multiple places:

  • The tag metaparameter adds the specified values to the list of tags

  • Tags automatically created for each resource, including the resource type, and each component of the class or defined type name

  • Tags applied to the class containing the resource

  • Tags applied to the class that declared the class containing the resource

If you recall the simple resource inspected at the beginning of this chapter, no tags were declared for the resource and yet tags existed on the resource in the catalog as seen in Example 5-3.

  tag 'example_tag'

  file { '/tmp/foo':
    tag => ['foo', 'bar', 'baz'],
  }

In the preceding code, the resource File['/tmp/foo'] would have the tags ["file","foo","bar","baz","example_tag"]. If it was in a module class, it would have the class tag, too.

As you might imagine, it’s easy to match resources you didn’t intend to match due to this behavior. Use distinct tag prefixes to avoid collecting or acting on resources you didn’t intend to.

Avoid Parse-Order Problems by Using Virtual Resources

When you declare a resource (such as a user), it is added to the catalog. Attempts to declare that same resource again in the same catalog build will generate an error. Therefore, if two different modules might need the same resource, but either one might not be declared for a given host, there needs to be a safe way for both to declare the resource if necessary—virtual resources fill that gap.

A virtual resource declaration adds the resource to the catalog, but marks it inactive. Modules that want to make use of that resource can realize that resource, which marks it active for convergence. Unlike normal resources, a virtual resource can be realized none, one, or multiple times safely.

Note

Unrealized virtual resources are in the catalog, but not available for dependency relationships. A resource relationship with a nonrealized/inactive virtual resource will cause a catalog failure.

The ability to realize resources multiple times dramatically simplifies cases for which resources are optionally added based on a set of rules. Example 5-4 demonstrates how to declare and realize virtual resources.

Example 5-4. Declaring and realizing virtual resources
@user { 'alice':
  ensure => 'present',
  gid    => 'dba',
}

@user { 'bob':
  ensure => 'present',
  gid    => 'web',
}

case $facts['role'] {
  'database': { realize( User['alice'] ) }  # realize a specific resource
  'web':      { User <| gid == 'web' |> }   # a collector realizes matching resources
}

In Example 5-4 if the role fact is database, the user alice is added to the catalog. If the role is web, users in the web group are added to the catalog. This logic can be applied multiple times in manifests without any concern about duplicate user resource declarations.

Virtual resources make it easy to create Don’t Repeat Yourself (DRY), data-driven code that conditionally realizes resources from a list of possible choices. The alternative approach would be to maintain separate lists for every possible combination of users that might be applied to the system, dramatically increasing the probability of a conflict and greatly increasing maintenance overhead.

Virtual resources are not parse-order dependent. You can declare virtual resources late in your manifests and realize them early. They will work as expected without any explicit ordering. This allows considerable flexibility and avoids trying to manage manifest parse order.

Although virtual resources allow a resource to be realized multiple times, you should not use them as a way of handling shared resources and module interdependencies. Using virtual resources in this way violates the principles of modular design, the single responsibility principle, and separation of concerns. With this approach, two modules need to agree to use virtual resources and on how those resources should be defined. Splitting virtual resources between modules means that changes to the resource declaration in one module can break another module. In these cases, the creation of additional modules or classes to handle the commonality or dependency is recommended.

Dangling relationships to unrealized resources causes breakage

You should also take care when designing resource relationships with virtual resources. If you realize a virtual resource that has a relationship with an unrealized resource, a catalog compilation error will be thrown. You can use the before/require and subscribe/notify symmetry to work around the issue; if the unrealized resource declares the relationship, you’re fine.

Instead, create relationships when realizing the resources with a collector.

Example 5-5 shows the require ordering (the -> chaining arrow) of the yumrepo resource being added as the collector realizes the list of packages.

Example 5-5. Declaring resource relationships when realizing virtual resources
@package { ['apache2', 'mysql-client']:
  tag => 'Debian',
}

@package { ['httpd', 'mysql']:
  tag => 'RedHat',
}

case $facts['osfamily'] {
  'RedHat': {
     Yumrepo['base'] -> Package <| tag == 'RedHat' |>
  }
  default: {
    Package <| tag == $facts['osfamily'] |>
  }
}

This technique is safe to implement with an empty list because a relationship to an empty collector will not cause an error, as shown here:

package {'filesystem':
  ensure => 'installed',
}

Package['filesystem'] ~> File <| title == 'nonexistent_resource' |>

Resource collectors can realize virtual resources based on any attribute of the resource. The tag metaparameter is commonly employed to realize virtual resources, but you should use it with caution. The compiler automatically assigns a large number of tags to each resource in the catalog based on calling class names, resource types, and many other considerations. If you use nonunique tag names to realize resources, you might find cases in which resources are realized unexpectedly. Tags are applied in a parse-order-dependent way, depending on the first class that declares another class, so the list of automatic tags for a resource is not stable. Use attributes other than tag to realize resources to avoid these issues.

Exporting Resources

Exported resources allow resources to be shared between nodes. Exported resources are implemented and treated very similarly to virtual resources. Resources can be exported without being applied, and must be collected in order to be added to the catalog. The syntax for exported resources is also very similar to that of virtual resources, only with a doubled @@ prefix.

For example, a web server could export details of a web service for inclusion in a load balancer pool. The following code adds a webserver instance to a haproxy service pool using the puppetlabs/haproxy module:

@@haproxy::balancermember { $facts['fqdn']:
  listening_service => 'web',
  server_names      => $facts['hostname'],
  ipaddresses       => $facts['ipaddress'],
  ports             => '80',
  options           => 'check',
}

When a resource is exported, it is marked with an exported flag in the exporting node’s catalog. A copy of the node’s catalog is then stored in PuppetDB.

The node running haproxy would collect haproxy::balancermember exported resources using a search for the service, as shown here:

include haproxy

haproxy::listen { 'web':
  ipaddress => $facts['ipaddress'],
  ports     => '80',
}

Haproxy::Balancermember <<| listening_service == 'web' |>>

PuppetDB responds to the search with a list of exported resources that match the search, which are added to the Puppet catalog of the collecting node to flesh out the haproxy configuration.

Tip

Exported resources are one method of implementing service discovery with Puppet. They allow nodes to supply resources for use on other nodes.

Exported resources satisfy a few common use cases:

  • Configuring monitoring systems to monitor services on node

  • Identifying nodes answering for a service pool or cluster membership

  • Any other use case for which the services might scale up or down dynamically

You can export any resource, including exec resources.

Comparing virtual and exported resources

The similar syntax of virtual and exported resources is due to their very similar nature. In review:

Local resources

Local node @virtual_resource collected by <| search |>

Shared resources

Any node’s @@exported_resource collected by <<| search |>>

Safely using exported resource

An important aspect of exported resources is that they have universal availability. A resource is unlikely to make sense outside the scope of its intended environment. An unexpected resource can cause catalog compilation errors, misconfiguration, or node failure if applied unexpectedly to the wrong environment.

Following are some guidelines for the safe usage of exported resources:

Exported resource titles must be globally unique across all nodes

Because an exported resource could be collected by any node, and a resource title must be unique within a catalog, this requires each exported resource to be absolutely unique. Use a string unique to the node (certname, FQDN, serial, etc.) in the exported resource title to guarantee uniqueness.

A resource can be collected by any node in the infrastructure

As of 2018, there’s not yet any security controls to limit access to exported resources. Only the search parameter of the resource collector will limit the results.

Exported resources are not constrained by their Puppet environment

It’s possible for resources from a development branch to be applied to a production branch. If a Puppet server provides multiple environments that should not collect one another’s resources, tag the resources with the environment and use it in the collector’s search.

Exported resources should not contain resource relationships

It is entirely possible that due to timing, one exported resource would be available and the other not yet processed. This will cause a catalog failure on the collecting node. As demonstrated in “Dangling relationships to unrealized resources causes breakage”, apply the resource relationships when collecting the resources, not when declaring them.

Exported resources could increase the catalog build time and size

If many exported resources are collected, the time to build the catalog and its overall size will both increase. For example, if you have 10,000 nodes exporting their services, the time to build the catalog for a monitoring station collecting those services could be extreme. Use judiciously.

Exported resources provide slow but eventual consistency

If you boot up a thousand new instances, it will take time for each node to run Puppet, build the catalog, and have their exported resources available in PuppetDB to be collected. This works well for eventual consistency, but is not suitable for quickly changing environments in which services might not exist for more than a few minutes.

The number of concerns listed here might concern you. The main benefit of exported resources is that they are tightly integrated and immediately available with Puppet. They solve many problems well, but they are not a solution for every service discovery need. Service registration services like Consul and orchestration services like Kubernetes, DC/OS, and Docker Swarm might be more suitable for highly dynamic services.

Overriding and Modifying Declared Resources

Resource declarations can be influenced or adjusted off the page, or outside of the specific Puppet language declaration. There are a number of ways in which you can specify a resource’s attributes, relationships, or behaviors, both before and after the resource is declared. This section explores those capabilities.

Resource Default Statements

Resource default statements supply attribute values that will be used in any case for which the attribute is not explicitly declared on that resource type. For example, an Enterprise Linux node might want to set the require attribute of package resources to include a specific Yum repository.

This concept can be very useful for situations in which common values are used within a class or defined type. Here’s an example that attempts to simplify a number of similar file resources:

File {
  mode  => '0440',
  owner => 'nobody',
  group => 'nobody',
}

file { '/tmp/foo.txt':
  content => 'Foo',
}

file { '/tmp/bar.txt':
  content => 'Bar',
}

include baz

Unfortunately, resource default statements apply to everything in the same scope, such as declared classes. This means that an inherited resource default will affect not just the file resources shown here, but also the file resources in the baz class.

It gets worse. Because classes are added to the catalog only once, if at a later time another class declares baz earlier the resource defaults will suddenly not apply. This parse-order dependency means that resource defaults can affect the behavior of included class resources without warning, and change suddenly due to an apparently irrelevent change far outside the scope of these two modules. In essence, the inherited nature of resource defaults violate the principles of modular design and SoC.

Resource default inheritence can affect the behavior of resources in unrelated modules without any intent on your part to do so. Many well-meaning operators add a default attribute to a given resource, only to discover that one or more modules was reliant on the default behavior. Such problems are often very difficult to track down.

To mitigate the problems associated with default inheritance, we recommend that you declare only resource default statements in leaf classes—classes that do not declare any other class.

Per-expression defaults provide a solution

Thankfully, there’s a better way to provide default attributes that manages to be both safer to use and easier to read. Example 5-6 presents the code.

Example 5-6. Using a defaults hash to set file permissions
$my_file_defaults = {
  mode  => '0440',
  owner => 'nobody',
  group => 'nobody',
}

file {
  default:
    * => $my_file_defaults,
    ;;

  '/tmp/foo.txt':
    content => 'Foo',
    ;;
}

include baz

This example works by applying two techniques:

  • It uses the splat (*) operator to assign the hash of attribute values.

  • By assigning the hash to the per-expression resource default, it allows the resource to explicitly override any defaults.

This stacked resource declaration is a little longer, but it greatly increases readability by explicitly declaring which set of defaults values to use. Nothing happens off the page or through a parse-order-dependent inheritence. And the baz class is completely unaffected by this default, although it could explicitly use the same hash to provide default values if desired.

Per-expression defaults provide a way to avoid surprises

Although it’s a bad idea to use resource default statements, it’s a good idea to be tolerant of them when writing modules. There are two ways to avoid having resource default statements affect the functionality of your module:

  • If your resource has specific attribute requirements, set them explicitly instead of relying on defaults you expect.

  • Have your class set its own default for the resource. This default will be limited to your class and classes it declares.

  • Have your class inherit from an empty class. An explicit inherits statement forces the parent class to one of your own choosing, blocking flow-down inheritence.

In contrast, being tolerant can also mean not fighting valid uses of the module. Let the user specify defaults if the values (such as file owner) aren’t important for what the module does.

Resource Chaining

You cannot redeclare a resource relationship attribute, but you can add it after declaration, as shown in the following:

File['/etc/httpd/conf/httpd.conf'] ~> Service['httpd']

This particular chain adds a notification for changes to the httpd.conf file to the httpd service. This is exactly the same as adding notify => Service['httpd'] to the file declaration, or subscribe => File['/etc/httpd/conf/httpd.conf'] to the service declaration. The only difference is that we’ve done it after the fact. This is a very powerful technique for use in wrapper modules or profiles to create associations between resources in unrelated modules.

A good guideline is that chaining should be used only between single-line statements, as demonstrated in the line of code above. Using resource chaining on larger resources can easily be overlooked, as shown here:

package { 'httpd':
  ensure => 'installed',
}
-> file { '/etc/httpd/conf.d/httpd.conf':
  ensure => 'present',
  group  => 'httpd',
  mode   => '0440',
}
~> service { 'httpd':
  ensure => 'running'
  enable => true,
}

As this code demonstrates, the chaining arrows don’t call attention to themselves. The notify and subscribe metaparameters would be more visible to the reader.

Resource Collectors

Resource collectors can also override resource attributes. Unlike resource chaining, resource collectors can override attributes for any matching resource, even if that attribute has already been defined. To illustrate this, in the next example, the file will contain the content bar, not foo.

file { '/tmp/example.txt':
  content => "foo
",
}

File <| title == '/tmp/example.txt'|> {
  content => "bar
"
}

Resource collectors can be very dangerous if used carelessly. The following code would cause every notify resource in the catalog to print YOLO:

Notify <||> {
  message => "YOLO
"
}

Resource collectors will realize any matching virtual resources. Be careful to limit the search expression to resources that you want to add to the node’s catalog. For example, the naive ordering implementation shown in the next example has unintended consequences:

Yumrepo <| |> -> Package <| |>

This example has a side effect of realizing every virtual Yumrepo and Package resource.

Resource Best Practices

In this section, we discuss a number of built-in types, custom types, and popular defined types for Puppet.

This section is not intended to be exhaustive; our focus is on best practices rather than just the usage of each resource type. Refer to the online documentation for each of the types referenced here to explore all the features of the type.

Spend some time reviewing the full list of built-in resource types as well as the list of Puppet supported and approved modules on the Puppet Forge. You will find many interesting custom resources and defined types among those modules.

Custom Resource Types

Built-in resource types are those that come installed with the Puppet agent. However, you should not limit yourself to using built-in types. Puppet long ago moved to a model of distributing new resource types in modules, and even spinning out built-in resources into their own modules. Built-in resources are not better in any objective way.

In this section, we reference a number of valuable resource types and providers that extend Puppet’s functionality. We discuss both officially supported and community modules here.

Tip

The official type reference includes only built-in types. You can get a list of all installed resource types and their documentation by using puppet describe. Try out puppet describe --list in your development environment.

The ACL type

The ACL type and provider allow for the management of Windows access control lists (ACLs). This resource type provides much finer control than the file resource permissions. This resource type does not manage file content; you must use it as a supplement to a standard file resource. You use the ACL resource when simple read/write/execute permissions are not sufficiently granular.

This resource type is provided by the puppetlabs/acl module.

The anchor type

The anchor resource was an early fix for the problem of child class containment for class-based relationships. For the most part, it has been superseded by the contain function call. You might occasionally encounter modules that still use this resource type.

Note

For more detail on the use and application of anchors, see “Anchors”.

Augeas providers

Augeas types are native resource types and providers that use the Augeas configuration editing tool to manage the underlying configuration files. Augeas providers is a set of modules written and managed by the Augeas team. These modules provide a number of new resource types for managing common files, a set of libraries for the creation of new Augeas resource types, and a number of Augeas providers for existing types.

These providers are more focused and user friendly than the built-in augeas resource type. We recommend using these modules rather than attempting to call Augeas directly.

You can deploy all of the providers by installing the herculesteam/augeasproviders module with the puppet module tool, or you can install them individually. You can best manage individual installations by using r10k to handle the long list of modules.

The datacat types

datacat_collector and datacat_fragment serve a function similar to the concat defined type discussed in “The concat defined type”. Instead of sending file fragments to be combined by the agent, datacat creates data structures in the catalog, which can be rendered into a template or utilized by other resources.

You can use this module to build very complex configuration files using templates. datacat works by merging data structures from the various fragment resources. datacat then generates a new resource based on the target_resource parameter given to datacat_collector, and passes the merged data structure to that resource.

Try to avoid crossing module boundaries with datacat. If you need to declare fragments from multiple modules, use a defined type in the module that contains the collector as the external interface for adding fragments.

Combining datacat and concat is where wizards tremble in fear. Generating the datacat fragments and collectors during the catalog build while leaving the concat fragments to be assembled by the agent will create a complex web of interactions to debug.

datacat_fragment and datacat_collector are provided by the richardc/datacat module.

The file_line resource type

You can use the file_line resource type to add or remove lines from arbitrary text files. This resource type is useful when you need to perform edits scoped more narrowly than the entire file but where a native resource is unavailable.

Warning

Using this resource to adjust a file managed by a different module will create constant churn as the modules each attempt to fix the file. You should use this only for files that cannot be managed by another module.

This resource type is provided by the puppetlabs/stdlib module.

The match attribute

The match attribute allows file_line to modify existing entries in a configuration file, rather than simply adding new entries. By default, file_line simply checks to see whether the desired line is present, and adds it if not.

match expects a Ruby regex, and will throw an error if the expression does not also match the line to be inserted. This behavior prevents erroneous notification loops and the creation of duplicate lines. It is poor practice to try to trick the resource type using regex OR matches. Instead, use one resource to remove the old line, and another resource to insert the new line.

The after attribute

The after attribute allows limited context awareness for the file_line type. You can use it to insert a line in a specific part of the file, immediately after the line matched by the after attribute. Normally, new entries are simply appended to the end of the file.

If you need more complex context awareness, consider using an augeas resource instead of a file_line resource.

Beware that not all versions of file_line support this attribute. If you are using an older bundled version of stdlib, this attribute might not be available to you.

Note that the file_line after attribute has no relationship1 to the before and require metaparameters.

The gnupg_key type

This resource type allows for the management and distribution of GNU Privacy Guard (GPG) keys. GPG keys are widely used by installers to validate package integrity. This resource provider can help cryptographically validate yumrepo resources and other package downloads.

This resource type is provided by the golja/gnupg module.

The ini_setting and ini_subsetting resource types

These generic types allow granular management of specific items in INI files. Because the INI format is extremely common, you are almost guaranteed to find use for this resource type.

ini_setting provides per-line management of key/value pairs. Because you can declare the delimiter, spacing, and section(s), it is capable of managing virtually any file comprising simple key/value pairs. ini_setting is often used as a base for other resource providers. For example, it is the base provider for the yumrepo resource type.

Tip

puppetlabs/inifile accepts parameters to specify delimiters and headers, allowing it to manage files that are not immediately recognizable as having INI syntax.

ini_subsetting provides element-level management of INI values. The scope of the ini_subsetting type is extremely tight, allowing multiple modules to safely manage elements on a single line.

These resource types can help manage the following:

  • /etc/sysconfig/ files containing Bash variables

  • Java properties files

  • puppet.conf and other puppet configuration settings

  • Hashicorp HCL configuration files

These resource types are provided by the puppetlabs/inifile module.

The registry type

The registry type is invaluable for managing registry entries in Windows environments. This resource type is far more efficient and reliable than alternative approaches for editing registry keys.

This resource type is provided by the puppetlabs/registry module.

The reboot type

The reboot type allows Puppet to restart the node it is running on. Its primary purpose is to restart a system so that configuration changes can take effect. On Linux nodes a restart is usually required only to upgrade the kernel or install a kernel extension. Windows nodes might need to restart for routine software installation.

The reboot resource type is inactive unless it receives a notification signal. You can have multiple instances of reboot in your catalog, so long as each one has a unique resource title. It is safe to schedule multiple reboots from one or multiple resources.

If your Puppet runs are idempotent, only a single reboot should be required at the end of your run. The goal is to reach a consistent state during the run, rebooting only for the state to take effect. Ideally, no further changes should be pending when the system comes up. If further changes are needed after the reboot, your code is convergent rather than idempotent. On some platforms, this might be a necessary evil.

If you use reboot in a convergent way, it’s a good idea to schedule a Puppet run at system startup. This can help to avoid a very long convergence cycle, and is useful for cases in which additional postreboot configuration tasks need to be performed. Without an immediate postreboot Puppet run, your overall configuration time can easily exceed an hour. Much of this time will be spent waiting for the next scheduled run.

Note that the reboot type will reboot the system only if it the reboot resource receives a notification signal from another resource. Consider how you contain and isolate the reboot resource in order to prevent an errant signal from inadvertantly triggering a reboot.

The reboot resource type is provided by the puppetlabs/reboot module.

The vcs_repo type

The vcs_repo type allows you to create and manage version control repositories. Its primary use is for cloning repositories to a managed system.

This resource type is useful for the distribution of files and data from repositories other than the ones that host your Puppet code. There are situations for which you might want to have Puppet deploy revision-controlled content but do not want to commit that content to your Puppet repository. Cloning your website to your web servers is one such example.

This approach helps to enforce separation of site content away from your site-logic and site-specific configuration data. It’s also useful for enforcing access control requirements; each repository can have its own access rules.

Although you can use vcs_repo to distribute Puppet manifests, r10k provides significantly more deployment-oriented features. For more details, see Chapter 9.

This resource type is provided by the puppetlabs/vcs_repo module. It’s a good idea to also check the available providers; as of this writing Git, Mercurial, SVN, Bazaar, Perforce, and CVS are supported.

The windows_env native type and provider

The windows_env type allows you to manage Windows environment variables. A surprisingly large number of applications use environment variables to modify pathing or other behaviors. This module is invaluable in Windows environments.

This resource type is provided by the badgerious/windows_env module.

Useful Defined Types

Defined types are not strictly speaking Puppet resources types. They are named collections of resources and logic bound together. They allow you to design a named abstraction layer that provides context for a group of resources.

We reference a few public-defined types here. These defined types extend Puppet’s capabilities in useful ways. They are also dramatically simpler and more reliable than creating a new resource type to perform the same task.

The concat defined type

The concat defined type allows you to build files from fragments, combining multiple file resources into a single complete file. This resource provider is useful when you need to produce files from large multiline chunks. It retains most of the efficiency of the file resource, but it allows multiple classes or instances of a defined type to contribute fragments.

The downside of concat is that it is not always intuitive. Although it’s much simpler to use than datacat, it is not nearly as simple as a conventional file resource. Like a file resource, concat must have complete control over the resulting output file. Ultimately, it’s designed to share inputs, not outputs.

Another consideration is that fragments must be defined in a consistent way for each file. It can be a good idea to wrap the file fragments in defined types to simplify and control their interface.

The concat defined type is provided by the puppetlabs/concat module and is bundled with Puppet Enterprise.

The account defined type

When managing users with Puppet, it is often useful to abstract away the creation of user, group, and other associated resources in order to better organize your data in Hiera, to remove redundent data, and to simplify account creation.

There are several good account modules available, including the pe_account module bundled with Puppet Enterprise. You can use these modules as-is or extended in order to meet your site-specific requirements.

The camptocamp/accounts module is another popular account defined type. It does not conflict with pe_account.

Summary

Resources are the fundamental building blocks of state management. Understanding the capabilities of Puppet’s resource types is invaluable for building comprehensive, scalable code. Understanding the ways in which you can declare, remove, and audit resources is invaluable for converting data about your environment into real configurations.

Here are this chapter’s takeaways:

  • Use resources to implement changes.

  • Use the best resource type for the task at hand.

  • Use resource types that provide context and demonstrate intent.

  • Virtual resources allow for conditional inclusion of optional resources from a list.

  • Understand your resource relationships and how a notification affects resources.

  • Understand how a particular resource type will scale.

  • Avoid implementations that will require thousands of resources.

  • Understand what impact an approach will have as your site grows.

  • Don’t reinvent the wheel.

  • Avoid exec resources whenever possible.

  • Avoid creating long chains of fragile resources.

  • KISS: there’s no need to use augeas when a simple file will do.

1 No pun intended.

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

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