Ansible Tips: dictionary や list_of_dictionaries 構造に対する値の参照とループ操作


# dictionaryデータ構造, list_of_dictionariesデータ構造 に対する操作

備忘


## dictionaryデータ構造



```yaml
foo:
  uid: 1000
  shell: /bin/bash
bar:
  uid: 1001
  shell: /bin/zsh
```

- 樹形構造なので、構造が理解し易い
- 値の参照が容易
    - fooさんのuidはfoo.uidもしくはfoo['uid']
- 階層ごとに唯一のユニークなキーが必要
- 通常のループはdictionary(データのキー)に対して行う
- deep mergeが容易
    - https://qiita.com/sky_jokerxx/items/5b541b6aa828f678e630 コメント


## list_of_dictionariesデータ構造


```yaml
- name: foo
  uid: 1000
  shell: /bin/bash
- name: bar
  uid: 1001
  shell: /bin/zsh
```

ここではこの様なデータ構造をlist_of_dictionaries(もしくは単にlist)構造と呼ぶことにする。(正式な呼び方は存在する？)

- 値の参照が困難
    - fooさんのuidはシンプルには表せない
- 単一のキーは必須では無い
- 通常のループはlistに対して行う → シンプル
- ~~変数をキーとして使用する事が困難~~
    - https://qiita.com/hiroyuki_onodera/items/38c3a4337b282e16cc6d
- deep mergeがあまり簡単ではない
    - https://qiita.com/sky_jokerxx/items/5b541b6aa828f678e630


## それぞれの型の相互変換 (1階層、2階層まで)

Ansible Tips: list of dictionaryとdictionaryを相互に変換する
https://qiita.com/hiroyuki_onodera/items/d60208f011ea663555ff


## 値の取得とループの例

```yaml:list_of_dictionaries.yml

---
- hosts: localhost
  gather_facts: false
  vars:

    users_dictionary: # dictionaryの例
      foo:
        uid: 1000
        shell: "/bin/bash"
      bar:
        uid: 1001
        shell: "/bin/zsh"

    users_list_of_dictionaries: # list_of_dictionariesの例
    - name: foo
      uid: 1000
      shell: "/bin/bash"
    - name: bar
      uid: 1001
      shell: "/bin/zsh"

  tasks:
```

### 1. dictionary における値の参照

#### 固定されたキーによる指定方法

```yaml
  - name: 1-1 users_dictionary => foo's uid
    debug:
      var: users_dictionary.foo.uid
```

```
TASK [1-1 users_dictionary => foo's uid] *******************************************************************
ok: [localhost] => {
    "users_dictionary.foo.uid": "1000"
}
```

#### この様な書式も可能。

```yaml
  - name: 1-2 users_dictionary => foo's uid
    debug:
      var: users_dictionary['foo']['uid']
```

```
TASK [1-2 users_dictionary => foo's uid] *******************************************************************
ok: [localhost] => {
    "users_dictionary['foo']['uid']": "1000"
}
```

キーに'foo'などを値として持つ変数を使用することで間接参照も可能。



### 2. list_of_dictionaries における値の参照

```
  - name: 2-1 users_list_of_dictionaries => foo's uid
    debug:
      var: users_list_of_dictionaries|selectattr('name','==','foo')|list|map(attribute='uid')|list|first
```

```
TASK [2-1 users_list_of_dictionaries => foo's uid] *********************************************************
ok: [localhost] => {
    "users_list_of_dictionaries|selectattr('name','==','foo')|list|map(attribute='uid')|list|first": "1000"
}
```

- selectattrフィルターによりnameがfooの要素を取り出す
- listフィルターによりlistとする
- mapフィルターによりキーがuidの値を抽出する
- listフィルターによりlistとする
- firstフィルターにより最初の要素を取り出す ( )[0] などでも可能

https://jinja.palletsprojects.com/en/2.11.x/templates/#selectattr
https://jinja.palletsprojects.com/en/2.11.x/templates/#list
https://jinja.palletsprojects.com/en/2.11.x/templates/#map
https://jinja.palletsprojects.com/en/2.11.x/templates/#first


#### 結果をlistとして外部に連携する必要がないのであればlistフィルターは不要

```yaml
  - name: 2-2 users_list_of_dictionaries => foo's uid
    debug:
      var: users_list_of_dictionaries|selectattr('name','==','foo')|map(attribute='uid')|first
```

```
TASK [2-2 users_list_of_dictionaries => foo's uid] *********************************************************
ok: [localhost] => {
    "users_list_of_dictionaries|selectattr('name','==','foo')|map(attribute='uid')|first": "1000"
}
```

#### 先頭要素を取り出してから().uidにてuidを求める順序とした例 

```yaml
  - name: 2-3 users_list_of_dictionaries => foo's uid
    debug:
      var: (users_list_of_dictionaries|selectattr('name','==','foo')|first).uid
```

```
TASK [2-3 users_list_of_dictionaries => foo's uid] *****************************************************************************
ok: [localhost] => {
    "(users_list_of_dictionaries|selectattr('name','==','foo')|first).uid": "1000"
}
```

