Programming, Technology

Multiple Class Definitions With Puppet

One issue we’ve discovered with running puppet orchestration is bumping into classes being multiply defined. In our setup all hosts get a generic role which among other things contains a definition of the foreman puppet class to manage the configuration file (agent stanza) on all hosts. The problem comes when you include the puppet master role which also pulls in the puppet class.

With hindsight the two roles should have been separated out so that all hosts include puppet::agent and the master(s) include puppet::master. But we are:

  1. Severely time constrained being a start-up
  2. Wish to leverage updates and improvements automatically from the community

As such we just roll with the provided API and have to deal with the fallout. First port of call for a newbie is to try ignore the definition with some conditional code

class profile::puppet {
  if !defined('profile::puppetmaster') {
    class { '::puppet': }
  }
}

or

class profile::puppet {
  if !defined(Class['profile::puppetmaster']) {
    class { '::puppet': }
  }
}

 

Either of which will land you with the same problem of this puppet definition clashing with the one defined in profile::puppetmaster. It’s a common mistake, but one that can be remedied. Oddly enough somehow the second example did work in our production environment, but upon playing about with the pattern to understand its inner workings I just could not recreate! This led to the development of the following. Can’t keep a good academic down even when in the role of sysadmin!

Allowing Multiple Class Definitions In Multiple Locations

Now here is how my compiler background head works. The previous examples rely on the entire manifest being parsed before the defined function can be evaluated, at which point you’re already too late. If however the conditional could be made to be evaluated at file parse time, and if it resolves to false, then why bother parsing the code block?

class profile::puppet {
  if $::fqdn != $::puppetmaster {
    class { '::puppet': }
  }
}

Here we are comparing facts, which are available before every run, and can be evaluated at parse time ($::puppetmaster is provided by foreman). The code works exactly as you’d expect every time regardless of ordering.

Obviously this may not be the official puppet methodology and more than likely dependant on the underlying implementation of the parsing and execution engine. It does provide a quick get out of jail free option for when resource is unavailable to do the job properly.