Search code examples
puppet

Puppet configuration for multiple classes in a module


I'd like to use and configure the puppet-nginx module, although this is a general question about Puppet configuration.

Exec { path => [ "/bin/", "/sbin/" , "/usr/bin/", "/usr/sbin/" ] }

class nginx-setup {
    class { 'nginx': }
}

include nginx-setup

Works great! Now, if I follow the docs for configuration I end up with something like this:

Exec { path => [ "/bin/", "/sbin/" , "/usr/bin/", "/usr/sbin/" ] }

class nginx-setup {
    class { 'nginx': }

    class { 'nginx::package':
        package_source => 'nginx-mainline'
    }
}

include nginx-setup

Error: Duplicate declaration: Class[Nginx::Package] is already declared

I tried include nginx instead of my first class declaration, but I think the module's init.pp is declaring the nginx::package class already and I still get duplicate declaration error. Even if that worked, what if I wanted to apply more configurations to another class within the nginx module? For example:

Exec { path => [ "/bin/", "/sbin/" , "/usr/bin/", "/usr/sbin/" ] }

class nginx-setup {
    class { 'nginx': }

    class { 'nginx::package':
        package_source => 'nginx-mainline'
    }

    class { 'nginx::config': 
        nginx_error_log => 'syslog:server=localhost',
    }
}

include nginx-setup

Many duplicate definitions!

So it feels like I should be passing everything required into my initial class declaration, but I can't seem to find the right way to do it. What is the best way to achieve this?


Solution

  • TL;DR

    Consider using Hiera after all, for this module is tricky to use otherwise, due to some shortcomings in Puppet's handling of class parameters.


    Long answer:

    That's a loaded question, actually, even though it should not be. You correcly inferred the gist already. But let's take it step-by-step.

    Module structure

    It is now considered best practice (citation needed, although Ryan Coleman from Puppet Labs mentioned this in a recent presentation at FOSDEM) to expose all tunables of a module in its central class (here, class nginx).

    This way, it is clear for the user that they need to look up the appropriate parameter for this one class, instead of going on a hunt for the appropriate class to tune.

    The nginx module you picked seems to adopt this in large parts, but not consequently.

    Hacks using defined()

    As you have noticed, the module author added some shortcuts to allow you to declare your classes "safely" if you make sure the nginx::config class is encountered before the nginx class proper, lexically.

    This is dangerous, because in a complex manifest, this might not be easy to assert.

    include vs. class { }

    Class parameters are problematic, because they lead to include being less safe than it used to be, because they don't mix well with class { 'name': ... } style declarations. The latter are always bad news because they have to be unique, as you are now experiencing.

    It is best to stick to include as much as possible, which leads to the next issue.

    Hiera

    With parameterized classes, you really want to adopt Hiera as soon as possible. Defining class parameters as data is almost universally superior to doing it in the manifest. I understand the desire to stick to simple constructs first, but due to the issue described above, it can really make life harder on yourself.