#### json_queryフィルターを用いる事でより簡潔に記述できる。
但し、json_queryフィルターを使用するには、コントローラーノードに sudo pip install jmespath などが必要。


```yaml
  - name: 2-4 users_list_of_dictionaries => foo's uid
    debug:
      var: users_list_of_dictionaries|json_query("[?name=='foo'].uid")|first
```

```
TASK [2-4 users_list_of_dictionaries => foo's uid] *********************************************************
ok: [localhost] => {
    "users_list_of_dictionaries|json_query(\"[?name=='foo'].uid\")|first": "1000"
}
```

### 3. dictionary におけるloop

#### with_dict, loop + dict2items
```yaml
  - name: 3-1 users_dictionary => loop
    debug:
      var: item
    with_dict: '{{ users_dictionary }}'
```

```
TASK [3-1 users_dictionary => loop] ************************************************************************
ok: [localhost] => (item={'key': 'foo', 'value': {'uid': 1000, 'shell': '/bin/bash'}}) => {
    "ansible_loop_var": "item",
    "item": {
        "key": "foo",
        "value": {
            "shell": "/bin/bash",
            "uid": 1000
        }
    }
}
ok: [localhost] => (item={'key': 'bar', 'value': {'uid': 1001, 'shell': '/bin/zsh'}}) => {
    "ansible_loop_var": "item",
    "item": {
        "key": "bar",
        "value": {
            "shell": "/bin/zsh",
            "uid": 1001
        }
    }
}
```

with_dict:の行は、loop: '{{ users_dictionary|dict2items }}' とも書ける
それぞれの値は以下にて参照可能

- item.key
- item.value.shell
- item.value.uid


#### loop + dictsort

```yaml
  - name: 3-2 users_dictionary => loop
    debug:
      var: item
    loop: "{{ users_dictionary|dictsort }}"
```

```
TASK [3-2 users_dictionary => loop] ************************************************************************
ok: [localhost] => (item=['bar', {'uid': 1001, 'shell': '/bin/zsh'}]) => {
    "ansible_loop_var": "item",
    "item": [
        "bar",
        {
            "shell": "/bin/zsh",
            "uid": 1001
        }
    ]
}
ok: [localhost] => (item=['foo', {'uid': 1000, 'shell': '/bin/bash'}]) => {
    "ansible_loop_var": "item",
    "item": [
        "foo",
        {
            "shell": "/bin/bash",
            "uid": 1000
        }
    ]
}
```

それぞれの値は以下にて参照可能
- item.0
- item.1.shell
- item.1.uid


### 4. users_list_of_dictionaries におけるloop

#### loop

```yaml
  - name: 4-1 users_list_of_dictionaries => loop
    debug:
      var: item
    loop: "{{ users_list_of_dictionaries }}"
```




```
TASK [4-1 users_list_of_dictionaries => loop] **************************************************************
ok: [localhost] => (item={'name': 'foo', 'uid': 1000, 'shell': '/bin/bash'}) => {
    "ansible_loop_var": "item",
    "item": {
        "name": "foo",
        "shell": "/bin/bash",
        "uid": 1000
    }
}
ok: [localhost] => (item={'name': 'bar', 'uid': 1001, 'shell': '/bin/zsh'}) => {
    "ansible_loop_var": "item",
    "item": {
        "name": "bar",
        "shell": "/bin/zsh",
        "uid": 1001
    }
}
```

それぞれの値は以下にて参照可能

- item.name
- item.shell
- item.uid





## 値の取得とループの例(通し)

