Search code examples
rubyhashpuppeterbhiera

Puppet: Iterate over a hiera_hash in template


I have this in a yaml hiera file.

reg_connection:
  toronto:
    - host: apple.net
    - port: 701
    - user: george
    - ssl: true
    - allowed: banana,orange
  texas:
    - host: pink.net
    - port: 702
    - user: joel
    - ssl: false
    - allowed: blue,gree,red

I want to access the values of host, port, user, ssl and allowed for toronto and texas.

I have this in my manifest:

 $reg_connection= hiera_hash('reg_connection')

I have this in my template:

<% reg_connection.keys().sort.each do |location| -%>
<%= location %>host=<%= location[host] %>
<%= location %>port=<%= location[port] %>
<%= location %>username=<%= location[user] %>
<%= location %>ssl.enable=<%= location[ssl] %>
<%= location %>allowed.list=<%= location[allowed] %>
<% end -%>

I want this to output two blocks of configuration in my template: one for toronto and one for texas.

My puppet output shows that it doesn't have a value for host. How can i access that value?


Solution

  • You have a number of problems. The first is that in this code:

    <%= location[host] %>
    

    ...host is (correctly) interpreted as a local variable or method name. There's no variable or method named host, hence the error.

    You want to access the value with the key named "host", which is a string, so you must use that string.

    <%= location["host"] %>
    

    The second problem is in your YAML file.

    reg_connection:
      toronto:
        - host: apple.net
        - port: 701
        - user: george
        - ssl: true
        - allowed: banana,orange
      texas:
        # ...
    

    Here your have a mapping (which is like—and is parsed into—a Ruby Hash) with a single key, reg_connection. The value associated with that key is itself a mapping with two keys, toronto and texas. So far so good. But the value associated with toronto is a sequence (read: array), not a mapping, because each line starts with -. The mapping has five items, each of which has a single key and value. Another way to represent the same data structure is this, which may help to illustrate the problem;

    { reg_connection:
      { toronto:
        [ { host: apple.net },
          { port: 701 },
          { user: george },
          # ...
        ],
        texas:
          # ...
      }
    }
    

    What you want, I suspect, is for that innermost data structure to be a mapping, not a sequence:

    reg_connection:
      toronto:
        host: apple.net
        port: 701
        user: george
        ssl: true
        allowed: banana,orange
      texas:
        # ...
    

    The third problem is with the way you're trying to iterate over the resulting hashes:

    <% reg_connection.keys().sort.each do |location| -%>
    <%= location %>host=<%= location["host"] %>
    <%= location %>port=<%= location["port"] %>
    ...
    

    You've called reg_connection.keys.sort.each (note: in idiomatic Ruby, parentheses are omitted for empty argument lists), which means you're not iterating over the locations, you're iterating over the keys of the reg_connection hash. In other words, you're iterating over the array ["toronto", "texas"], so the subsequent line is equivalent to"toronto"["host"], which isn't going to work.

    You could replace location["host"] with reg_connection[location]["host"], but that's overkill. Just do this:

    <% reg_connection.sort.each do |location, values| -%>
    <%= location %>host=<%= values["host"] %>
    <%= location %>port=<%= values["port"] %>
    ...
    

    There's no need to get reg_connection's keys before calling sort—you can just call it directly on the hash. This will, in effect, convert the hash to an array of key-value pairs (two-element arrays), which Ruby has no trouble sorting. Then when we call each two values are passed to the block—location (they key, e.g. "toronto") and values the hash with "host", "port", etc. keys.