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


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

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

**ディクショナリの場合**
- `with_dict`と`include`,`loop_control`を使う

**リストの場合**
- `with_subelements`を使う



#sample

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

##ディレクトリ構成

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

##inventory

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

```bash:hosts/local
[local]
localhost
```

##vars

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


```yaml: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の該当箇所
```yaml: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_control`の`loop_var`で指定した任意の変数名に値がセットされます。
この場合、outer_item1、outer_item2が`outer_item`にセットされます。
参考：[公式ドキュメント](http://docs.ansible.com/ansible/playbooks_loops.html#loop-control)

```yaml: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`の子要素である`inner`を`with_dict`のループ対象に指定することで多重ループを実現します。

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

```YAML: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
```


####実行
```bash: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の該当箇所
```yaml: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重以上の多重ループには対応していません。

```yaml: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
```

####実行
```bash: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_dict`と`include`,`loop_control`を使えば複雑な多重ループを実現できますが、反面playbookの見通しが悪くなります。
個人的に、多重ループは`with_subelements`で処理できるレベルに留めるのがいいと思います。