Tim Sharpe

Home :: Projects :: About Me

Auto-notify Resources From Your Puppet Types

So, I ran into a bit of a problem yesterday. I was working on a Puppet module for the Sensu monitoring framework, part of which involved writing a custom Puppet type to manage the configuration of service checks. Normally this is a trivial matter, however in this case, the checks must be configured on both the server and the client and must therefore be able to notify either the server process or the client process or both to restart after a configuration change.

Obviously, I couldn't just do this:

1 sensu_check { 'mycheck':
2   notify => [
3     Service['sensu-client'],
4     Service['sensu-server'],
5   ],
6 }

As if the host was only running the client process, this would result in an error during the Puppet runs.

One option would be to use the following mess:

 1 if(defined(Service['sensu-client']) && defined(Service['sensu-server'])) {
 2   Sensu_check {
 3     notify => [
 4       Service['sensu-client'],
 5       Service['sensu-server'],
 6     ],
 7   }
 8 } elsif defined(Service['sensu-client']) {
 9   Sensu_check {
10     notify => Service['sensu-client'],
11   }
12 } else {
13   Sensu_check {
14     notify => Service['sensu-server'],
15   }
16 }

Not the most elegant solution in the world. Ideally what I needed was something like the existing autorequire functionality that creates require relationships between your custom type and the named resources automatically but only if the resources exist in the node catalogue.

Unfortunately, such functionality doesn't exist (yet), so I had to go some digging around in Puppet's codebase to implement it. The first thing to do was to establish a way to create arbitrary notify relationships. This turned out to be easily done:

1 Puppet::Type.newtype(:mytype) do
2   def initialize(*args)
3     super
4     self[:notify] = [
5       "Service[myservice]",
6       "Service[myotherservice]",
7     ]
8   end
9 end

Now, all I needed to do was make it so that it only notified the resources that actually existed in the node's manifest

1 Puppet::Type.newtype(:mytype) do
2   def initialize(*args)
3     super
4     self[:notify] = [
5       "Service[myservice]",
6       "Service[myotherservice]",
7     ].select { |ref| catalog.resource(ref) }
8   end
9 end

The moral of this extremely drawn out story? Anything is possible in Puppet if you're willing to get your hands dirty. And this should really be a built in feature *hint*.