Dominic Cleal's Blog

Puppet manifests: a multi-OS style guide

Puppet manifests are often hard enough without having to worry about deploying to multiple operating systems. Small variations can be easy enough with selectors:

file { "ntp.conf":
    path   => $operatingsystem ? {
        "solaris" => "/etc/inet/ntp.conf",
        default   => "/etc/ntp.conf",
    owner  => "root",
    group  => "root",
    mode   => 644,
    source => "file:///modules/ntp/ntp.conf",
You can even deploy different files elegantly with variable/fact interpolation and multiple source file paths.
file { "ntp.conf":
    # ..snip..
    source => [ "file:///modules/ntp/ntp.conf.$operatingsystem",
                "file:///modules/ntp/ntp.conf", ],
Puppet will search through the source files, looking first for ntp.conf.Solaris, ntp.conf.RedHat etc. before moving to the generic ntp.conf.

But sooner or later, you'll need to include or exclude entire resources based on the platform. You can go down the slippery slope of if and case statements... but resist, that way madness lies.

Don't use conditionals for including and excluding resources

Now I've got that blanket statement out there, try this out for size:

class foo {
    include "foo::$operatingsystem"

class foo::common {
    file { "foo.conf":
        path => $operatingsystem {
            "solaris" => "/opt/csw/etc/foo.conf",
            default   => "/etc/foo/foo.conf",
        # etc...

class foo::redhat inherits foo::common {
    # No changes

class foo::solaris inherits foo::common {
    file { "bar.conf":
        path => "/opt/csw/etc/bar.conf",
Then just include foo from your node definition (though you're using an ENC, right?).

This is my preferred approach, including for decisions not based on OS (such as architecture perhaps). It lets you easily work out which resources are included in a system's configuration just by looking at the classes applied to it (/var/lib/puppet/classes.txt) and helps you ensure resources are consistent across platforms.