'Loop over list of dictionaries or flow mapping using Ansible?
I have the following data structure. I'm struggling to know precisely how to describe this data structure. My first instinct is to call it a "list of dictionaries" but I've also been told it's called a "flow mapping".
site_subnets:
- control: { network: "100.99.97.0/24", mtu: "1500", dhcp_from_ip: "30", dhcp_to_ip: "50" }
- storage: { network: "100.99.98.0/24", mtu: "9000", dhcp_from_ip: "30", dhcp_to_ip: "50" }
- data: { network: "100.99.99.0/24", mtu: "1500", dhcp_from_ip: "30", dhcp_to_ip: "50" }
- site: { network: "100.99.0.0/16", mtu: "1500", dhcp_from_ip: "30", dhcp_to_ip: "50" }
- local: { network: "100.99.44.1/22", mtu: "1500", dhcp_from_ip: "30", dhcp_to_ip: "50" }
For this particular exercise, I'd like to loop over the data structure and simply print the name of the item.
Here's the playbook I've written:
---
- hosts: localhost
vars:
site_subnets:
- control: { network: "100.99.97.0/24", mtu: "1500", dhcp_from_ip: "30", dhcp_to_ip: "50" }
- storage: { network: "100.99.98.0/24", mtu: "9000", dhcp_from_ip: "30", dhcp_to_ip: "50" }
- data: { network: "100.99.99.0/24", mtu: "1500", dhcp_from_ip: "30", dhcp_to_ip: "50" }
- site: { network: "100.99.0.0/16", mtu: "1500", dhcp_from_ip: "30", dhcp_to_ip: "50" }
- local: { network: "100.99.44.1/22", mtu: "1500", dhcp_from_ip: "30", dhcp_to_ip: "50" }
tasks:
- name: site_subnet network names.
debug:
msg: "subnet name is: {{ item }}"
loop: "{{ site_subnets }}"
What I'm hoping to see is something like the following:
subnet name is: control
subnet name is: storage
subnet name is: data
subnet name is: site
subnet name is: local
Instead I'm seeing the entire dictionary returned upon evaluating each loop.
Solution 1:[1]
Yes, it is most certainly a list of dicts, and the flow mapping is what the yaml spec calls the style of syntax that you used to define the interior { key: value } pairs. If you were to say - { control: { network: "... then all of your dict structures would be in "flow mapping" style
Anyway, since you only care about the keys of those embedded dicts, dict2items will allow you to "pivot" those dicts into {key: control, value: {network: ...}} shape, allowing map(attribute="key") to pull out just the keys. That first in in there because your top-level dict only has one key-value pair
- vars:
site_subnets:
- control: { network: "100.99.97.0/24", mtu: "1500", dhcp_from_ip: "30", dhcp_to_ip: "50" }
- storage: { network: "100.99.98.0/24", mtu: "9000", dhcp_from_ip: "30", dhcp_to_ip: "50" }
- data: { network: "100.99.99.0/24", mtu: "1500", dhcp_from_ip: "30", dhcp_to_ip: "50" }
- site: { network: "100.99.0.0/16", mtu: "1500", dhcp_from_ip: "30", dhcp_to_ip: "50" }
- local: { network: "100.99.44.1/22", mtu: "1500", dhcp_from_ip: "30", dhcp_to_ip: "50" }
debug:
msg: "subnet name is: {{ item }}"
loop: "{{ site_subnets | map('dict2items') | map('first') | map(attribute='key') }}"
yielding
ok: [localhost] => (item=control) => {
"msg": "subnet name is: control"
}
ok: [localhost] => (item=storage) => {
"msg": "subnet name is: storage"
}
...
Solution 2:[2]
There are many options. For example,
- The simplest option is to get the key of a dictionary in a loop
- debug:
msg: "subnet name is: {{ item.keys()|first }}"
loop: "{{ site_subnets }}"
gives (abridged)
msg: 'subnet name is: control'
msg: 'subnet name is: storage'
msg: 'subnet name is: data'
msg: 'subnet name is: site'
msg: 'subnet name is: local'
or, in a Jinja block
- debug:
msg: |-
{% for item in site_subnets %}
subnet name is: {{ item.keys()|first }}
{% endfor %}
gives (abridged) in a single string
msg: |-
subnet name is: control
subnet name is: storage
subnet name is: data
subnet name is: site
subnet name is: local
- The next option is the conversion of the dictionaries. Create attributes key and value
site_subnets_attr: "{{ site_subnets|map('dict2items')|flatten }}"
gives
site_subnets_attr:
- key: control
value:
dhcp_from_ip: '30'
dhcp_to_ip: '50'
mtu: '1500'
network: 100.99.97.0/24
- key: storage
value:
dhcp_from_ip: '30'
dhcp_to_ip: '50'
mtu: '9000'
network: 100.99.98.0/24
- key: data
value:
dhcp_from_ip: '30'
dhcp_to_ip: '50'
mtu: '1500'
network: 100.99.99.0/24
- key: site
value:
dhcp_from_ip: '30'
dhcp_to_ip: '50'
mtu: '1500'
network: 100.99.0.0/16
- key: local
value:
dhcp_from_ip: '30'
dhcp_to_ip: '50'
mtu: '1500'
network: 100.99.44.1/22
Then, the iteration is trivial
- debug:
msg: "subnet name is: {{ item.key }}"
loop: "{{ site_subnets_attr }}"
gives (abridged) the same result
msg: 'subnet name is: control'
msg: 'subnet name is: storage'
msg: 'subnet name is: data'
msg: 'subnet name is: site'
msg: 'subnet name is: local'
- The next option is the conversion of the list to a dictionary
site_subnets_dict: "{{ site_subnets|combine }}"
gives
site_subnets_dict:
control:
dhcp_from_ip: '30'
dhcp_to_ip: '50'
mtu: '1500'
network: 100.99.97.0/24
data:
dhcp_from_ip: '30'
dhcp_to_ip: '50'
mtu: '1500'
network: 100.99.99.0/24
local:
dhcp_from_ip: '30'
dhcp_to_ip: '50'
mtu: '1500'
network: 100.99.44.1/22
site:
dhcp_from_ip: '30'
dhcp_to_ip: '50'
mtu: '1500'
network: 100.99.0.0/16
storage:
dhcp_from_ip: '30'
dhcp_to_ip: '50'
mtu: '9000'
network: 100.99.98.0/24
Then, iterate the list created by dict2items
- debug:
msg: "subnet name is: {{ item.key }}"
loop: "{{ site_subnets_dict|dict2items }}"
or, simply use filter list that extracts the dictionaries' keys
- debug:
msg: "subnet name is: {{ item }}"
loop: "{{ site_subnets_dict|list }}"
Both options give (abridged) the same result
msg: 'subnet name is: control'
msg: 'subnet name is: storage'
msg: 'subnet name is: data'
msg: 'subnet name is: site'
msg: 'subnet name is: local'
This procedure will work if the keys of the dictionaries are unique, of course. To be sure, test it
- assert:
that: site_subnets|length == site_subnets_dict|length
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | mdaniel |
| Solution 2 |
