The Puppet Labs Issue Tracker has Moved: https://tickets.puppetlabs.com

Module Anchors Pattern

This pattern has been vetted on tech and is a MUST for all modules which may be used with Puppet 2.6.x or Puppet Enterprise 1.x.

This pattern should be used until Bug #8040 is addressed and released with PE.

The problem this pattern solves:

The end-user of a module posted to the Forge has a reasonable expectation of being able to establish relationships easily to the module. In addition, the end user of the module SHOULD NOT have to modify the source manifests to get this desired functionality.

Consider the desired use case of the activemq module. The end user of this module wants Puppet to manage Java first, then manage ActiveMQ, then manage some other resources after all of this (e.g. MCollective):

    node default {
      notify { 'alpha': }
      ->
      class  { 'java':
        distribution => 'jdk',
        version      => 'latest',
      }
      ->
      class  { 'activemq': }
      ->
      notify { 'omega': }
    }
 

With Puppet 2.6.x, there is no containment relationship between a class and any subsequent classes the class declares. If we have the following code in activemq/manifests/init.pp, the resources contained in the “implementation” classes will “float” off in the graph and will not be contained within Class[‘activemq’]:

class activemq(
  $version       = 'present',
  $ensure        = 'running',
  $server_config = 'UNSET'
) {

  validate_re($ensure, '^running$|^stopped$')
  validate_re($version, '^present$|^latest$|^[._0-9a-zA-Z:-]+$')

  $version_real = $version
  $ensure_real  = $ensure

  # Since this is a template, it should come _after_ all variables are set for
  # this class.
  $server_config_real = $server_config ? {
    'UNSET' => template("${module_name}/activemq.xml.erb"),
    default => $server_config,
  }

  # Anchors for containing the implementation class
  anchor { 'activemq::begin': }

  class { 'activemq::packages':
    version => $version_real,
    notify  => Class['activemq::service'],
  }

  class { 'activemq::config':
    server_config => $server_config_real,
    require       => Class['activemq::packages'],
    notify        => Class['activemq::service'],
  }

  class { 'activemq::service':
    ensure => $ensure_real,
  }

  anchor { 'activemq::end': }

}

NOTE: Anchor resources are being declared, but NOT any relationships to the implementation classes. We see two disconnected subgraphs as a result, even though the end user specified an explicit ordering of the resources:

Anchor - No Relationships

It is possible to satisfy the end-user’s request to establish a before OR a require relationship to the module so long as implementation classes are related back to the composite Class[‘activemq’], but it is NOT possible to satisfy BOTH before and require because a cycle would be created in the graph.

The Anchor solution:

This solution is designed to solve the problem given 2.6.x constraints and should be re-visited when Stages is improved, weaker forms of relationship edges are added to the product, or containment edges may be expressed in the language easily.

This solution SHOULD be used for all modules which may be used with Puppet Enterprise 1.x.

For any class that declares other classes, a module author SHOULD contain the declared classes by declaring a begin and an end anchor, then relating the declared classes to these anchors. These relationships allow the end user of the module to declare resources that require the composite class, or have a before relationship to the composite class, or both.

Adding relationships to the anchors fully contains the implementation classes within the composite class.

class activemq(
  $version       = 'present',
  $ensure        = 'running',
  $server_config = 'UNSET'
) {

  validate_re($ensure, '^running$|^stopped$')
  validate_re($version, '^present$|^latest$|^[._0-9a-zA-Z:-]+$')

  $version_real = $version
  $ensure_real  = $ensure

  # Since this is a template, it should come _after_ all variables are set for
  # this class.
  $server_config_real = $server_config ? {
    'UNSET' => template("${module_name}/activemq.xml.erb"),
    default => $server_config,
  }

  # Anchors for containing the implementation class
  anchor { 'activemq::begin':
    before => Class['activemq::packages'],
    notify => Class['activemq::service'],
  }

  class { 'activemq::packages':
    version => $version_real,
    notify  => Class['activemq::service'],
  }

  class { 'activemq::config':
    server_config => $server_config_real,
    require       => Class['activemq::packages'],
    notify        => Class['activemq::service'],
  }

  class { 'activemq::service':
    ensure => $ensure_real,
  }

  anchor { 'activemq::end':
    require => Class['activemq::service'],
  }

}

An implementation of the anchor type is provided in the puppet stdlib.

Here is the resulting graph: (Notice how the implementation classes are fully contained between the two anchors AND the resources the end user has specified without modifying the Module manifests)

Anchor - Both Relationships

Anchor_both_anchors.png - Anchor - Both Anchors (329 KB) Jeff McCune, 12/14/2011 06:00 pm

Anchor_no_relationships.png - Anchor - No Relationships (229 KB) Jeff McCune, 12/14/2011 06:00 pm

Anchor_no_relationships.png (13.2 KB) Kelsey Hightower, 04/09/2013 04:15 pm