Terraform: (and Ansible: and Chef :) Extract a hash from an array where a key has a specific value.

What you want to do

** With terraform, ** Attr with name n2 is taken out 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, the selectattr filter can be used, and if chef | ruby, the select method can be used.

Method: Use wildcard-specified 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 }"
    }
  }
}

I also put the yaml format using the yq utility to make the contents easier 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 element of local.array_01 and the key is an array (list) of values with name :, and index (local.array_01. \ . Name, "n2") is name. Is 1 of the element number (starting with 0) where is n2. You can also use 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

In addition, probably because it is only local and output, terraform init was not necessary, and even if terraform init was performed, the .terraform directory was not created.

Postscript

In addition, when fetching from a resource by wildcard, there was a case where the element types were not aligned and? Index became an error. terraform or provider bug? This was avoided by giving the list created by the for statement to index.

Example of avoiding by passing the list created by the 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 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

I'm touching a wildcard a little. https://www.terraform.io/docs/configuration-0-11/resources.html

No description of the wildcard 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:リストを属性でフィルタリングする/1053979194/

Recommended Posts

Terraform: (and Ansible: and Chef :) Extract a hash from an array where a key has a specific value.
[Ruby] How to extract a specific value from an array under multiple conditions [select / each]
[Java] Get a random value from an array
Regarding hash key and value mapping
[Ruby] I want to extract only the value of the hash and only the key
[ruby] How to assign a value to a hash by referring to the value and key of another hash