Ansible

Ansibleでの多重ループあれこれ

More than 1 year has passed since last update.

はじめに

Ansibleでループ処理を実現するためには、with_itemswith_dictを使います。
では、多重ループを実現するためにはどうすればいいでしょうか。

ループさせる変数がディクショナリかリストかで実現方法が変わります。

ディクショナリの場合
- with_dictinclude,loop_controlを使う

リストの場合
- with_subelementsを使う

sample

では、実際に試してみます。

ディレクトリ構成

ディレクトリ構成
dict_loop.yml
dict_loop_inner.yml
list_loop.yml
hosts/local    #inventory
host_vars/
|--localhost.yml

inventory

localhostで実行するためのインベントリファイルを用意します。

hosts/local
[local]
localhost

vars

こんな感じに変数を定義します。今回はhost_varsで定義しました。

host_vars/localhost.yml
---
my_vars:
  dict_loop:
    outer_item1:
      inner:
        foo: inner_item1_1
        hoge: inner_item1_2
    outer_item2:
      inner:
        foo: inner_item2_1
        hoge: inner_item2_2
  list_loop:
    - outer: outer_item1
      inner:
        - inner_item1_1
        - inner_item1_2
    - outer: outer_item2
      inner:
        - inner_item2_1
        - inner_item2_2

Playbook

ディクショナリの多重ループ

varsの該当箇所

host_vars/localhost.yml
---
my_vars:
  dict_loop:
    outer_item1:
      inner:
        foo: inner_item1_1
        hoge: inner_item1_2
    outer_item2:
      inner:
        foo: inner_item2_1
        hoge: inner_item2_2

dict_loop.yml

まずwith_dictで、外側のループ対象を指定します。その際、loop_controlloop_varで指定した任意の変数名に値がセットされます。
この場合、outer_item1、outer_item2がouter_itemにセットされます。
参考:公式ドキュメント

dict_loop.yml
---
- name: dict_loop
  hosts: localhost
  connection: local
  tasks:
    - name: outer loop
      include: dict_loop_inner.yml
      with_dict: "{{ my_vars.dict_loop }}"
      loop_control:
        loop_var: outer_item

dict_loop_inner.yml

そして、outer_itemごとにincludeで指定されたdict_loop_inner.ymlが実行されます。
includeされたタスクの中でもouter_itemを参照できます。そして、outer_itemの子要素であるinnerwith_dictのループ対象に指定することで多重ループを実現します。

loop_controlでこの値をinner_itemにセットしているので(必須ではありませんが)、inner_item.valueとして値(この場合、inner_item1_1,inner_item1_2...)が取り出せます。

dict_loop_inner.yml
---
- name: inner loop
  debug:
    msg: "outer:{{ outer_item.key }} inner:{{ inner_item.value }}"
  with_dict: "{{ outer_item.value.inner }}"
  loop_control:
    loop_var: inner_item

実行

Command
$ ansible-playbook -i hosts/local -l localhost dict_loop.yml

結果

PLAY [dict_loop] ***************************************************************

TASK [outer loop _raw_params=inner_loop.yml] ***********************************
included: /PATH/TO/inner_loop.yml for localhost
included: /PATH/TO/inner_loop.yml for localhost

TASK [inner loop msg=outer:{{ outer_item.key }} inner:{{ inner_item.value }}] **
ok: [localhost] => (item={'key': u'foo', 'value': u'inner_item1_1'}) => {
    "inner_item": {
        "key": "foo",
        "value": "inner_item1_1"
    },
    "msg": "outer:outer_item1 inner:inner_item1_1"
}
ok: [localhost] => (item={'key': u'hoge', 'value': u'inner_item1_2'}) => {
    "inner_item": {
        "key": "hoge",
        "value": "inner_item1_2"
    },
    "msg": "outer:outer_item1 inner:inner_item1_2"
}

TASK [inner loop msg=outer:{{ outer_item.key }} inner:{{ inner_item.value }}] **
ok: [localhost] => (item={'key': u'foo', 'value': u'inner_item2_1'}) => {
    "inner_item": {
        "key": "foo",
        "value": "inner_item2_1"
    },
    "msg": "outer:outer_item2 inner:inner_item2_1"
}
ok: [localhost] => (item={'key': u'hoge', 'value': u'inner_item2_2'}) => {
    "inner_item": {
        "key": "hoge",
        "value": "inner_item2_2"
    },
    "msg": "outer:outer_item2 inner:inner_item2_2"
}

リストの多重ループ

varsの該当箇所

host_vars/localhost.yml
---
my_vars:
  list_loop:
    - outer: outer_item1
      inner:
        - inner_item1_1
        - inner_item1_2
    - outer: outer_item2
      inner:
        - inner_item2_1
        - inner_item2_2

list_loop.yml

with_subelementsで、外側のループ対象と内側のループ対象を定義します。
タスク中で、前者がitem.0に、後者がitem.1にセットされます。
3重以上の多重ループには対応していません。

list_loop.yml
---
- name: list_loop
  hosts: localhost
  connection: local
  tasks:
    - debug:
        msg: "outer:{{ item.0.outer }} inner:{{ item.1 }}"
      with_subelements:
        - "{{ my_vars.list_loop }}"
        - inner

実行

Command
$ ansible-playbook -i hosts/local -l localhost list_loop.yml

結果

PLAY [list_loop] ***************************************************************

TASK [debug msg=outer:{{ item.0.outer }} inner:{{ item.1 }}] *******************
ok: [localhost] => (item=({u'outer': u'outer_item1'}, u'inner_item1_1')) => {
    "item": [
        {
            "outer": "outer_item1"
        },
        "inner_item1_1"
    ],
    "msg": "outer:outer_item1 inner:inner_item1_1"
}
ok: [localhost] => (item=({u'outer': u'outer_item1'}, u'inner_item1_2')) => {
    "item": [
        {
            "outer": "outer_item1"
        },
        "inner_item1_2"
    ],
    "msg": "outer:outer_item1 inner:inner_item1_2"
}
ok: [localhost] => (item=({u'outer': u'outer_item2'}, u'inner_item2_1')) => {
    "item": [
        {
            "outer": "outer_item2"
        },
        "inner_item2_1"
    ],
    "msg": "outer:outer_item2 inner:inner_item2_1"
}
ok: [localhost] => (item=({u'outer': u'outer_item2'}, u'inner_item2_2')) => {
    "item": [
        {
            "outer": "outer_item2"
        },
        "inner_item2_2"
    ],
    "msg": "outer:outer_item2 inner:inner_item2_2"
}

まとめ

Ansibleでの2種類の多重ループの方法についてご紹介しました。

with_dictinclude,loop_controlを使えば複雑な多重ループを実現できますが、反面playbookの見通しが悪くなります。
個人的に、多重ループはwith_subelementsで処理できるレベルに留めるのがいいと思います。