Open Credo

October 25, 2012 | DevOps

Overriding Puppet resources with class inheritance

A common issue with Puppet manifests is a clash of resource definitions that appears in the Puppet log file as: ‘Duplicate definition: File[resource-name] is already defined; cannot redefine at…’

This issue is likely to occur when building a service stack from reusable Puppet modules and you try to alter a resource that has already been defined in modules that the service depends on.

Building a service from reusable Puppet modules has a couple of benefits. It saves you time when you don’t have to write new modules every time you build a new service. It also improves the quality when you build your service from modules that are trusted, tried and tested.

WRITTEN BY

Jussi Heinonen

Overriding Puppet resources with class inheritance

But reusing modules can sometimes cause a bit of a headache and bad temper. Especially when building a service from modules that were written by someone else (as in http://forge.puppetlabs.com), you may notice that the module does almost everything the service is required to do but then the module does just a tiny bit too much, e.g. tweaks a start-up script that your service manifests are going to modify.

In a situation like this you want to keep the majority of the module’s functionality and only alter some of its behaviour. In Puppet this can be done through class inheritance.

Example issue

Class apache::proxy in Apache module declares a resource /etc/httpd/conf/mods-enabled/proxy.conf that configures default mod_proxy behaviour:
class apache::proxy {
file { '/etc/httpd/conf/mods-enabled/proxy.conf':
source => "puppet:///modules/apache/proxy.conf"
}

 

Your service called myapp requires a special configuration for mod_proxy so you decide to define your own version of the same file:

class myapp {
include apache::proxy
file { '/etc/httpd/conf/mods-enabled/proxy.conf':
source => "puppet:///services/myapp/proxy.conf"
}

 

This is confusing for Puppet because it cannot decide which of the two resource declarations it should be using and this causes an error ‘File[/etc/httpd/conf/mods-enabled/proxy.conf] is already defined; cannot redefine at…”

Workaround: non-parameterised class

You can override resources with class inheritance by creating a subclass that inherits resources from the parent class.
class myapp::apache inherits apache::proxy {
file { '/etc/httpd/conf/mods-enabled/proxy.conf':
source => "puppet:///services/myapp/proxy.conf"
}

 

Then instead of calling the class with the statement include apache::proxy you call the override class with the statement include myapp::apache in the service manifest.

Workaround: parameterised class

When using parameterised classes e.g. ‘class apache::proxy (var1, var2)‘ subclass is called slightly differently because you cannot pass the parameter to subclass without causing a conflict with the parent class.Here is the parameterised apache::proxy class:
class apache::proxy (var1, var2) {
file { '/etc/httpd/conf/mods-enabled/proxy.conf':
source => "puppet:///modules/apache/proxy.conf"
}

 

This class is otherwise the same as the non-parameterised apache::proxy class except that you have to pass values for parameters var1 and var2 when calling the class. Calling the class with parameters uses the following syntax:

class { 'apache::proxy':
    var1    => 'param1',
    var2    => 'param2';
}

 

But when you want to override the resource in parameterised class apache::proxy and resource override is defined in myapp::apache as per the example above (see Workaround: non-parameterised class) you must call both classes apache::proxy and myapp::apache separately and let Puppet merge these classes.

In service manifests of myapp I’d call both class apache::proxy with parameters and class myapp::apache without parameters:

class { 'apache::proxy':
    var1    => 'param1',
    var2    => 'param2';
}
include myapp::apache

 

Summary

The above would be implemented in Puppet manifests something like the following.

file:///puppet/modules/apache/manifests/apache-proxy.pp:

class apache::proxy (var1, var2) {
file { '/etc/httpd/conf/mods-enabled/proxy.conf':
source => "puppet:///modules/apache/proxy.conf"
}

 

file:///puppet/services/myapp/manifests/myapp.pp:

class myapp::apache inherits apache::proxy {
file { '/etc/httpd/conf/mods-enabled/proxy.conf':
source => "puppet:///services/myapp/proxy.conf"
}
class myapp {
    class { 'apache::proxy':
        var1    => 'param1',
        var2    => 'param2';
    }
    include myapp::apache
}

RETURN TO BLOG

SHARE

Twitter LinkedIn Facebook Email

SIMILAR POSTS

Blog