Managing order and dependencies
The Puppet language is declarative and not procedural; it defines states as follows: the order in which resources are written in manifests does not affect the order in which they are applied to the desired state.
Note
The Puppet language is declarative and not procedural. This is not entirely true—contrary to resources, variables definitions are parse order dependent, so the order used to define variables is important. As a general rule, just set variables before using them, which sounds logical, but is procedural.
There are cases where we need to set some kind of ordering among resources, for example, we might want to manage a configuration file only after the relevant package has been installed, or have a service automatically restart when its configuration files change. Also, we may want to install packages only after we've configured our packaging systems (apt sources, yum repos, and so on) or install our application only after the whole system and the middleware has been configured.
To manage these cases, there are three different methods, which can coexist:
- Use the meta parameters
before
,require
,notify
, andsubscribe
- Use the chaining arrows operator (respective to the preceding meta parameters:
->
,<-
,<~
,~>
) - Use run stages
In a typical package/service/configuration
file example, we want the package to be installed first, configure it, and then start the service, eventually managing its restart if the config
file changes.
This can be expressed with meta parameters:
package { 'exim': before => File['exim.conf'], } file { 'exim.conf': notify => Service['exim'], } service { 'exim': }
This is equivalent to this chaining arrows syntax:
package {'exim': } -> file {'exim.conf': } ~> service{'exim': }
However, the same ordering can be expressed using the alternative reverse meta parameters:
package { 'exim': } file { 'exim.conf': require => Package['exim'], } service { 'exim': subscribe => File['exim.conf'], }
They can also be expressed like this:
service{'exim': } <~ file{'exim.conf': } <- package{'exim': }
Run stages
Puppet 2.6 introduced the concept of run stages to help users manage the order of dependencies when applying groups of resources.
Puppet provides a default main stage; we can add any number or further stages, and their ordering, with the stage
resource type and the normal syntax we have seen:
stage { 'pre': before => Stage['main'], }
The normal syntax is equivalent to:
stage { 'pre': } Stage['pre'] -> Stage['main']
We can assign any class to a defined stage with the stage
meta parameter:
class { 'yum': stage => 'pre', }
In this way, all the resources provided by the yum
class are applied before all the other resources (in the default main stage).
The idea of stages at the beginning seemed a good solution to better handle large sets of dependencies in Puppet. In reality, some drawbacks and the augmented risk of having dependency cycles make them less useful than expected. A thumb rule is to use them for simple classes (that don't include other classes) and where it is really necessary (for example, to set up package management configurations at the beginning of a Puppet run or deploy our application after all the other resources have been managed).