```yaml:list_of_dictionaries.yml

---
- hosts: localhost
  gather_facts: false
  vars:

    users_dictionary: # dictionaryの例
      foo:
        uid: 1000
        shell: "/bin/bash"
      bar:
        uid: 1001
        shell: "/bin/zsh"

    users_list_of_dictionaries: # list_of_dictionariesの例
    - name: foo
      uid: 1000
      shell: "/bin/bash"
    - name: bar
      uid: 1001
      shell: "/bin/zsh"

  tasks:

  # dictionary における値の参照

  - name: 1-1 users_dictionary => foo's uid
    debug:
      var: users_dictionary.foo.uid
    # ↑
    # 固定されたキーによる指定方法

  - name: 1-2 users_dictionary => foo's uid
    debug:
      var: users_dictionary['foo']['uid']
    # ↑
    # この様な書式も可能。
    # キーに'foo'などを値として持つ変数を使用することで間接参照も可能。

  # list_of_dictionaries における値の参照

  - name: 2-1 users_list_of_dictionaries => foo's uid
    debug:
      var: users_list_of_dictionaries|selectattr('name','==','foo')|list|map(attribute='uid')|list|first
    # ↑
    # selectattrフィルターによりnameがfooの要素を取り出す
    # listフィルターによりlistとする
    # mapフィルターによりキーがuidの値を抽出する
    # listフィルターによりlistとする
    # firstフィルターにより最初の要素を取り出す ( )[0] などでも可能

    # https://jinja.palletsprojects.com/en/2.11.x/templates/#selectattr
    # https://jinja.palletsprojects.com/en/2.11.x/templates/#list
    # https://jinja.palletsprojects.com/en/2.11.x/templates/#map
    # https://jinja.palletsprojects.com/en/2.11.x/templates/#first

  - name: 2-2 users_list_of_dictionaries => foo's uid
    debug:
      var: users_list_of_dictionaries|selectattr('name','==','foo')|map(attribute='uid')|first
    # ↑
    # 結果をlistとして外部に連携する必要がないのであればlistフィルターは不要

  - name: 2-3 users_list_of_dictionaries => foo's uid
    debug:
      var: (users_list_of_dictionaries|selectattr('name','==','foo')|first).uid
    # ↑
    # 先頭要素を取り出してから().uidにてuidを求める順序とした例 

  - name: 2-4 users_list_of_dictionaries => foo's uid
    debug:
      var: users_list_of_dictionaries|json_query("[?name=='foo'].uid")|first
    # ↑
    # json_queryフィルターを用いる事でより簡潔に記述できる。
    # 但し、json_queryフィルターを使用するには、コントローラーノードに sudo pip install jmespath などが必要。


  # dictionary におけるloop

  - name: 3-1 users_dictionary => loop
    debug:
      var: item
    with_dict: '{{ users_dictionary }}'
    # ↑
    # with_dict:の行は、loop: '{{ users_dictionary|dict2items }}' とも書ける
    # それぞれの値は以下にて参照可能
    # item.key
    # item.value.shell
    # item.value.uid

  - name: 3-2 users_dictionary => loop
    debug:
      var: item
    loop: "{{ users_dictionary|dictsort }}"
    # ↑
    # それぞれの値は以下にて参照可能
    # item.0
    # item.1.shell
    # item.1.uid


  # users_list_of_dictionaries におけるloop

  - name: 4-1 users_list_of_dictionaries => loop
    debug:
      var: item
    loop: "{{ users_list_of_dictionaries }}"
    # ↑
    # それぞれの値は以下にて参照可能
    # item.name
    # item.shell
    # item.uid
```

```
$ ansible-playbook -i localhost, list_of_dictionaries.yml

PLAY [localhost] *******************************************************************************************

TASK [1-1 users_dictionary => foo's uid] *******************************************************************
ok: [localhost] => {
    "users_dictionary.foo.uid": "1000"
}

TASK [1-2 users_dictionary => foo's uid] *******************************************************************
ok: [localhost] => {
    "users_dictionary['foo']['uid']": "1000"
}

TASK [2-1 users_list_of_dictionaries => foo's uid] *********************************************************
ok: [localhost] => {
    "users_list_of_dictionaries|selectattr('name','==','foo')|list|map(attribute='uid')|list|first": "1000"
}

TASK [2-2 users_list_of_dictionaries => foo's uid] *********************************************************
ok: [localhost] => {
    "users_list_of_dictionaries|selectattr('name','==','foo')|map(attribute='uid')|first": "1000"
}

TASK [2-3 users_list_of_dictionaries => foo's uid] *****************************************************************************
ok: [localhost] => {
    "(users_list_of_dictionaries|selectattr('name','==','foo')|first).uid": "1000"
}

TASK [2-4 users_list_of_dictionaries => foo's uid] *********************************************************
ok: [localhost] => {
    "users_list_of_dictionaries|json_query(\"[?name=='foo'].uid\")|first": "1000"
}

TASK [3-1 users_dictionary => loop] ************************************************************************
ok: [localhost] => (item={'key': 'foo', 'value': {'uid': 1000, 'shell': '/bin/bash'}}) => {
    "ansible_loop_var": "item",
    "item": {
        "key": "foo",
        "value": {
            "shell": "/bin/bash",
            "uid": 1000
        }
    }
}
ok: [localhost] => (item={'key': 'bar', 'value': {'uid': 1001, 'shell': '/bin/zsh'}}) => {
    "ansible_loop_var": "item",
    "item": {
        "key": "bar",
        "value": {
            "shell": "/bin/zsh",
            "uid": 1001
        }
    }
}

TASK [3-2 users_dictionary => loop] ************************************************************************
ok: [localhost] => (item=['bar', {'uid': 1001, 'shell': '/bin/zsh'}]) => {
    "ansible_loop_var": "item",
    "item": [
        "bar",
        {
            "shell": "/bin/zsh",
            "uid": 1001
        }
    ]
}
ok: [localhost] => (item=['foo', {'uid': 1000, 'shell': '/bin/bash'}]) => {
    "ansible_loop_var": "item",
    "item": [
        "foo",
        {
            "shell": "/bin/bash",
            "uid": 1000
        }
    ]
}

TASK [4-1 users_list_of_dictionaries => loop] **************************************************************
ok: [localhost] => (item={'name': 'foo', 'uid': 1000, 'shell': '/bin/bash'}) => {
    "ansible_loop_var": "item",
    "item": {
        "name": "foo",
        "shell": "/bin/bash",
        "uid": 1000
    }
}
ok: [localhost] => (item={'name': 'bar', 'uid': 1001, 'shell': '/bin/zsh'}) => {
    "ansible_loop_var": "item",
    "item": {
        "name": "bar",
        "shell": "/bin/zsh",
        "uid": 1001
    }
}

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