[Ruby] Extract a hash from the Terraform: (and Ansible: and Chef:) array where a key is a specific value.

2 minute read

What you want to do

In terraform, take out the attr whose name is n2 from the array (list) of hash (map) as shown below. The key is unique.


array_01:
  -name: n1
    attr: attr1
  -name: n2
    attr: attr2
  -name: n3
    attr: attr3
If it is ansible, selectattr filter can be used, and if chef ruby, select method can be used.

Method: Use wildcard index.

Example

Use .tf.json instead of .ft

json:main.tf.json


{
  "locals": {
    "array_01": [
      {"name": "n1", "attr": "attr1" },
      {"name": "n2", "attr": "attr2" },
      {"name": "n3", "attr": "attr3"}
    ]
  },
  "output": {
    "array_01_name": {
      "value": "${ local.array_01.*.name }"
    },
    "n2_attr": {
      "value": "${ local.array_01[ index( local.array_01.*.name, \"n2\") ].attr }"
    }
  }
}

Also put yaml format using yq utility to make the contents easy to see

$ yq -y .main.tf.json
locals:
  array_01:
    -name: n1
      attr: attr1
    -name: n2
      attr: attr2
    -name: n3
      attr: attr3
output:
  array_01_name:
    value: ${ local.array_01.*.name}
  n2_attr:
    value: ${ local.array_01[ index( local.array_01.*.name, "n2") ].attr}

Here, local.array_01.*.name is an array (list) of elements of local.array_01 and the value of key is name:, and index(local.array_01.*.name, “n2”) is name Is 1 of the element number (starting from 0) where n is n2. It can be local.array_01[*].name.

$ terraform apply --auto-approve

Apply complete! Resources: 0 added, 0 changed, 0 destroyed.

Outputs:

array_01_name = [
  "n1",
  "n2",
  "n3",
]
n2_attr = attr2

It should be noted that teraform init was not necessary, probably because it was only local and output, and the .terraform directory was not created when terraform init was executed.

Added

There were cases where ?index became an error because the element types did not match when the wildcard acquisition was performed from the resource. Bug in terraform or provider? It was avoided by giving the list created by the for statement to index.

Example of avoiding by passing the list created by for statement to index

json:main.tf.json


{
  "locals": {
    "array_01": [
      {
        "name": "n1",
        "attr": "attr1"
      },
      {
        "name": "n2",
        "attr": "attr2"
      },
      {
        "name": "n3",
        "attr": "attr3"
      }
    ]
  },
  "output": {
    "array_01_name": {
      "value": "${ local.array_01.*.name }"
    },
    "n2_attr": {
      "value": "${ local.array_01[ index( [for s in local.array_01 :s.name ], \"n2\") ].attr }"
    }
  }
}
$ terraform apply --auto-approve

Apply complete! Resources: 0 added, 0 changed, 0 destroyed.

Outputs:

array_01_name = [
  "n1",
  "n2",
  "n3",
]
n2_attr = attr2

Ansible example

selectattr.yml


- hosts: all
  gather_facts: false
  vars:
    array_01:
      -name: n1
        attr: attr1
      -name: n2
        attr: attr2
      -name: n3
        attr: attr3
  tasks:
    -debug: msg={{ (array_01 | selectattr("name", "equalto", "n2") | list )[0].attr }}

    -debug: msg={{ (array_01 | selectattr("name", "==", "n2") | list )[0].attr }}

    -debug: msg={{ (array_01 | json_query("[?name=='n2'].attr"))[0] }}
$ ansible-playbook -i localhost, -c local selectattr.yml
PLAY [all] ********************************************** ************************************************** *******

TASK [debug] ********************************************** ************************************************** *****
ok: [localhost] => {
    "msg": "attr2"
}

TASK [debug] ********************************************** ************************************************** *****
ok: [localhost] => {
    "msg": "attr2"
}

TASK [debug] ********************************************** ************************************************** *****
ok: [localhost] => {
    "msg": "attr2"
}

PLAY RECAP ************************************************ ************************************************** *****
localhost :ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

Chef (or rather Ruby) example

select.rb



require'yaml'

array_01 = YAML.load(<<~EOS)
  -name: n1
    attr: attr1
  -name: n2
    attr: attr2
  -name: n3
    attr: attr3
EOS

p array_01.select {|e| e['name'] =='n2' }[0]['attr']
$ ruby select.rb
"attr2"

Reference: terraform

https://stackoverflow.com/questions/52119400/how-to-get-an-object-from-a-list-of-objects-in-terraform

https://stackoverflow.com/a/59437020/13819312

A little touch on wildcards. https://www.terraform.io/docs/configuration-0-11/resources.html

No wildcard explanation was found in the official index section. https://www.terraform.io/docs/configuration/functions/index.html

Reference: ansible

https://www.it-swarm-ja.tech/ja/jinja2/ansible: Filter the list by attributes/1053979194/