Product SiteDocumentation Site

Chapter 7. Language Tutorial

7.1. Resources
7.1.1. Resource Defaults
7.1.2. Resource Collections
The language puppet uses has a strong focus on making the specification of types as easy as possible within a heterogeneous set of resources shared over all the systems you are managing.

7.1. Resources

Resource are built by using a type and specifying a list of attributes to that type. Each type has a specific list of supported attributes. You can find the complete list of native types and their attributes on http://reductivelabs.com/trac/puppet/wiki/TypeReference
A simple example resource is:
file { "/etc/passwd":
    owner => "root",
    group => "root",
    mode => 644
}
Any system on which this resource is managed will use it to verify that the /etc/passwd is owned by the root user and group, and has permissions 644. The field before the colon, in this case "/etc/passwd", is the resource's title, which can be used to refer to the resource later in the manifest.
For simple resources that don't vary much between systems, a single title to refer to is sufficient. Many system resources though vary from system to system. The OpenSSH Client package for example is called openssh-client on Debian systems, while it is openssh-clients on Fedora systems. To create the package resource to get the OpenSSH Client package installed through a manifest, Puppet allows you to specify:
package { "openssh-clients":
    ensure => installed,
    name => $operatingsystem ? {
        "Debian" => "openssh-client",
        default => "openssh-clients"
    }
}
Puppet on a Debian system will now direct the package manager to install "openssh-client", while on other systems, the package manager is told to install "openssh-clients".

Important

Note that the name of this resource is now conditional, and thus virtually unpredictable from within the rest of the manifests, but still if you wanted to require the OpenSSH Client package you do not need to conditionally require the resource, but instead can use the title of the resource, "openssh-clients".
Even more complex resources are file resources that are located in different paths on different operating systems, such as the configuration file for the OpenSSH Client package.
file { "/etc/ssh/ssh_config":
    path => $operatingsystem ? {
        "Solaris" => "/usr/local/etc/ssh/ssh_config",
        "Darwin"  => "/etc/ssh_config",
        "Debian"  => "/etc/openssh/ssh_config",
        default   => "/etc/ssh/ssh_config"
    },
    require => Package["openssh-clients"]
}
The title for this resource is "/etc/ssh/ssh_config", while the path of the file managed is conditional to the value of the $operatingsystem variable. The $operatingsystem variable in this case is a fact describing the operating system name. Note that the title of the resource is used as a path if the path parameter had not been seperately configured.
Also note that it is the title of the Package["openssh-clients"] used in the reference to the OpenSSH Client package resource.

Note

To refer to a resource, you can use it's title, or specify the magic attribute "alias".

7.1.1. Resource Defaults

You can specify resource defaults so that the default parameter is always used when you make a resource by capitalizing the resource and not specifying a title. With the exec type for example, the command either must use a fully qualified path, or the path parameter to the exec type must be included.
Exec {
    path => [
        "/bin/",
        "/usr/bin/",
        "/usr/local/bin/",
        "/sbin/",
        "/usr/sbin/",
        "/usr/local/sbin/"
    ]
}
This makes the following work
exec { "echo this works": }
While normally you would have to specify (in each exec resource):
exec { "/bin/echo this works" }
Needless to say, echo does not exist in /bin/ on all operating sytems you may want to manage.
This type of defaults you would normally store in a file named after the type you specify the defaults for, in the directory utils/ beneath the manifests/ directory.

7.1.1.1. Conditional Resource Defaults

To facilitate the use of different defaults per operating system for example, you can set the resource defaults conditionally, using either if-then-else statements, or using case statements.
Case statements let you set the resource defaults as follows:
case $operatingsystem {
    "Darwin": {
        Exec {
            path => [
                "/foo/bin/",
                "/bar/sbin/",
                (...etc...)
            ]
        }
    }
    "Solaris": {
        Exec {
            path => [
                "/baz/bin/",
                "/baz/sbin/",
                (...etc...)
            ]
        }
    }
    default: {
        Exec {
            path => [
                "/bin/",
                "/usr/bin/",
                (...etc...)
            ]
        }
    }
}

Important

