puppethiera

Puppet Hiera Variables - Accessing Hash within Hash


I have a Puppet class which uses this format to create a variable for a deep merge later.

$config = {
    'foo'       => {
      'fvar1'   => 'fvar1-string',
      'fvar2'   => 'fvar2-string',
    },
    'bar'  => {
      'bvar1'  => 'bvar1-string',
      'bvar2'  => 'bvar2-string',
    },
}

I am working to refactor the code so that the data is managed in hiera instead of within the code itself. In doing so, I would prefer to only need a single hiera lookup for each hash (foo and bar). Is this possible?

Here is what I have attempted so far:

Hiera data:

role::fb:
  foo:
    fvar1: 'fvar1-string'
    fvar2: 'fvar2-string'
  bar:
    bvar1: 'bvar1-string'
    bvar2: 'bvar2-string'

Code:

class test {
  $config = {
    'foo'  => hiera('role::fb::foo', undef),
    'bar'  => hiera('role::fb::bar', undef),
  }
  notify { "Config: ${config}": }
}

include test

Output:

[root@puppetclient1 ~]# puppet agent -t
Info: Using configured environment 'production'
Info: Retrieving pluginfacts
Info: Retrieving plugin
Info: Retrieving locales
Info: Caching catalog for puppetclient1.home.arpa
Info: Applying configuration version '1684256670'
Notice: Config: {foo => , bar => }
Notice: /Stage[main]/Test/Notify[Config: {foo => , bar => }]/message: defined 'message' as 'Config: {foo => , bar => }'
Notice: Applied catalog in 0.01 seconds
[root@puppetclient1 ~]#

My desired output is that foo and bar contain the contents of the hiera data, including fvar{1,2} and bvar{1,2} respectively.

EDIT: John Bollinger's answer below has helped me in my understanding tremendously. I have been able to recreate the solutions in Puppet 5. However, I should have mentioned this earlier, I have a need for compatibility with Puppet 3 at the moment. When running the code against version 3.8.7, I run into a syntax error and compilation fails.

Updated code (utilizing hiera() for version compatibility here):

class fb {
  $config = {
    'foo'  => hiera('role::fb', undef)['foo'],
    'bar'  => hiera('role::fb', undef)['bar'],
  }
  notify { "Config: ${config}": }
}

include fb

Error when using Puppet 3.8.7:

Error: Could not retrieve catalog from remote server: Error 400 on SERVER: Syntax error at '['; expected ']' at /etc/puppet/environments/production/modules/A/manifests/fb.pp:3 on node puppetclient1.home.arpa
Warning: Not using cache on failed catalog
Error: Could not retrieve catalog; skipping run

Final edit:

I spoke too soon. John's suggestion stating "promote the members to top-level keys" worked like a charm. I apparently had a syntax issue during my first attempt. Thanks!


Solution

  • The most natural mapping of your data from manifest code to Hiera would be this:

    module_name::class_name::config:
      foo:
        fvar1: 'fvar1-string'
        fvar2: 'fvar2-string'
      bar:
        bvar1: 'bvar1-string'
        bvar2: 'bvar2-string'
    

    The most natural way to retrieve that in your manifest would be via automatic data binding to a class parameter:

    class module_name::class_name (
      $config
    ) {
        // ... $config data automatically looked up in Hiera ...
    }
    

    Automatic data binding relies on the Hiera key having the form module_name::class_name::param_name.

    If you prefer, however, then you can still perform an explicit lookup:

    class module_name::class_name {
      // lookup() is now the preferred data-lookup function; hiera() is deprecated
      $config = lookup('module_name::class_name::config')
    }
    

    Explicit lookup can be performed for any key, so if this is your target case then you are not limited to keys of the module_name::class_name::specific_name form.

    If you want only one or the other member hash at a time, then you can retrieve the whole config and then select the wanted value from it:

      $foo_config = lookup('module_name::class_name::config')['foo']
    

    Alternatively, if you want the ability to look up the foo and bar members individually, and are ok with losing the ability to look up the whole config at once, then you can simply promote the members to top-level keys:

    role::fb::foo:
        fvar1: 'fvar1-string'
        fvar2: 'fvar2-string'
    role::fb::bar:
        bvar1: 'bvar1-string'
        bvar2: 'bvar2-string'
    

    Additional note: following @MattSchuchard's comment, if you want lookup() to provide a default value, even undef, when the key is not found, then you need to specify that in the lookup() call. This is different from the behavior of the legacy hiera() function. There's more than one way to do that, but if you want to default to undef as hiera() did, then the simplest is to add the three optional arguments to the lookup() call, with the last of those being the wanted default value:

      $config = lookup('module_name::class_name::config', Hash, 'first', undef)
    

    If you do not specify a default and the requested key is not found, then catalog building will fail.