I just came cross puppet inheritance lately. A few questions around it:
is it a good practice to use puppet inheritance? I've been told by some of the experienced puppet colleagues Inheritance in puppet is not very good, I was not quite convinced.
Coming from OO world, I really want to understand under the cover, how puppet inheritance works, how overriding works as well.
That depends, as there are two types of inheritance and you don't mention which you mean.
Node inheritance: inheriting from one node fqdn { }
definition to another. This in particular is strongly recommended against, because it tends to fail the principle of least surprise. The classic example that catches people out is this:
node base {
$mta_config = "main.cf.normal"
include mta::postfix # uses $mta_config internally
}
node mailserver inherits base {
$mta_config = "main.cf.mailserver"
}
The $mta_config
variable is evaluated in the base scope, so the "override" that is being attempted in the mailserver doesn't work.
There's no way to directly influence what's in the parent node, so there's little benefit over composition. This example would be fixed by removing the inheritance and including mta::postfix
(or another "common"/"base" class) from both. You could then use parameterised classes too.
Class inheritance: the use for class inheritance is that you can override parameters on resources defined in a parent class. Reimplementing the above example this way, we get:
class mta::postfix {
file { "/etc/postfix/main.cf":
source => "puppet:///modules/mta/main.cf.normal",
}
service { ... }
}
class mta::postfix::server inherits mta::postfix {
File["/etc/postfix/main.cf"]:
source => "puppet:///modules/mta/main.cf.server",
}
# other config...
}
This does work, but I'd avoid going more than one level of inheritance deep as it becomes a headache to maintain.
In both of these examples though, they're easily improved by specifying the data ahead of time (via an ENC) or querying data inline via extlookup or hiera.
Hopefully the above examples help. Class inheritance allows for overriding of parameters only - you can't remove previously defined resources (a common question). Always refer to the resource with a capitalised type name (file { ..: }
would become File[..]
).
Also useful is that you can also define parameters to be undef
, effectively unsetting them.