Building files from fragments with Puppet
While building up complex configs with Puppet you often need to build up one file from many fragments. This is useful for files like older sysctl.conf files and maybe named.conf files.
The basic pattern is you want to manage a file, but want the contents to be very different from node to node. A fragment based system lets you register different contents into a file on different nodes. It’s exactly like the conf.d directory you’d find in Apache but for daemons that does not support this construct on their own.
I’ve had an older version of this floating around but had to clean it up for a client today so thought I might as well do a proper job, release it and get some more eye balls on it. This version is specific to Puppet 0.25.x, I will soon make a >= 0.24.8 version too since that is what my client is on.
An example says more than words, so lets create something to manage sysctl.conf:
# Initial setup class sysctl { include concat::setup exec{"reload-sysctl": refreshonly => true, command => "/sbin/sysctl -p" } concat{"/etc/sysctl.conf": notify => Exec["reload-sysctl"], } } # use this to set values define sysctl::setting($value) { concat::fragment{"sysctl_${name}": target => "/etc/sysctl.conf", content => "${name} = ${value}\n", } }
The above sets up a class that will create an empty sysctl.conf and provides an utility for setting individual values. Whenever the sysctl.conf file gets changed the changes will be made live using the refreshonly exec.
Lets see how we might use it:
node "your.example.com" { include sysctl sysctl::setting{"net.ipv4.ip_forward": value => 1 } }
You can see this looks and feels a lot like a native type but without a lot of the hassle it would take to write one, you can really get a lot of mileage out of this pattern. The concat is clever enough to unregister the setting should you remove lines 4 to 6 in the above node block.
A cleaner approach would be to just make classes like sysctl::ipv4_forward that you can include on the nodes that need it.
You can grab the current code here.

