Containment

Containment is what controls the order in which the various parts of your Puppet code are executed. Containment is the relationship that resources have to classes and defined types, determining what has to happen before other things can happen.

Classes and defined type instances contain the resources they declare. Contained resources are not applied before the container begins, and they finish before the container finishes.

This means that if any resource or class forms a relationship with the container, it forms the same relationship with every resource inside the container.

Consider this example:
class ntp {
  file { '/etc/ntp.conf':
    ...
    require => Package['ntp'],
    notify  => Service['ntp'],
  }
  service { 'ntp':
    ...
  }
  package { 'ntp':
    ...
  }
}

include ntp
exec {'/usr/local/bin/update_custom_timestamps.sh':
  require => Class['ntp'],
}
Here, exec['/usr/local/bin/update_custom_timestamps.sh'] would happen after every resource in the ntp class, including the package, file, and service.

Containment allows you to notify and subscribe to classes and defined resource types as though they were a single resource.

Containment of resources

Resources of both native and defined resource types are automatically contained by the class or defined type in which they are declared.

Containment of classes

Unlike with resources, Puppet does not automatically contain classes when they are declared inside another class (by using the include function or resource-like declaration). But in certain situations, having classes contain other classes can be useful, especially in larger modules where you want to improve code readability by moving chunks of implementation into separate files.

You can declare a class in any number of places in the code, allowing classes to announce their needs without worrying about whether other code also needs the same classes at the same time. Puppet includes the declared class only one time, regardless of how many times it's declared (that is, the include function is idempotent). Usually, this is fine, and code shouldn't attempt to strictly contain the class. However, there are ways to explicitly set more strict containment relationships of contained classes when it is called for.

When you're deciding whether to set up explicit containment relationships for declared classes, follow these guidelines:
  • include: When you need to declare a class and nothing in it is required for the enforcement of the current class you're working on, use the include function. It ensures that the named class is included. It sets no ordering relationships. Use include as your default choice for declaring classes. Use the other functions only if they meet specific criteria.
  • require: When resources from another class should be enforced before the current class you're working on can be enforced properly, use the require function to declare classes. It ensures that the named class is included. It sets relationships so that everything in the named class is enforced before the current class.
  • contain: When you are writing a class in which users should be able to set relationships, use the contain function to declare classes. It ensures that the named class is included. It sets relationships so that any relationships specified on the current class also apply to the class you're containing.

The require function

The require function is useful when the class you're writing needs another class to be successfully enforced before it can be enforced properly.

For example, suppose you're writing classes to install two apps, both of which are distributed by the Chocolatey package manager, for which you've created a class called chocolatey. Both classes require that Chocolatey be properly managed before they can each proceed. Instead of using include, which won't ensure Chocolatey's resources are managed before it installs each app, use require.
class myapp::install {
  # works just like include, but also creates a relationship
  # Class['chocolatey'] -> Class['myapp::install']
  require chocolatey
  package { 'myapp':
    ensure => present,
  }
}

class my_other_app::install {
  require chocolatey
  package { 'my_other_app':
    ensure => present,
  }
}

The contain function

Use the contain function to declare that a class is contained. This is useful for when you're writing a class in which other users should be able to express relationships. Any classes contained in your class will have containment relationships with any other classes that declare your class. The contain function uses include-like behavior, containing a class within a surrounding class.

For example, suppose you have three classes that an app package (myapp::install), creating its configuration file (myapp::config), and managing its service (myapp::service). Using the contain function explicitly tells Puppet that the internal classes should be contained within the class that declares them. The contain function works like include, but also adds class relationships that ensure that relationships made on the parent class also propagate inside, just like they do with resources.
class myapp {
  # Using the contain function ensures that relationships on myapp also apply to these classes
  contain myapp::install
  contain myapp::config
  contain myapp::service
  
  Class['myapp::install'] -> Class['myapp::config'] ~> Class['myapp::service']
}
Although it may be tempting to use contain everywhere, it's better to use include unless there's an explicit reason why it won't work.