Resource defaults are not global, but apply to every resource in the current scope. If you define the resource defaults within a class, then the resources within that class and all sub-classes will have defaults set. The only way you can currently specify global defaults is to define them outside of any classes.

7.1.2. Resource Collections

There are two ways you can combine multiple resources: Classes and definitions. Classes are only interpreted once per node. Definitions on the other hand can be used as custom types and are meant to be interpreted more then once, each time with different parameters.

7.1.2.1. Classes

Classes are introduced by using the class keyword, and support a simple form of inheritence. Subclasses are allowed to override resources defined in parent classes.
class unix {
    file { [
            "/etc/passwd",
            "/etc/group"
        ]:
        owner => root,
        group => root,
        mode => 644
    }
}

class freebsd inherits unix {
    File["/etc/passwd"] {
        group => "wheel"
    }
    File["/etc/group"] {
        group => "wheel"
    }
}
You can use the undef keyword when overriding a resource to make the child class act as if the value had never been set in the parent:
class freebsd inherits unix {
    File["/etc/passwd"] {
        group => undef
    }
}
In this example, nodes which include the unix class will have the password file forced to group root, while nodes including freebsd would have the password file group ownership left unmodified.
It is also possible to add additional values to resource parameters using the +> operator:
class apache {
    service { "apache":
        require => Package["httpd"]
    }
}

class apache-ssl inherits apache {
    # host certificate is required for SSL to function
    Service["apache"] {
        require +> File["apache.pem"]
    }
}
The above effectively makes the require parameter for the Service["apache"] resource in the apache-ssl class equal to [ Package["httpd"], File["apache.pem"] ].
You can add multiple values by separating each value with commas:
class apache {
    service { "apache":
        require => Package["httpd"]
    }
}

class apache-ssl inherits apache {
    Service["apache"] {
        require +> [
            File["apache.pem"],
            File["/etc/httpd/conf/httpd.conf"]
        ]
    }
}
The above would make the require parameter in the apache-ssl class equal to [ Package["httpd"], File["apache.pem"], File["/etc/httpd/conf/httpd.conf"] ].
Like resources, you can also require a class, like so:
class apache {
    service { "apache":
        require => Class["squid"]
    }
}
In this case, the Class["squid"] will need to be applied successfully before the Service["apache"] resource is applied on the puppet.
Namespacing
Classes such as the apache class in one of the forementioned examples, sub-classed by the apache-ssl class, can also be defined within another class:
class apache {
    service { "apache":
        require => Package["httpd"]
    }

    class ssl inherits apache {
        Service["apache"] {
            require +> [
                File["apache.pem"],
                File["/etc/httpd/conf/httpd.conf"]
            ]
        }
    }
}
In this case, using the ssl subclass of apache would become include apache::ssl.
The main difference (operational wise) between the two styles is that if you make a module out of the first above mentioned classes, both classes would have to be in their own file, so as to facilitate automatic loading by puppet. If you do not do this, puppet cannot find the class(es) that have a different name from the name of the module. On its own there is nothing wrong with having each class in its own file, however with large modules this tends to generate a lot of files, something one may wish to avoid.

7.1.2.2. Definitions

Definitions are very similar to classes, but are introduced with the define keyword, and do not allow inheritance. Definitions also take parameters, which classes do not.
class yum {
    define repository($enable = true) {
        file { "/etc/yum.repos.d/$name.repo":
            mode => 644,
            owner => "root",
            group => "root",
            backup => false,
            links => follow,
            source => $enable ? {
                true => [
                    "puppet:///yum/$os/$osver/repos/$name.repo"
                ],
                default => [
                    "puppet:///yum/$os/$osver/repos/$name.repo.disabled"
                ]
            }
        }
    }
}
This definition can be used as follows:
node 'node1.example.com' {
    yum::repository { "custom":
        enable => true
    }
}
Now, node1.example.com gets a file /etc/yum.repos.d/custom.repo from puppet:///yum/$os/$osver/repos/custom.repo.

Note

The above example makes use of $os and $osver variables you have to set first. See Section 11.1, “Setting $os and $osver” for more details.