Dave Shaw
Dave Shaw

Reputation: 97

using .map (or another stdlib feature) to create a hash not array

I'm trying to target JMS servers in the cloud, the puppet module init.pp needs to add a key to a hash.

I'm reading a block of hiera and having to extract parts of it to form a new hash. .each doesn't return any values so I'm using .map. The values I'm getting out are exactly as I want, however when I tried a deep_merge I discovered that .map outputs as an array.

service.yaml

jms_subdeployment_instances:
   'BPMJMSModuleUDDs:BPMJMSSubDM':
      ensure:     'present'
      target:
       - 'BPMJMSServer_auto_1'
       - "BPMJMSServer_auto_%{::ec2_tag_name}"
      targettype:
        - 'JMSServer'
        - 'JMSServer'      

init.pp

  $jms_subdeployments = lookup('jms_subdeployment_instances', $default_params)
  $jms_target_args = $jms_subdeployments.map |$subdep, $value| {
    $jms_short_name = $subdep[0, 3]
    $jms_subdeployment_inst = $array_domain_jmsserver_addresses.map |$index, $server| {
      "${jms_short_name}JMSServer_auto_${server}"

      if defined('$jms_subdeployment_inst') {
        $jmsTargetArg = {
          "${subdep}" => {
            'target' => $jms_subdeployment_inst
          }
        }
      }
    }

  $merge_subdeployment_targets = merge($jms_subdeployments, $jms_target_args)



```Output
New JMS targets are : [{BPMJMSModuleUDDs:BPMJMSSubDM => {target => [BPMJMSServer_auto_server101, BPMJMSServer_auto_server102]}}]

The enclosing [ ] are causing me trouble. As far as I can see, in puppet .to_h doesn't work either

Thanks

Update 22/07/2019:

Thanks for the reply, I've had to tweak it slightly because puppet was failing with "Server Error: Evaluation Error: Error while evaluating a Method call, 'values' parameter 'hsh' expects a Hash value, got Tuple"

  $array_domain_jmsserver_addresses = 
  any2array(hiera('pdb_domain_msserver_addresses'))
  $array_domain_jmsserver_addresses.sort()
  $jms_subdeployments = lookup('jms_subdeployment_instances', $default_params)
  $hash_domain_jmsserver_addresses = Hash($array_domain_jmsserver_addresses)

  if $hash_domain_jmsserver_addresses.length > 0 {
      $jms_target_arg_tuples = $jms_subdeployments.keys.map |$subdep| {
      $jms_short_name = $subdep[0, 3]
      $jms_subdeployment_inst = regsubst(
          $hash_domain_jmsserver_addresses.values, /^/, "${jms_short_name}JMSServer_auto_")

  # the (key, value) tuple to which this element maps
  [ $subdep, { 'target' => $jms_subdeployment_inst } ]
}

$jms_target_args = Hash($jms_target_arg_tuples)
} else {
$jms_target_args = {}
}

notify{"Normal array is : ${jms_subdeployments}": }
notify{"Second array is : ${jms_target_args}": }  

$merge_subdeployment_targets = deep_merge($jms_subdeployments, $jms_target_args)
notify{"Merged array is : ${merge_subdeployment_targets}": }

Normal is : {BPMJMSModuleUDDs:BPMJMSSubDM => {ensure => present, target => [BPMJMSServer_auto_1, BPMJMSServer_auto_server1], targettype => [JMSServer, JMSServer]},

Second is : {BPMJMSModuleUDDs:BPMJMSSubDM => {target => [BPMJMSServer_auto_server2]}

Merged is : {BPMJMSModuleUDDs:BPMJMSSubDM => {ensure => present, target => [BPMJMSServer_auto_server2], targettype => [JMSServer, JMSServer]}

Desired output it:

{BPMJMSModuleUDDs:BPMJMSSubDM => {ensure => present, target => [BPMJMSServer_auto_1, BPMJMSServer_auto_server1, BPMJMSServer_auto_server2], targettype => [JMSServer, JMSServer, JMSServer]}

Upvotes: 2

Views: 3505

Answers (1)

John Bollinger
John Bollinger

Reputation: 180201

when I tried a deep_merge I discovered that .map outputs as an array.

Yes, this is its documented behavior. map() should be considered a function on the elements of a collection, not on the collection overall, and the results are always provided as an array.

It would probably be useful to look over the alternatives for converting values to hashes. Particularly attractive is this one:

  • An Array matching Array[Tuple[Any,Any], 1] is converted to a hash where each tuple describes a key/value entry

To make use of this, map each entry to a (key, value) tuple, and convert the resulting array of tuples to a hash. A conversion of your attempt to that approach might look something like this:

if $array_domain_jmsserver_addresses.length > 0 {
  $jms_target_arg_tuples = $jms_subdeployments.keys.map |$subdep| {
    $jms_short_name = $subdep[0, 3]
    $jms_subdeployment_inst = regsubst(
        $array_domain_jmsserver_addresses.sort, /^/, "${jms_short_name}JMSServer_auto_")

    # the (key, value) tuple to which this element maps
    [ $subdep, { 'target' => $jms_subdeployment_inst } ]
  }

  $jms_target_args = Hash($jms_target_arg_tuples)
} else {
  $jms_target_args = {}
}

$merge_subdeployment_targets = merge($jms_subdeployments, $jms_target_args)

Note that since you don't use the values of $jms_subdeployments, I have taken the liberty of simplifying your code somewhat by applying the keys() function to it. I have also used regsubst() instead of map() to form target names from the elements of $array_domain_jmsserver_addresses, which I personally find more readable in this case, especially since you were not using the indexes.

I've also inferred what I think you meant your if defined() test to accomplish, and replaced it with the outermost test of the length of the $array_domain_jmsserver_addresses array. One could also write it in somewhat more functional form, by building the hash without regard to whether there are any targets, and then filter()ing it after, but that seems wasteful because it appears that either all entries will have (the same) targets, or none will.

Upvotes: 3

Related Questions