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

Managing resource dependencies

Introduction

Declaring dependencies between Puppet resources is required for a couple of reasons:

  • To process resources in correct order
  • Allow node to have a functional configuration after the first run
  • Avoid getting tons of errors to system logs

Special care needs to be taken to avoid circular dependencies.

Suggested module layout

In my case, classes are split into several ‘'atomic subclasses’‘. These are combined by ’‘combining classes’‘ that are then imported in node definitions. For example, the Apache module has the following core atomic subclasses:

  • apache::install: installs Apache
  • apache::config::common: common configuration steps taken regardless of what this instance of Apache will do
  • apache::config::ssl::enablemod: activate mod_ssl
  • apache::config::ssl::certs: configure SSL certificates
  • apache::service: enable Apache on boot

In addition, there are several supporting atomic subclasses:

  • apache::iptables::nonssl: open port 80 in the firewall
  • apache::iptables::ssl: open port 443 in the firewall
  • apache::monit: setup Monit rules for Apache

These subclasses are combined into usable pieces of code by two combining classes:

  • apache: Setup Apache to serve content through port 80
  • apache::ssl: Setup Apache to serve content using SSL/TLS and port 443

The dependencies are primarily declared between these classes, not resources. For example, a typical dependency chain looks like this:

  • ntp::monit depends on ntp::service
  • ntp::service depends on ntp::config
  • ntp::config depends on ntp::install
  • ntp::install depends on nothing

This layout avoids creating dependencies to any specific resources and focuses on the state of the service in question. If we didn’t do this, classes would break the very moment that a resource changes name or is deleted. States such as “NTP is configured” or “Apache is installed” should be stable in this regard. The contents of these subclasses can also be changed freely without any side-effects. The above layout also helps avoid circular dependencies. For example, monit requires postfix to operate properly. Similarly, postfix requires monit for service monitoring. So, we get circular dependencies if we depend on entire classes both ways. Depending on a subclass such as monit::service or postfix::service does not trigger this problem.

Occasionally, when a subclass contains several resources it’s necessary to declare dependencies to resources inside subclasses, or to “chain” them together. These dependencies don’t get propagated to other classes or node definitions, so they’re safe.