The language-puppet website.

Work with your manifests!

Sneak Peak at the Language-puppet PuppetDB Testing Features

I always thought that one of the most rewarding effect of Puppet is that the whole system gets configured automatically as nodes are added. For me, the main, and for a long time sole, manifestation of this property is in the configuration of the Nagios servers. The built-in types lend themselves pretty well to this exercice.

Now, with PuppetDB, we have a simple and powerful way to create new effects, beyond what could be achieved with just exported resources (I believe it used to be possible before PuppetDB, but required black magic in the template files). I will demonstrate a typical use case, along with a sneak peak of the testing features that will appear in the next version of language-puppet.

Let’s say we have an HTTP proxy and several groups of servers acting as backends. You wish to be able to add servers to the pool just by running the agent on them. The site.pp should look like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
node 'proxy' {
    include haproxy
}

node 'back1' {
    haproxy::backend { $hostname:
        backend_type   => 'web',
        backend_server => $hostname,
        backend_port   => 80;
    }
}
node 'back2' {
    haproxy::backend { $hostname:
        backend_type   => 'web',
        backend_server => $hostname,
        backend_port   => 80;
    }
}

The haproxy::backend defines are empty, and the interesting part is in the haproxy class:

1
2
3
4
5
6
7
8
9
10
11
class haproxy {
    $backends = pdbresourcequery(
        ['and',
            ['=',['node','active'],true],
            ['=','type','Haproxy::Backend']
        ],'parameters')

    file { '/etc/haproxy/haproxy.cfg':
        content => template('haproxy/config.erb');
    }
}

The pdbresourcequery function comes from this excellent module, and has been included natively in language-puppet for a while. Its effect here is to fill the $backends variable with an array containing all resources that are of type Haproxy::Backend on any active node.

But now comes the complicated part: how are you supposed to write, and, more importantly, to test, the config.erb template ? As far as I know you can’t pull this off with puppet-rspec (and it is way too slow anyway). With the new testing API, you can write a simple program like this:

Main.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
module Main where

import qualified Data.Map as Map
import Control.Monad (void)
import Puppet.Testing
import Puppet.Interpreter.Types
import Facter

main :: IO ()
main = do
    qfunction <- testingDaemon Nothing "." allFacts
    void $ qfunction "back1"
    void $ qfunction "back2"
    (proxycatalog, _, _) <- qfunction "proxy"
    case Map.lookup ("file","/etc/haproxy/haproxy.cfg") proxycatalog of
        Nothing -> error "could not find config file"
        Just f  -> case Map.lookup "content" (rrparams f) of
                       Just (ResolvedString s) -> putStrLn s
                       _ -> error "could not find content"

Line by line, this program does:

  • lines 1-10 : various headers
  • line 11 : the catalog computing function is initialized, using the new testing system
  • line 12 : the catalog for the node back1 is computed, and stored into the fake PuppetDB
  • line 13 : same thing for back2
  • line 14 : same thing for proxy, but we keep the final catalog this time
  • lines 15-19 : the content of the /etc/haproxy/haproxy.cfg is displayed. This part is terrible and will be replaced by some helper soon.

The template groups the resources by their “backend_type” attribute, creates a backend block for each of them, and populates the blocks with the corresponding backends.

modules/haproxy/templates/config.erb
1
2
3
4
5
6
7
<%- backends = scope.lookupvar('haproxy::backends').group_by do |x| x["backend_type"] end -%>
<%- backends.each do |backendname, backends| -%>
backend <%= backendname %>
    <%- backends.each do |backend| -%>
        server <%=backend["backend_server"]%> <%=backend["backend_server"]%>:<%=backend["backend_port"]%>
    <%- end -%>
<%- end -%>

And the output is :

1
2
3
backend web
        server back2 back2:80
        server back1 back1:80

It works! With this feature, it will soon be possible to test and experiment with the most complex aspects of inter-node interactions.

Comments