Puppet : How to override / redefine outside child class (usecase and example detailled)

The use case i try to illustrate is when to declare some item (eq mysqld service) with a default configuration that could be included on every node (class stripdown in the example, for basenode), and still be able to override this same item in some specific class (eg mysql::server), to be included by specific nodes (eg myserver.local)

I illustrated this use case with the example below, where i want to disable mysql service on all nodes, but activate it on a specific node. But of course, Puppet parsing fails because the Service[mysql] is included twice. And of course, class mysql::server bears no relation to be a child of class stripdown

Is there a way to override the Service["mysql"], or mark it as the main one, or whatever ? I was thinking about the virtual items and the realize function, but it only permits apply an item multiple times, not to redefine or override.

# In stripdown.pp :
class stripdown {
    service {"mysql": enable => "false", ensure => "stopped" }
}

# In mysql.pp :
class mysql::server {  
    service { mysqld:  
        enable      => true,  
        ensure      => running,  
        hasrestart  => true,  
        hasstatus   => true,  
        path        => "/etc/init.d/mysql",  
        require     => Package["mysql-server"],  
    }
}

# Then nodes in nodes.pp :
node basenode {
    include stripdown
}

node myserver.local inherits basenode {  
    include mysql::server`         # BOOM, fails here because of Service["mysql"] redefinition             
}

Solution 1:

Whenever you find yourself saying "I want this component to behave one way in one node, but a different way in another node", you probably need to be looking at a definition driven by variables, either via conditionals in the class definition, or substitutions in a template.

Unfortunately, your example shows you attempting to use inheritance, which is pretty badly broken in puppet and will cause you a lot of grief here because one of the things that it breaks is variable redefinition in nodes.

UPDATE: old answer details are obsolete

What I'm doing as of 2.7 is to ensure that all variables used by a class are not global variables at all, but rather parameters with reasonable defaults set. You still don't want to use the inheritance built into Puppet, but you can now override particular variables directly from the node definition, and you can even have a primitive form of inheritance manually by having one class take variables and then pass them to an invocation of another class. For example, you would have in your node definitions:


node experimental_server {
  # This will still call class 'databases', but will install Postgresql and not Mysql
  # as a default.  You can still override it the same way as with 'databases'
  class { 'mydept::nextgeneration': },
}
node server_with_mysql {
  class { 'databases':
    mysql_enabled => true,
  }
}
node server_with_no_db {
  class { 'databases': # this installs only the clients }
}

class databases (
  # By default, install no servers, just clients
  $pgsql_enabled => false,
  $mysql_enabled => false
)
{
  ...
}

class mydept::nextgeneration (
  # If not explicitly overridden, an undef value passed as a parameter to a class
  # assumes the default value in that class
  $mysql_enabled => undef,
  $pgsql_enabled => true
)
{
  class { 'databases':
    mysql_enabled => $mysql_enabled,
    pgsql_enabled => $pgsql_enabled,
  }
}

As a commenter noted, there is now also a nifty new feature called Hiera, which allows you to offload defaults into a separate data structure, and you should look into it if you have Puppet 3.2 available. Unfortunately, many Linux distributions are still shipping with 2.6 or 2.7, so this may not yet be an option for you.

Previous answer details preserved below

The way that I generally handle something like this is to create a 'default_parameters.pp' file that has nothing in it but default assignments for a long list of variables, so in this case, it would contain a line something like $mysql_enabled = false. This file is then included by the module's init.pp file. Then, the node definitions file would look something like:

import "mymodule"

node myserver.local {   # NOTE: no inheritance!
  $mysql_enabled = true
  include mysql::server
}

node otherserver.local {
  include mysql::server
}

Then in the class definition for mysql::server you check if $mysql_enabled is true, and if so, use a selector on enable and ensure.

This breaks instantly if node myserver.local inherits anything, because that will lock the value of the variables at inherit time, and further changes inside the node definition will have no effect, but otherwise I use this technique all over the place to have individual components behave differently without generating multiple classes.

If you absolutely have to have inheritance, but the list of machines with mysql enabled is small, you can take an alternate route: instead of setting a variable in the node definition, build your selector off of $fqdn, with the default to disable, and selected fqdns enabled.

Solution 2:

Have a go with this:

# In stripdown.pp : 
class stripdown {
    service { "mysql": 
         enable => "false", 
         ensure => "stopped" 
    }
}

# In mysql.pp : 
class mysql::server {  

    if defined(Service["mysql"]) {
        Service["mysql"] {  
            enable     => true,  
            ensure     => running,  
            hasrestart => true,  
            hasstatus  => true,  
            path       => "/etc/init.d/mysql",  
            require    => Package["mysql-server"],  
        }
    } else {
        service { "mysql":  
            enable     => true,  
            ensure     => running,  
            hasrestart => true,  
            hasstatus  => true,  
            path       => "/etc/init.d/mysql",  
            require    => Package["mysql-server"],  
        }
    }
}

# Then nodes in nodes.pp : 
node basenode {
    include stripdown 
}

node myserver.local inherits basenode {  
    include mysql::server
}

This of course comes with the caveat that you already have Package["mysql-server"] defined elsewhere was it will fail as written without it due to your require statements.

The other error I found was that you had too many spaces after the options. There options in the stanza must be aligned no more than 1 space greater than the longest option name.