2021/12: 1階層の変換をloopを使用しないロジックに置き換え
dictonaryのlistを、dictonary型に変換したり、戻したりする例
それぞれのデータ型はメリット、デメリットが存在する。
参考
- Ansible Tips: dictionary や list_of_dictionaries 構造に対する値の参照とループ操作
例えばdictonary型ではdeep mergeが使えるのでデータの統合が容易、など
ここでは、それらを相互に変換するロジックを考える。
1階層のデータ
データ
以下の様なdictonaryのlistを
- name: foo1
attr: bar1
- name: foo2
attr: bar2
以下の様なdictonaryと相互に変換することを考える
foo1:
name: foo1
attr: bar1
foo2:
name: foo2
attr: bar2
- ただしdictionaryでは順序の保証がないので、listに戻した際にも順序は保証されない。
playbook例
site.yml
---
- hosts: all
vars:
hoge:
- name: foo1
attr: bar1
- name: foo2
attr: bar2
ansible_python_interpreter: '/usr/bin/python3'
tasks:
- name: 元データ
debug:
var: hoge
- name: list of dict型をdict型に変換
set_fact:
hoge_dict: '{{ dict( hoge|map(attribute="name")|zip(hoge) ) }}'
- name: dict型に変換後
debug:
var: hoge_dict
- name: dict型からlist of dict型に戻す
set_fact:
hoge_dict2list: '{{ hoge_dict.values() }}'
- name: list of dict型に変換後
debug:
var: hoge_dict2list
$ ansible-playbook -i localhost, -c local site.yml
PLAY [all] ***************************************************************************************************************************************************
TASK [Gathering Facts] ***************************************************************************************************************************************
ok: [localhost]
TASK [元データ] **********************************************************************************************************************************************
ok: [localhost] => {
"hoge": [
{
"attr": "bar1",
"name": "foo1"
},
{
"attr": "bar2",
"name": "foo2"
}
]
}
TASK [list of dict型をdict型に変換] **************************************************************************************************************************
ok: [localhost]
TASK [dict型に変換後] ****************************************************************************************************************************************
ok: [localhost] => {
"hoge_dict": {
"foo1": {
"attr": "bar1",
"name": "foo1"
},
"foo2": {
"attr": "bar2",
"name": "foo2"
}
}
}
TASK [dict型からlist of dict型に戻す] ************************************************************************************************************************
ok: [localhost]
TASK [list of dict型に変換後] ********************************************************************************************************************************
ok: [localhost] => {
"hoge_dict2list": [
{
"attr": "bar1",
"name": "foo1"
},
{
"attr": "bar2",
"name": "foo2"
}
]
}
PLAY RECAP ***************************************************************************************************************************************************
localhost : ok=6 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
2階層のデータ
with_subelements が使える2階層の場合、list of dict -> dictはinclude_tasksなどを使わなくとも変換可能
tasks例: 2階層list of dict -> dict
site.yml
---
- hosts: all
gather_facts: false
vars:
hoge2:
- name: foo1
attr: foo1_attr
member:
- name: bar11
attr: baz11
- name: bar12
attr: baz12
- name: foo2
attr: foo2_attr
member:
- name: bar21
attr: baz21
- name: bar22
attr: baz22
tasks:
- set_fact: { fuga2: {} } # loopで出力を追加していく初期値
- vars: # task変数定義
index1: member # 2階層目を示すkey
key0: name # 1階層目の要素を識別するキー。dictのキーとして使用
key1: name # 2階層目の要素を識別するキー。dictのキーとして使用
with_subelements: # 2階層ループ
- "{{ hoge2 }}" # 対象のlist of dict
- "{{ index1 }}" # 2階層目を示すkey。task変数で指定されたもの
set_fact:
fuga2: "{{
fuga2
| combine(
{ (item[0][key0]): item[0] }
| combine(
{ (item[0][key0]):
{ index1:
{ (item[1][key1]): item[1] }
}
}
, recursive=true)
, recursive=true)
}}"
- debug:
var: fuga2
- loopで、空dictのfuga2にdictを追加していく
- |combine()にてdict型をマージ
実行例
TASK [set_fact] *****************************************************************************************************
ok: [localhost] => (item=[{'name': 'foo1', 'attr': 'foo1_attr'}, {'name': 'bar11', 'attr': 'baz11'}])
ok: [localhost] => (item=[{'name': 'foo1', 'attr': 'foo1_attr'}, {'name': 'bar12', 'attr': 'baz12'}])
ok: [localhost] => (item=[{'name': 'foo2', 'attr': 'foo2_attr'}, {'name': 'bar21', 'attr': 'baz21'}])
ok: [localhost] => (item=[{'name': 'foo2', 'attr': 'foo2_attr'}, {'name': 'bar22', 'attr': 'baz22'}])
TASK [debug] ********************************************************************************************************
ok: [localhost] => {
"fuga2": {
"foo1": {
"attr": "foo1_attr",
"member": {
"bar11": {
"attr": "baz11",
"name": "bar11"
},
"bar12": {
"attr": "baz12",
"name": "bar12"
}
},
"name": "foo1"
},
"foo2": {
"attr": "foo2_attr",
"member": {
"bar21": {
"attr": "baz21",
"name": "bar21"
},
"bar22": {
"attr": "baz22",
"name": "bar22"
}
},
"name": "foo2"
}
}
}
tasks例: 2階層 dict -> list of dict
2階層 dictからlist of dictへの戻し
playbook続き
- set_fact: { out_list: [] }
- vars:
in_dict: "{{ fuga2 }}"
index1: member
loop: "{{ in_dict|dict2items|map(attribute='value')|list }}"
set_fact:
out_list: "{{ out_list + [
item
|combine(
{ index1: item[index1]|dict2items|map(attribute='value')|list }
, recursive=true)
]
}}"
- debug:
var: out_list
- loopで、空listのout_listに要素を追加していく
- + [xxxx] にてlist型に要素を追加
出力続き
TASK [set_fact] *****************************************************************************************************
ok: [localhost] => (item={'name': 'foo1', 'attr': 'foo1_attr', 'member': {'bar11': {'name': 'bar11', 'attr': 'baz11'}, 'bar12': {'name': 'bar12', 'attr': 'baz12'}}})
ok: [localhost] => (item={'name': 'foo2', 'attr': 'foo2_attr', 'member': {'bar21': {'name': 'bar21', 'attr': 'baz21'}, 'bar22': {'name': 'bar22', 'attr': 'baz22'}}})
TASK [debug] ********************************************************************************************************
ok: [localhost] => {
"out_list": [
{
"attr": "foo1_attr",
"member": [
{
"attr": "baz11",
"name": "bar11"
},
{
"attr": "baz12",
"name": "bar12"
}
],
"name": "foo1"
},
{
"attr": "foo2_attr",
"member": [
{
"attr": "baz21",
"name": "bar21"
},
{
"attr": "baz22",
"name": "bar22"
}
],
"name": "foo2"
}
]
}
out_listをYAMLで書くと
---
- attr: foo1_attr
member:
- attr: baz11
name: bar11
- attr: baz12
name: bar12
name: foo1
- attr: foo2_attr
member:
- attr: baz21
name: bar21
- attr: baz22
name: bar22
name: foo2