Ansibleの呼吸 弐ノ型 フィルター芸「別々のリスト内にある辞書の共通キーで結合」


この投稿はAnsible 3 Advent Calendar 2019の2日目の記事です。

別々のリストにある辞書で共通キーがあるものを結合したいという時があったりなかったりあったりします。
その時のフィルター例です。
ここではシーケンスをリスト、マッピングを辞書と表現します

変数ファイル

ここでは次のファイルに定義した変数を使用します。

demon_slayer1.yml

---
damon_slayer:
  - organization: 鬼殺隊
    calacters:
      - name: 竈門炭治郎
        sword_color: 

      - name: 嘴平伊之助
        sword_color: 藍鼠

      - name: 我妻善逸
        sword_color: 黄色

demon_slayer2.yml

---
damon_slayer:
  - organization: 鬼殺隊
    calacters:
      - name: 竈門炭治郎
        words:
          - 頑張れ!人は心が原動力だから心はどこまでも強くなれる!
          - 頑張れ炭治郎頑張れ!俺は今までよくやってきた!俺はできる奴だ!
          - 水の呼吸 壱ノ型 水面斬り

      - name: 嘴平伊之助
        words:
          - 猪突猛進!
          - ゴメンネ弱クッテ
          - 獣の呼吸 壱ノ牙 穿ち抜き

      - name: 我妻善逸
        words:
          - こんなことある!?
          - 禰󠄀豆子ちゃんは俺がまもる
          - 雷の呼吸 壱ノ型 霹靂一閃

      - name: 竈門禰󠄀豆子
        words:
          - ''

両方の変数にある同じ organization 内にある name を共通キーにし、共通キーにマッチしたら辞書を結合するということをします。

Playbook

Playbookはこんな感じです。

---
- name: ni no kata playbook
  hosts: localhost
  gather_facts: no
  tasks:
    - name: include vars from daemon_slayer1.yml
      include_vars: demon_slayer1.yml

    - name: set daemon_slayer1 variable
      set_fact:
        daemon_slayer1: "{{ damon_slayer }}"

    - name: include vars from daemon_slayer2.yml
      include_vars: demon_slayer2.yml

    - name: set daemon_slayer2 variable
      set_fact:
        daemon_slayer2: "{{ damon_slayer }}"

    - debug:
        msg: >-
          {{ [item.1]
            | selectattr("name", "equalto", item.1.name)
            | list
            | first
            | combine(data.0 | default({}))
          }}
      vars:
        data: >-
          {{ daemon_slayer1
            | selectattr("organization", "equalto", item.0.organization)
            | map(attribute="calacters")
            | list
            | first
            | selectattr("name", "equalto", item.1.name)
            | list
          }}
      with_subelements:
        - "{{ daemon_slayer2 }}"
        - calacters

data 変数には daemon_slayer2with_subelements で回し daemon_slayer1organization とマッチしたら次に calacters から同じ name を見つけます。
そして、次は daemon_slayer2calacters を元に data に使った name と共通する辞書を抜き出して結合します。
default({}) があるのは daemon_slayer1 には禰󠄀豆子ちゃんがいないので、その対策です。

Playbook実行

これを実行します。

(venv) [root@localhost ansible]# ansible-playbook main.yml
[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all'


PLAY [ni no kata playbook] ****************************************************************************************************************************************************

TASK [include vars from daemon_slayer1.yml] ***********************************************************************************************************************************
ok: [localhost]

TASK [set daemon_slayer1 variable] ********************************************************************************************************************************************
ok: [localhost]

TASK [include vars from daemon_slayer2.yml] ***********************************************************************************************************************************
ok: [localhost]

TASK [set daemon_slayer2 variable] ********************************************************************************************************************************************
ok: [localhost]

TASK [debug] ******************************************************************************************************************************************************************
ok: [localhost] => (item=[{'organization': '鬼殺隊'}, {'name': '竈門炭治郎', 'words': ['頑張れ!人は心が原動力だから心はどこまでも強くなれる!', '頑張れ炭治郎頑張れ!俺は今までよくやってきた!俺はできる奴だ!', '水の呼吸 壱ノ型 水面斬り']}]) => {
    "msg": {
        "name": "竈門炭治郎",
        "sword_color": "黒",
        "words": [
            "頑張れ!人は心が原動力だから心はどこまでも強くなれる!",
            "頑張れ炭治郎頑張れ!俺は今までよくやってきた!俺はできる奴だ!",
            "水の呼吸 壱ノ型 水面斬り"
        ]
    }
}
ok: [localhost] => (item=[{'organization': '鬼殺隊'}, {'name': '嘴平伊之助', 'words': ['猪突猛進!', 'ゴメンネ弱クッテ', '獣の呼吸 壱ノ牙 穿ち抜き']}]) => {
    "msg": {
        "name": "嘴平伊之助",
        "sword_color": "藍鼠",
        "words": [
            "猪突猛進!",
            "ゴメンネ弱クッテ",
            "獣の呼吸 壱ノ牙 穿ち抜き"
        ]
    }
}
ok: [localhost] => (item=[{'organization': '鬼殺隊'}, {'name': '我妻善逸', 'words': ['こんなことある!?', '禰󠄀豆子ちゃんは俺がまもる', '雷の呼吸 壱ノ型 霹靂一閃']}]) => {
    "msg": {
        "name": "我妻善逸",
        "sword_color": "黄色",
        "words": [
            "こんなことある!?",
            "禰󠄀豆子ちゃんは俺がまもる",
            "雷の呼吸 壱ノ型 霹靂一閃"
        ]
    }
}
ok: [localhost] => (item=[{'organization': '鬼殺隊'}, {'name': '竈門禰󠄀豆子', 'words': ['']}]) => {
    "msg": {
        "name": "竈門禰󠄀豆子",
        "words": [
            ""
        ]
    }
}

PLAY RECAP ********************************************************************************************************************************************************************
localhost                  : ok=5    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

共通 name キーおよび片方には存在しない辞書の結合できました :)