This looks great! Do you have the code in a git repo or anything anywhere? We’ve been doing something similar for a long time, and now that we’re switching to Puppet I’ve been looking for a way to do exactly this.
I don’t at the moment – it’s in my company repo and I just grabbed a snapshot of that. If there’s patches I’ll open some project somewhere I guess
Why reinvent the wheel? Why aren’t you using the Augeas type that’s already built into Puppet 0.24+?
http://docs.reductivelabs.com/guides/types/augeas.html
http://augeas.net/
Does Augeas run on every OS puppet runs? can it manage any arbitrary file? Is it as easy to build support for unsupported files?
No on all accounts. Sysctl was an example but there are many that Augeas does not support where this is handy. I build iptables, named.conf, motd and countless others using this method.
Augeas combined with Puppet is just in general an anti-pattern in my opinion, it’s a great tool for ad-hoc editing of files. With emphasis on ad-hoc.
In most Puppet use cases ad-hoc is not what you want though, deterministic outcome is what you want.
If say I had 100 user desktops, and I wanted to enforce the use of a local smart relay I’d use the Augeas type – this would let user adjust the rest of their mail setup, in this case Augeas is an appropriate tool.
In most Puppet use cases I’ve seen though managing just a small subset of a file is simply not desirable. I’ve also not seen many lenses that supports any notion of purging unmanaged bits from a file – and I doubt Augeas supports being purged by the resources type.
So really what you are achieving is a stateful notion on just one aspect of a file. Lets stick to the desktop situation. Perhaps you want to not just set the relayer but also a few other settings. You do not know what the desktop owners will do to the rest of the file and generally you cannot be 100% certain that the changes Puppet+Augeas make will be compatible with their changes. You simply do not know the state of your server estate at any given moment. The cfengine like notion of line editing has been dispelled as a terrible idea many times over Augeas is simply a line editor that understands context.
You’re also relying on the quality of the lenses, I don’t imagine a user would personally review code of every lense they might be open to simple abuse, for instance they might parse comment blocks wrong, they might not handle setting the same option many times in the single file, there’s a lot of possible edge cases. You can’t even be certain of the order of certain config patterns in your config files if you’re looking at them in a granular scope.
Contrasting with managing the entire file like when you just copy the whole file out – no problems. Building a file using fragments gives you the flexibility of building it up in stages and getting the deterministic outcome that managing a whole file can give you with the bonus of flexibility.
Interesting post, and very elegant solution to building a file from
fragments; there’s a few things about Augeas and using it in Puppet that
are not true. Before I get into where we disagree (or are thinking about
subtly different problems that look deceptively similar), I agree that
producing a whole file from a fixed order of fragments is a very safe and
sound way to produce a reliable configuration.
You’ve already mentioned one case where that is not desirable: when you
only want to manage part of a file, but leave the rest of the file under
the control of something or someone else. Besides a user, that something
else can, for example, also be the package management system.
There is nothing in Augeas though that requires you to accept the file you
start from as the one that happens to be on the server: you can always blow
out a known file and have the Augeas changes require that file.
The downside of concatenating is that you need to globally understand how
the file is put together in each possible combination of classes that can
be applied to any of your nodes.
Whether the concat approach or known base + Augeas changes is appropriate
depends on how varied the changes to that file are across all your
nodes. With concat, you’ve moved the point where you die from combinatorial
explosion out a bit; fundamentally though, you eventually run into the same
problem as you do with copying out entire files: at some point the burden
of maintaining multiple copies of the same file (or fragment, in your case)
becomes overwhelming. If you manage such a file as base file + Augeas
changes, that combinatorial explosion does not happen, since you are only
concerned with how the file differs from the base in each case.
Combinatorial explosion can not just happen because of file complexity: it
can also happen because you have more than one or a small number of people
working on the manifests – with concatenating, they _all_ need to
understand how any change to a file fragment will affect how that file is
put together globally. In the case of a diverse team working on a manifest,
the apporach of base file + targetted changes might actually be safer.
At its core, Augeas gives you something very simple: it lets you look at
one config file as if it were stored in many small files, one for each
‘interesting’ entry in the file. The ideal tradeoff between using the power
and flexibility that comes with this resolution, and simply cating the file
from known fragments depends a lot on the particular needs and variation of
whatever you’re trying to achieve.
To address some concrete points in your comment:
(1) Purging unmanaged bits: you can achieve that either by blowing out a
base file before making changes with Augeas, or removing the contents of
the file with a simple Augeas resource that does a ‘rm /files/$path’ before
making changes with the Augeas type.
(2) Availability on various platforms: which platforms are you missing ?
Let me know and I’ll see to it that it will work there. It currently works
on a wide variety of platforms, from all kinds of Linux distros to Solaris,
FreeBSD and AIX
(3) Manaing arbitrary files: by its nature, Augeas needs to be told about
the format of the file. Whether that is easy or hard depends hugely on the
complexity of the file format. The lens for sysctl took a few minutes, the
one for iptables a few hours, and yes, there’s no lens for named.conf
currently.
(4) Quality of lenses: that’s a strawman – as any piece of software, lenses
might have bugs. But just as nobody reviews every piece of Puppet code
before using Puppet, because they know that others in the community have
done some work to ensure its quality, users of Augeas aren’t alone in
ensuring that lenses (or the Augeas library itself) is of decent quality,
nor do they have to write every single lens themselves – Augeas has a large
number of lenses, and that collection is growing steadily.
(5) Duplicating entries for an individual setting: you actually have to
very conciously tell Augeas that you want to do that – it’s hard to do that
accidentally.
David, that’s a great comment I really appreciate it
I am missing something though, do you mind showing or linking too a sample where you copy out a known file then editing with Augeas?
How do you avoid a constant fight between the two types doing competing things to a file?
In fact that’s another question I have: how in a large team do you avoid two guys Augeas editing the same bit but with diff resource names?
I actually don’t use the known file + Augeas approach, since for what I need editing files that came from a package is good enough. You basically already provide the infrastructure for avoiding a resource fight with your concat module: I’d use a similar ‘double-buffering’ approach as you do with concat (in fact, I’d just add a concat::augeas define to your module)
Avoiding resource conflicts, i.e. avoiding two resources working on the same bits for different purposes, needs to be addressed by convention for now. I am actually thinking about revamping the Augeas type to (a) make it more user-friendly and (b) avoid such conflicts automatically. I am thinking the type should look more like
augeas {
find => “/files/etc/hosts/*[ipaddr = '127.0.0.1']“,
create => “/files/etc/hosts/01″,
tree => [ "ipaddr = '127.0.0.1'", "canonical = 'localhost.localdomain'" ]
}
with the meaning that we first go looking for an entry that matches ‘find’, if that doesn’t exist, we create one with the path that’s given in ‘create’, and then make sure that tree looks like what’s in the tree parameter. (I need to think a little more about how you’d write down that tree fragment)
That would make it reasonable to have resources with the same ‘find’ parameter conflict.
Kewl, I don’t think the known file approach really does what we need. You’d either copy it out once and then aug manage it, or have a fight. Doing it once doesn’t give you any sense of security – a user can maliciously/accidentally/unintentionally modify it, or your package manager can screw you over. Especially a concern where you get packages from devs etc.
I’ve not used aug to manage a very complex file but my gut feel is still that you probably need an equally good understanding of the file structure as you would by building it from snippets. Happy to be proven wrong though if someone have a good complex example they can share.
I really think they’re different problems – I’d use aug as I initially indicated in the uni scenario or for files like network interfaces etc, totally support it there but otherwise it just doesn’t feel like a good fit for my needs
And really the suggestion that the 2 approaches are re-inventing the wheel as was made above is just a bit silly cos they really do not operate in the same problem domain.
Will you blog announce the 0.24 version of concat? I subscribe to your RSS feed, so I’d notice that, but don’t want to keep checking the comments on this post for updates.
Looks like you’ve tidied up the interface a lot since concatfile, and am looking forward to migrating to the new way!
Thanks,
Sheldon.
I will, no problem, its only 2 things if you wanted to hack at it:
You could easily make it work for your setup as long as you at least has regsubst function that came in late 0.24.x
I suspect #1 is the reason that I am not seeing phantom execs firing the 2nd time round since 25 has a built in handling for the purging now.
I have this working now on 0.24.8 and with a couple of extra additions:
1) A ‘force’ flag to the script that will allow the writing of empty configuration files. This is useful to us when we remove the last remaining line from a config file, previously the script would cowardly fail to remove the last remaining line.
2) A ‘warn’ flag that appends a ‘managed by puppet’ style comment to the top of any configuration files managed by the module.
Both are disabled by default.
I would like to contribute the changes back if possible. Do you have the code in a DVCS?
I do not currently have it in git or something no, I’ll make a plan for that, for now if you just mail me a tarball I’ll check it out and incorporate your new flags and then make 2 releases one for each of 24 and 25.
You can mail me on rip@ this domain
much appreciated!
I created a Git repository and checked in your 19 Feb 20910 tarball at http://git.madduck.net/v/puppet/modules/concat.git . If you provide me with a username and a SSH key, I’d gladly give you write access. I hope this helps for now — using VCS is clearly better than sending tarballs back and forth.
Have you considered using run-parts instead of concatfragments.sh? I think this would help standardisation a lot.
Bit of a linux centric view of the world
Well, sure. But instead of NIH on Linux, wouldn’t it make sense to rather push run-parts to /usr/local/bin on those systems that don’t have it?
Don’t think it really does what I need to be honest, the concat helper does a lot more – and about to do even more in the next release that I’ll hopefully have up today.
[...] For background of what this is about please see my earlier post: Building files from fragments with Puppet [...]
Finally got round to reading this; looks really versatile, thanks.
Currently we do something a bit similar to build up iptables rules (pinched from http://projects.reductivelabs.com/projects/puppet/wiki/Module_Iptables_Patterns ).
I’d prefer to switch over to yours, but I’m not clear how I’d handle e.g. a header and footer – would it simply be a case of fragments named “000_header” and “ZZZ_footer” ?
To do footers and headers just supply on the fragments:
order => “00″
and
order => “99″
put your rules at “10″ (the default)
I do numerical sorting on the fragments so that should do it.
[...] For background of what this is about please see my earlier post: Building files from fragments with Puppet [...]
Out of curiosity, is there any reason that this *can’t* be turned into a native type? It seems like some efficiencies could be gained that way.