Puppet – Understanding Class Inheritance in Puppet

puppet

I've read the documentation on scope, but I'm still having trouble working this out. I've got two environments that are very similar – so I've got:

modules/django-env/manifests/init.pp

class django-env {
    package { "python26":
        ensure => installed
    }
    # etc ...
}
import "er.pp"

modules/django-env/manifests/er.pp

$venvname = "er"
$venvpath = "/home/django/virtualenvs"

class er {
    file { "$venvpath/$venvname" :
        ensure => directory
    }
    # etc ...
}
class er-dev {
    include er
}
class er-bce-dev {
    $venvname = "er-bce"
    include er
}

manifests/modules.pp

import "django-env"

manifests/nodes.pp

node default {
    # etc ...
}
node 'centos-dev' imports default {
    include django-env
    include er-bce-dev
    include er-dev
}

The result here is that the "inheritance" works – but only the first "er-" item under the 'centos-dev' node is acted upon, I either get er-bce-dev or er-dev, but not both. There must be some basic thing I'm misunderstanding here.

Is it the difference between import and include ? (not sure I understand that)

Best Answer

Puppet does not support this kind of configuration, but the restriction can be easily bypassed. The reason is in two basic puppet "rules":

  1. A class can be included only once (subsequent include -statements will do nothing)
  2. The order of execution is not strictly defined and can even be random

er-dev and er-bce-dev both include the class er. But the class cannot be included two times, so er class is executed only with the default $venvname = "er", or with overridden $venvname = "er-dev", but not both.

The solution: Change er class to a definition (see "Definitions" from Puppet Language Tutorial (http://docs.puppetlabs.com/guides/language_tutorial.html)):

modules/django-env/manifests/er.pp

# Create new er resource definition
define django-env::er($vpath="/home/django/virtualenvs", $vname="er") {
    file { "$vpath/$vname" :
        ensure => directory
    }
    # etc ...
}

We do not need the $venvname and $venvpath variables, they are specified as default values in the definition. The name django-env::er adds the definition into django-env namespace and allows automatic import (see below).

Import and Include

The difference between import and include statemens is:

  • import works with files, and does not execute classes
  • include executes classes
  • files must be imported before the classes can be included

Note: there is a very strong exception to the last rule: Puppet module lookup. include statement does automatic imports in many situations. Here are some of them:

  • include foo tries to import the file module_dir/foo/manifests/init.pp
  • include foo::bar imports module_dir/foo/manifests/bar.pp

With these automatic imports and the resource definition, you can define multiple virtual environments very easily. Change node 'centos-dev':

node 'centos-dev' imports default {
    include django-env
    # The er resource with default values:
    django-env::er { 'er-bce': }
    # Another er resource with different environment name:
    django-env::er { 'er-bce-dev': vname => 'bce-dev'}
}

And you can remove basically all import statements considering django-env module.

Happy Puppeting!