Ansible Tips: dict 構造においてキー部分に対する文字列操作をフィルターやjinja2 loopにて行う

備忘

# 課題

以下の様なリストにおいて、各dictのキー部分を文字列操作したい。
例えば以下のキーを小文字化する例を考える。

```yaml
    - KEY1: Val1
      KEY2: Val2
      KEY3: Val3
    - KEY1: Val4
      KEY2: Val5
      KEY3: val6
```

↓

```yaml
    - key1: Val1
      key2: Val2
      key3: Val3
    - key1: Val4
      key2: Val5
      key3: Val6
```


# ソリューション例

キーと値を一旦分離し、keyに対してフィルターなりで変更を加え、zipフィルターで再び合わせる。


## サンプルplaybook

```yaml
---
- hosts: localhost
  gather_facts: false
  vars:
    foo:
    - KEY1: Val1
      KEY2: Val2
      KEY3: Val3
    - KEY1: Val4
      KEY2: Val5
      KEY3: Val6
  tasks:
  - loop: "{{ foo }}"
    debug:
      msg: "{{ dict(item.keys()|map('lower')|zip(item.values())) }}"
    register: r
  - debug:
      var: r.results|map(attribute="msg")|list
```

- loopにて、listの各要素に対する処理に分解
- item.keys()
    - dictのkey部分のlistを作成
    - 例えば["KEY1", "KEY2", "KEY3"]
    - item|dict2items|map(attribute='key')|listと同義
- item.values()
    - dictのvalue部分のlistを作成
    - 例えば["Val1", "Val2", "Val3"]
    - item|dict2items|map(attribute='value')|listと同義
- |map('lower')
    - listの各要素にlowerフィルターを適用
    - 例えば["key1", "key2", "key3"]を作成
    - 内部で指定するフィルターに引数が必要な場合、map('フィルター名', '第一引数', '第二引数',,)などとして渡す事が可能
    - 要素が文字列ならば、regex_replace フィルターを使用することで、map('regex_replace', '^(.*)$', 'https://\\1')などとして、各文字列に対する正規表現での編集も可能
- dict(xxxx|zip(yyyy))により、list xxxxとlist yyyyからdictを再作成
- |map(attribute="msg")|list
    - debugのloop結果から出力内容"msg"の内容でlistを再作成

## 実行結果

```
 $ ansible-playbook -i localhost, site.yml

PLAY [localhost] *************************************************************************************************

TASK [debug] *****************************************************************************************************
ok: [localhost] => (item={'KEY1': 'Val1', 'KEY2': 'Val2', 'KEY3': 'Val3'}) => {
    "msg": {
        "key1": "Val1",
        "key2": "Val2",
        "key3": "Val3"
    }
}
ok: [localhost] => (item={'KEY1': 'Val4', 'KEY2': 'Val5', 'KEY3': 'val6'}) => {
    "msg": {
        "key1": "Val4",
        "key2": "Val5",
        "key3": "Val6"
    }
}

TASK [debug] *****************************************************************************************************
ok: [localhost] => {
    "r.results|map(attribute=\"msg\")|list": [
        {
            "key1": "Val1",
            "key2": "Val2",
            "key3": "Val3"
        },
        {
            "key1": "Val4",
            "key2": "Val5",
            "key3": "Val6"
        }
    ]
}

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

```


# 参考
Ansible Tips: dictionary や list_of_dictionaries 構造に対する値の参照とループ操作
https://qiita.com/hiroyuki_onodera/items/b42f728c2270a7bccd97

# 雑感
使える武器がフィルターだったりpythonの関数だったり。。



## 別解1 loop時に作った解をまとめる

```yaml

  - set_fact:
      list0: []
  - loop: "{{ foo }}"
    set_fact:
      list0: "{{ list0 + [ dict(item.keys()|map('lower')|zip(item.values())) ] }}"

  - debug:
      var: list0
```

```
TASK [set_fact] *****************************************************************************************************
ok: [localhost]

TASK [set_fact] *****************************************************************************************************
ok: [localhost] => (item={'KEY1': 'Val1', 'KEY2': 'Val2', 'KEY3': 'Val3'})
ok: [localhost] => (item={'KEY1': 'Val4', 'KEY2': 'Val5', 'KEY3': 'Val6'})

TASK [debug] ********************************************************************************************************
ok: [localhost] => {
    "list0": [
        {
            "key1": "Val1",
            "key2": "Val2",
            "key3": "Val3"
        },
        {
            "key1": "Val4",
            "key2": "Val5",
            "key3": "Val6"
        }
    ]
}
```

## 別解2 jinja2のforを使用

可読性は最も高そう


```yaml

  - loop: "{{ foo }}"
    debug: 
      msg: "{{ bar|from_yaml }}"
    vars:
      bar: >-
        {
          {% for k,v in item.items() %} 
            '{{k|lower}}': '{{v}}', 
          {% endfor %} 
        }
    register: r
  
  - debug:
      var: r.results|map(attribute="msg")|list
```

- task varsにて{% %}によるforループを回して文字列のYAMLを作成、モジュール属性側でfrom_yamlフィルターを適用してYAMLとして認識
    - from_yamlフィルターの為の{{ }}内にforの為の{% %}を含めることができなかった為
- forループは以下でも可
    - bar: "{ {% for i in foo|dict2items %} '{{i.key|lower}}': '{{i.value}}', {% endfor %} }"

```
TASK [debug] ********************************************************************************************************
ok: [localhost] => (item={'KEY1': 'Val1', 'KEY2': 'Val2', 'KEY3': 'Val3'}) => {
    "msg": {
        "key1": "Val1",
        "key2": "Val2",
        "key3": "Val3"
    }
}
ok: [localhost] => (item={'KEY1': 'Val4', 'KEY2': 'Val5', 'KEY3': 'Val6'}) => {
    "msg": {
        "key1": "Val4",
        "key2": "Val5",
        "key3": "Val6"
    }
}

TASK [debug] ********************************************************************************************************
ok: [localhost] => {
    "r.results|map(attribute=\"msg\")|list": [
        {
            "key1": "Val1",
            "key2": "Val2",
            "key3": "Val3"
        },
        {
            "key1": "Val4",
            "key2": "Val5",
            "key3": "Val6"
        }
    ]
}
```