Search code examples
arrayshashpuppethiera

Extracting an array of values from an array of hashes in Puppet


I have the following array of hashes in hiera:

corporate_roles:
  - name: 'user.1'
    system_administrator: true
    global_administrator: false
    password: TestPassword1234
  - name: 'user.2'
    system_administrator: true
    global_administrator: true
    password: TestPassword1234

I need to extract a list of users with a give role (eg global_administrator) to be assigned later on. I managed to use the map function to extract the data I need:

$corporate_roles = lookup('corporate_roles')
$global_admins = $corporate_roles.map | $hash | { if ($hash['global']){$hash['name']}}
notify { "global admins are: ${global_admins}":
  }

However this results in undef values seemingly making their way into the array for the users that don't match the criteria:

Notice: /Stage[main]/salesraft_test/Notify[global admins are: [, user.2]]/message: defined 'message' as 'global admins are: [, user.2]'
       Notice: Applied catalog in 0.04 seconds

I can get around this by using the filter function as such:

$test = $global_admins.filter | $users | {$users =~ NotUndef}

Which results in clean output:

Notice: /Stage[main]/salesraft_test/Notify[global admins are: [user.2]]/message: defined 'message' as 'global admins are: [user.2]'
       Notice: Applied catalog in 0.03 seconds

But I suspect there must be a better way of doing this and I am either missing some logic in my map or I am likely using the wrong function altogether for this.

I would like to know if there is a better way to achieve what I am trying to do?


Solution

  • But I suspect there must be a better way of doing this and I am either missing some logic in my map or I am likely using the wrong function altogether for this.

    map() emits exactly one output item for each input item, so if your objective is to apply a single function to obtain your wanted output from your (lengthier) input, then indeed, map will not achieve that.

    I would like to know if there is a better way to achieve what I am trying to do?

    Personally, I would do the job by filtering out the hashes you want from your input and then mapping those to the wanted output form (as opposed to mapping and then filtering the result):

    $global_admins = $corporate_roles.filter |$hash| {
        $hash['global_administrator']
      }.map |$hash| { $hash['name'] }
    

    I like that because it's nice and clear, but if you want to do it with one function call instead of two then you're probably looking for reduce:

    $global_admins = $corporate_roles.reduce([]) |$admins, $hash| {
      $hash['global_admin'] ? {
        true    => $admins << $hash['name'],
        default => $admins
      }
    }