The language-puppet website.

Work with your manifests!

The Puppet Resources Application

This is the official demonstration of the capabilities of the language-puppet library. While it is pretty hackish, it is also quite useful, especially for people working a log with large manifests.

Installing the application

In order for the application to work, you will need a working Haskell environment for now. As it compiles into native code, it is pretty simple to generate a redistributable binary. As with most Haskell binaries, the only required library is libgmp.

The only trouble is the dependency on a Ruby script that will compute the complicated templates, as it would be way too much work to emulate fully this language. This means you will also need a ruby interpreter installed, and probably not much more.

So once you have a working cabal, and a recent GHC (all coming from your distribution or from The Haskell Platform), just type :

1
cabal install puppetresources

Checking the parser

You now have a puppetresources executable. If invoked with a single argument, it will parse a Puppet manifest file and show you how it parsed it :

1
2
3
4
5
6
$ puppetresources modules/git/manifests/init.pp 
class git () {
    package { "git-core":
        "ensure" => "installed";
    }
}

This is not terribly useful, but could be used to check for syntax errors.

Computing catalogs

This is the main usage of this application : computing whole catalogs. In order to do this you must invoke it with a path to a standard Puppet directory (such as /etc/puppet) and a node name :

1
2
3
4
5
6
7
8
9
10
11
$ puppetresources . test.nod
The defined() function is not implemented for resource references. Returning true at "./modules/apt/manifests/ppa.pp" (line 20, column 3)
The defined() function is not implemented for resource references. Returning true at "./modules/apt/manifests/key.pp" (line 38, column 7)
The defined() function is not implemented for resource references. Returning true at "./modules/apt/manifests/key.pp" (line 42, column 7)
anchor {
    "apt::builddep::glusterfs-server": #"./modules/apt/manifests/builddep.pp" (line 12, column 12)
        name => "apt::builddep::glusterfs-server";
    "apt::key/Add key: 55BE302B from Apt::Source
        debian_unstable": #"./modules/apt/manifests/key.pp" (line 32, column 16)
        name => "apt::key/Add key: 55BE302B from Apt::Source
        debian_unstable";

This will display the whole catalog as a large, top level, Puppet manifest, after the warnings. As not everything is implemented yet, there will probably be quite a few of them for real world catalogs.

You will notice that the relationship between resources is not yet supported, and that a class resource type is used. This is a placeholder for the future relationships system.

The typical use of this feature occurs during manifests development. It serves as a high level correctness checker, as it will fail about the same as the real Puppet application, but can be run on your workstation before pushing to the puppet master. It is also significantly faster than the Ruby code.

Debugging templates

Using the previous features helps a lot, but is not very useful when debugging templates. The reason is that the content parameter is displayed as a one line string, and is hard to read in most cases :

1
2
3
4
5
6
$ puppetresources . test.nod  2> /dev/null | grep content
    content => "# debian_unstable\ndeb http://debian.mirror.iweb.ca/debian/ unstable main contrib non-free\ndeb-src http://debian.mirror.iweb.ca/debian/ unstable main contrib non-free\n",
    content => "# debian_unstable\nPackage: *\nPin: origin \"debian.mirror.iweb.ca\"\nPin-Priority: -10\n",
    content => "# karmic-security\nPackage: *\nPin: release a=karmic-security\nPin-Priority: 700\n",
    content => "# karmic-updates\nPackage: *\nPin: release a=karmic-updates\nPin-Priority: 700\n",
    content => "# karmic\nPackage: *\nPin: release a=karmic\nPin-Priority: 700\n",

It is then possible to add a third argument, which is the name of one of the files, to display its content on the standard output :

1
2
3
4
5
$ puppetresources samplesite test.nod karmic.pref 2> /dev/null
# karmic
Package: *
Pin: release a=karmic
Pin-Priority: 700

Interactive use and diffing

This is the fun part. For this a binary distribution will not work as it requires ghci. In order to play with this, you need to run it on the Main.hs file, initialize the daemon and start computing catalogs :

session.hs
1
2
3
4
5
6
$ ghci Main.hs
>>> queryfunc <- initializedaemon "./samplesite/"
>>> c1 <- queryfunc "test.nod"
>>> c2 <- queryfunc "test2.nod"
>>> :type c1
c1 :: FinalCatalog

The catalogs returned are of type FinalCatalog, which is a plain Data.Map. This means you can manipulate it as usual, checking its size or typing crazy one liners :

session.hs
1
2
3
4
5
6
7
8
>>> Map.size c1
25
>>> mapM_ print $ Map.toList $ Map.map (length . lines . (\x -> case x of (ResolvedString n) -> n) .fromJust . Map.lookup "content" . rrparams) $ Map.filter (Map.member "content" . rrparams) c1
(("file","debian_unstable.list"),3)
(("file","debian_unstable.pref"),4)
(("file","karmic-security.pref"),4)
(("file","karmic-updates.pref"),4)
(("file","karmic.pref"),4)

But the real point here is to run diffs between catalogs, to check differences between hosts that should be alike, or progress when altering a catalog :

1
2
3
4
5
6
>>> diff c1 c2
file[karmic-updates.pref] {
# content
+ Pin-Priority: 750
- Pin-Priority: 700
}

Important note about the facts

All the facts, except those related to hostnames, are extracted from the current host. This means that the differences will not be accurate if part of the catalog comes from the facts, which is pretty common. Handling this properly is left as an exercice to the reader, but should be fairly obvious (roll out your own initializedaemon that takes facts as parameter).

Comments