アレイから、あるキーが特定の値であるハッシュを取り出す (Terraform, Ansible, Chef)


備忘


# やりたい事

**terraformにて、**以下の様なハッシュ(マップ)のアレイ(リスト)から、nameがn2のattrを取り出す。
キーはユニークとする。


```yaml

array_01:
  - name: n1
    attr: attr1
  - name: n2
    attr: attr2
  - name: n3
    attr: attr3
```

なお、ansibleならば、selectattrフィルターが、chef|rubyならばselectメソッドが使用可能。


# 追記 方法2: forループを使用 (2023/02)
terraformの場合、for文でも可能
例

```terraform:main.tf
locals {
  array_01 = [
    {
      name = "n1",
      attr = "attr1"
    },
    {
      name = "n2",
      attr = "attr2"
    },
    {
      name = "n3",
      attr = "attr3"
    }
  ]
}

output "out1" {
  value = local.array_01[index(local.array_01.*.name, "n2")].attr  # 方法1
}

output "out2" {
  value = [for x in local.array_01 : x.attr if x.name == "n2"][0]  # 方法2
}
```

```
$ terraform apply --auto-approve

No changes. Your infrastructure matches the configuration.

Terraform has compared your real infrastructure against your configuration and found no differences, so no changes are needed.

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

Outputs:

out1 = "attr2"
out2 = "attr2"
```
方法1は以下の方法1
方法2が追記した方法で、forループで回しつつif文で要素を選択。結果はlistなので[0]にて最初の要素を取り出し

方法2の方が僅かに読みやすいかもしれませんね。



# 方法1: ワイルドカード指定のindexを使用する。

#### 例

なお、.ftではなく、.tf.jsonを使用

```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 }"
    }
  }
}
```

内容を見やすくする為にyqユーティリティを使ってyamlフォーマット化したものも載せておく

```yaml
$ 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 }
```

ここで、local.array_01.\*.nameは、local.array_01の要素でキーがname:の値のアレイ(リスト)となり、index( local.array_01.\*.name, "n2" ) は、nameがn2である要素番号(0始まり)の1となる。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
```

なお、localとoutputのみだからか、terraform initも不要であったし、terraform initを行っても.terraformディレクトリーは作成されなかった。


### 追記

なお、リソースからワイルドカードによる取得を行った際、要素のタイプが揃わず?indexがエラーとなるケースがあった。terraformかproviderのバグ？
indexに、for文にて作成したリストを与える事で回避した。

#### indexにfor文で作成したリストを渡して回避した例

```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の例

```yaml: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 | selectattr("name", "==", "n2") | list | first ).attr }}

    - debug: msg={{ ( array_01 | json_query("[?name=='n2'].attr"))[0] }}
```

- どこかのバージョンから | list は不要。(ansible 7.0.0, ansible-core 2.14.0にて確認)

- json_query には sudo pip install jmespath などが必要

```
$ ansible-playbook -i localhost, -c local selectattr.yml
PLAY [all] *******************************************************************************************************

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

以下同様
```

# Chef(というかRuby)の例

```ruby: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"
```

# 参考: terraform

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

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

ワイルドカードに少し触れている。
https://www.terraform.io/docs/configuration-0-11/resources.html

公式のindexの項にはワイルドカードの説明は見つけられなかった。
https://www.terraform.io/docs/configuration/functions/index.html

## 参考: ansible

https://www.it-swarm-ja.tech/ja/jinja2/ansible：リストを属性でフィルタリングする/1053979194/