サブ要素をループするAnsibleのwith_subelements

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

渡辺です。

AnsibleのPlaybookやRoleの再利用性を高めるためには、綺麗なグループ変数定義を目標とし、タスク定義でゴニョゴニョするのが基本です。 今回は、こんなグループ変数に役立つwith_subelementsを紹介します。

  vars:
    users:
      - name: user1
        groups:
          - wheel
          - hoge
      - name: user2
        groups:
          - hoge

要は多重ループをしたいってことになります。 プログラミング的にはこんなイメージです(擬似コード)。

for (user in users) {
  for (group in user.groups) {
     # user と groupによる処理
  }
}

with_subelementsによる多重ループ

結論から言えば、with_subelementsを利用することで多重ループを処理することができます。 ただ、微妙に解りにくいシンタックスなので、注意が必要です。

  tasks:
    - debug:
        msg: "name={{ item.0.name }}, group={{ item.1 }}"
      with_subelements:
        - "{{ users }}"
        - groups

with_subelementsにはリストでループする要素を指定します。 ひとつめ目の要素には、ループの主体となる要素、すなわちusersを指定します。 ここまでは通常のwith_itemsと同様です。 ふたつ目の要素には、ループの主体となる要素から多重ループさせたい要素名を指定します。 要素名ですから、変数の内部展開("{{ groups }}")と記述しないことに注意してください。

モジュールで要素を参照する時はitemという予約された変数名を使う点は、with_itemsと同様です。 ですが、はじめにitem.0item.1のように、with_subelementsで指定した要素のインデックスを指定します。 item.0usersの各要素になるため、nameを参照するならば、item.0.nameとなります。 item.1groupsの各要素なのでそのまま利用すれば良いでしょう。

出力結果は次のようになります。

TASK [debug] *******************************************************************
ok: [localhost] => (item=({u'name': u'user1'}, u'wheel')) => {
    "item": [
        {
            "name": "user1"
        }, 
        "wheel"
    ], 
    "msg": "name=user1, group=wheel"
}
ok: [localhost] => (item=({u'name': u'user1'}, u'hoge')) => {
    "item": [
        {
            "name": "user1"
        }, 
        "hoge"
    ], 
    "msg": "name=user1, group=hoge"
}
ok: [localhost] => (item=({u'name': u'user2'}, u'hoge')) => {
    "item": [
        {
            "name": "user2"
        }, 
        "hoge"
    ], 
    "msg": "name=user2, group=hoge"
}

期待通りにループできていることが解ると思います。

一部の要素がない場合はskip_missingを利用する

グループ変数が、次のように一部のネスト変数を持たない場合、多重ループはエラーとなります。

  vars:
    users:
      - name: user1
        groups:
          - wheel
          - hoge
      - name: user2
        groups:
          - hoge
      - name: user3

このような場合は、3つ目の要素flagsを追加し、ディクショナリにskip_missingTrueに設定することで、ループをスキップすることが可能です。

  tasks:
    - debug:
        msg: "name={{ item.0.name }}, group={{ item.1 }}"
      with_subelements:
        - "{{ users }}"
        - groups
        - flags:
            skip_missing: True

まとめ

グループ変数に定義したリストが更にリストを持つパターンは多いと思います。 with_itemsに加えて、with_subelementsを道具箱に入れておけば、Ansibleをより効率良く使えるようになるでしょう。 なお、3重以上のループには対応していません。