5
7

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-08-16

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
5
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
7