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

この投稿は[Ansible 3 Advent Calendar 2019](https://qiita.com/advent-calendar/2019/ansible3)の2日目の記事です。

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

# 変数ファイル

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

**demon_slayer1.yml**

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

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

      - name: 我妻善逸
        sword_color: 黄色
```

**demon_slayer2.yml**

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

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

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

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

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

# Playbook

Playbookはこんな感じです。

```yaml
---
- 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_slayer2` を `with_subelements` で回し `daemon_slayer1` の `organization` とマッチしたら次に `calacters` から同じ `name` を見つけます。
そして、次は `daemon_slayer2` の `calacters` を元に `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` キーおよび片方には存在しない辞書の結合できました :)