iterationpuppethieradefaults

Puppet - set defaults in manifest if not present in hiera when iterating over hash


I am iterating over many entries in a hiera hash, and wish to remove identical duplicate lines from hiera by setting defaults in the manifest (such as ensure, groups, managehome etc), and have the defaults overridden IF the duplicate key/value pair exists in hiera.

To date, everything I have tried fails to get the default values. I get the idea that I need to declare a resource, but am uncertain.

I have tried setting "default_values_hash" in the lookup and other methods, but nothing appears to pass defaults into the iteration and --debug output

This is a (pseudo) example of my manifest and hiera data. Any guidance is sincerely appreciated. Thank you.

class test (
  Hash $testhash = lookup('test::hash', "merge" => 'hash'}),
){

  $testhash.each |$key, $value| {
    user { $key :
      ensure     => $value['ensure'],
      name       => $value['name'],
      password   => $value['password'],
      groups     => $value['groups'],
      managehome => $value['managehome'],
    }
  }
}
include class test

in Hiera:

test::hash:

'fred':
  name:         fred
  password:     somepassword
  groups:       wheel
  managehome:   true

'mary':
  name:         mary
  password:     otherpassword

'john':
  name:         john
  password:     anotherpassword

'harry':

Setting resource defaults in the manifest (with capitalized User) does not pass into the iteration, though it does if I keep all data in the manifest (too much data to do that).


Solution

  • The lookup function gives you the ability to establish a default value for the hash itself, but not really for the keys and values inside the hash. Additionally, using the lookup function as a class parameter value in Puppet will be superseded by automatic parameter bindings. In other words, your lookup function in this context does nothing because Puppet is preferring automatic parameter bindings over it, and you are using those (this is definitely true for lookup in conjunction with Hiera 5, which it appears you are using). I personally find this behavior annoying, but it is what it is. Edit: Never mind on that specific reasoning; the parameter is called $testhash and not $hash. If that parameter is coming from module data though, then the lookup will still be ignored since Puppet only allows automatic parameter binding for module data.

    I am surprised that the resource defaults are not working here for you. I have to believe that this is either unintended, or something was implemented wrong regarding them when you went that route.

    Regardless, here is a guaranteed method for you. First, we implement per-expression default attributes: https://puppet.com/docs/puppet/5.3/lang_resources_advanced.html#per-expression-default-attributes

    class test (
      Hash $testhash = lookup('test::hash', "merge" => 'hash'}),
    ){
    
      $testhash.each |String $key, Hash $value| {
        user { 
          default:
            ensure     => present,
            name       => 'username',
            password   => 'userpassword',
            groups     => ['usergroups'],
            managehome => false,
          ;
          $key:
            ensure     => $value['ensure'],
            name       => $value['name'],
            password   => $value['password'],
            groups     => $value['groups'],
            managehome => $value['managehome'],
          ;
        }
      }
    }
    

    One problem still remains here though, which is that you are establishing values for the attributes in the second block for all iterations. If those key-value pairs are undefined, Puppet will error instead of defaulting to another value. We need to instruct Puppet to only establish values for attributes if you have defined a value for them in the first place. Thankfully, we can do this with the * attribute: https://puppet.com/docs/puppet/5.3/lang_resources_advanced.html#setting-attributes-from-a-hash

    class test (
      Hash $testhash = lookup('test::hash', "merge" => 'hash'}),
    ){
    
      $testhash.each |String $key, Hash $value| {
        user { 
          default:
            ensure     => present,
            name       => 'username',
            password   => 'userpassword',
            groups     => ['usergroups'],
            managehome => false,
          ;
          $key:
            * => $value,
          ;
        }
      }
    }
    

    One recommendation here would be to make your lambda iterator variables $key, $value a bit more transparently named. Another note is the caveat that for the * attribute to work, your hash keys must match Puppet attribute names.

    Updated question: In the situation where the hash has a key with a nil value, you can set an empty hash as the default value for the key's value in the lambda iterator parameters. The empty hash will replace the undef (Puppet nil) for the $value during that iteration. This will ensure that no attributes and values are included in the * operator and that all the defaults will prevail.

    class test (
      Hash $testhash = lookup('test::hash', "merge" => 'hash'}),
    ){
    
      $testhash.each |String $key, Hash $value = {}| {
        user { 
          default:
            ensure     => present,
            name       => 'username',
            password   => 'userpassword',
            groups     => ['usergroups'],
            managehome => false,
          ;
          $key:
            * => $value,
          ;
        }
      }
    }