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


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

備忘

dictionaryデータ構造

foo:
  uid: 1000
  shell: /bin/bash
bar:
  uid: 1001
  shell: /bin/zsh
  • 樹形構造なので、構造が理解し易い
  • 値の参照が容易
    • fooさんのuidはfoo.uidもしくはfoo['uid']
  • 階層ごとに唯一のユニークなキーが必要
  • 通常のループはdictionary(データのキー)に対して行う
  • deep mergeが容易

list_of_dictionariesデータ構造

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

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

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

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

値の取得とループの例

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 における値の参照

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

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

この様な書式も可能。

  - 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フィルターは不要

  - 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を求める順序とした例

  - 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 などが必要。

  - 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

  - 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

  - 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

  - 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

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

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