Puppet variables not always working

I want to apply the "DRY" (don't repeat yourself) principle in the nodes.pp by grouping machines, e.g. RH5 and RH6 machines instead of adding multiple lines of includes for all RH5 and RH6 servers.

Puppet on the tst-01 works fine when using:

node "tst-01" inherits basenode {

But it breaks when I try to organize servers into groups with this configuration:

node "tst-01" inherits redhat6server {

The error with "inherits redhat6server" is:

err: Could not retrieve catalog; skipping run
[root@tst-01 ~]# puppet agent --test
err: Could not retrieve catalog from remote server: Error 400 on SERVER: Failed to parse template ldap/access.conf: Could not find value for 'netgroup' at 124:/etc/puppet/modules/ldap/templates/access.conf at /etc/puppet/modules/ldap/manifests/init.pp:82 on node tst-01.tst.it.test.com
warning: Not using cache on failed catalog
err: Could not retrieve catalog; skipping run

This is the access.conf file, that works fine if inherits is set to "inherits basenode".

[root@puppet]# grep -v "#" /etc/puppet/modules/ldap/templates/access.conf 
+ : root : LOCAL
+ : @<%= netgroup %> : ALL
- : ALL : ALL
[root@puppet]# 

This is the configuration in /etc/puppet/manifests/nodes.pp.

# Basenode configuration
node "basenode" {
        include resolv_conf
        include sshd
        include ntpd
        include motd
}

# Groups
node "redhat6server" inherits basenode {
        include ldap_auth
}

# Testservers
node "tst-01" inherits redhat6server {
        $netgroup = tst-01
}

It probably fails because inheritance occurs before the $netgroup variable is evaluated, and therefore, catalog compilation fails. However, using this method to separate code from data has limitations and should be avoided.

I have refactored my puppet code to use hierarchical data for databindings and to achieve the same effect of grouping similar nodes. A simplified example, assuming you only need to group RHEL servers, would be:

  • The hierarchical data will be retrieved using the following definition:

    hiera.yaml
    ---
    :backends: 
      - yaml
    :hierarchy:
      - %{::operatingsystem}
      - %{::hostname}
      - common
    :yaml:
      :datadir: /etc/puppet/hieradata/
    

    Both operatingsystem and hostname are facts. Hiera will attempt data resolution in this order for a Red Hat system whose hostname is some-rhel-hostname:

    • RedHat.yaml
    • some-rhel-hostname.yaml
    • common.yaml

  • This expects the following directory structure and files:

    /etc/puppet/hieradata/
    ├── common.yaml
    ├── RedHat.yaml
    ├── some-rhel-hostname.yaml
    └── tst-01.yaml
    
  • The contents of the example yaml files:

    common.yaml
    ---
    classes:
      - resolv_conf
      - sshd
      - ntpd
      - motp
    
    RedHat.yaml
    ---
    classes:
      - ladp_auth
    
    some-rhel6-hostname.yaml
    ---
    classes:
      - this_host_only
    ldap_auth::netgroup: foo-bar
    
    tst-01.yaml
    ---
    ldap_auth::netgroup: tst-01
    
    nodes.pp
    default {
        hiera_include(classes)
    }
    

    Note the use of the hiera_include function, that will return an array of all the values for each node (those from common.yaml and those comming from the hierarchy resolution, i.e., some-rhel-hostname will include all classes from common.yaml plus ldap_auth from RedHat.yaml and this_host_only from its own yaml file).

    How to use databindings in your modules is done differently depending on which version of puppet you are using. Check here for pointers.