3

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).

xit
  • 135
  • 1
  • 12

1 Answers1

3

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,
      ;
    }
  }
}
Matthew Schuchard
  • 25,172
  • 3
  • 47
  • 67
  • Thank you for your quick and detailed reply. I have tested both code blocks and (while they do not error) unfortunately they only process the first key in the hiera hash. I have also slightly edited my post to reflect the desire to have keys (that match the default values) to have NO key/pairs in hiera, and if they do exist in hiera to override the manifest defaults. Any further guidance is sincerely appreciated. – xit Dec 21 '17 at 12:47
  • @xit I had a suspicion that code might lose the hash iteration. I need to think over how to retain the hash iteration without losing the functionality. Unfortunately, this only works intrinsically with array iteration. As per the second half of your comment, you can do that with a lambda default value. Updating answer for that. – Matthew Schuchard Dec 21 '17 at 12:54
  • 1
    @xit I just tested my code with the hash of mary and john that you provided above, and it processes both mary and john. Is something else going on here, because your sample hash combined with my code is producing the results you wanted? – Matthew Schuchard Dec 21 '17 at 13:41
  • it didn't work in my test vm, so I will create a new environment and test it again. Thank you. – xit Dec 21 '17 at 20:38
  • One more question: the hiera hash HAS to have at least one key/value pair under the key, even with defaults in the manifest. In my hiera data example above, harry fails with error ('value' expects a hash value, got undef). Do you have an idea how to allow only the $key to exist in hiera (like harry in the example), and the manifest set defaults to append to it? One use case for this is managing services, where the only key/value for most services would be 'ensure => running'. Thanks again, you have saved my bacon! Merry Christmas! – xit Dec 23 '17 at 01:16
  • @xit I ran into the same issue when I used your test hash. Since the lambda documentation states what I did above should work, I am going to to file a JIRA ticket asking if this is either a documentation or code issue. I will update with the ticket info after filing it. – Matthew Schuchard Dec 23 '17 at 13:20