Reputation: 13578
I am using the following data structure in Ansible:
datacenters:
- name: Datacenter1
clusters:
- name: ClusterA
hosts:
- 192.168.0.1
- 192.168.0.2
- name: ClusterB
hosts:
- 192.168.1.1
- 192.168.1.2
- name: Datacenter2
clusters:
- name: ClusterC
hosts:
- 192.168.2.1
- 192.168.2.2
In a task, I want to iterate over each host while having access to the data of all the parent layers. If there is only one nesting level, it can easily be done with the subelements
filter:
loop: '{{ datacenters | subelements(''clusters'') }}'
This will give me access to the data like this:
'Datacenter: {{ item.0.name }}, Cluster: {{ item.1.name }}'
I was hoping to be able to extend this concept like this:
loop: '{{ datacenters | subelements(''clusters'') | subelements(''hosts'') }}'
And being able to access the data like this:
'Datacenter: {{ item.0.name }}, Cluster: {{ item.1.name }}, Host: {{ item.2 }}'
But that does not work and I get the following error message instead:
Unexpected templating type error occurred on ({{ datacenters | subelements('clusters') | subelements('hosts') }}): the key hosts should point to a dictionary, got ...(the result of the first layer)
I found this question, which solves a similar problem, but relies on having distinct dict keys on all nesting levels, which I don't, because datacenters and clusters have the same name
key.
So, how can I iterate over subelements of subelements in Ansible?
Upvotes: 2
Views: 3054
Reputation: 13578
While Zeitounator answers the question already, I still want to present a different approach to the looping problem. It still can become difficult to loop over multiple nesting layers if there are more than two or three of them.
Another way to solve this problem is to use ansible.builtin.include_tasks and distinct task files for each layer to loop over the whole data structure. Example for the data structure from the question:
configure-environment.yaml
:
# Configure datacenters
- name: Configure datacenters
ansible.builtin.include_tasks: configure-datacenter.yaml
loop: '{{ datacenters }}'
loop_control:
loop_var: datacenter
label: '{{ datacenter.name }}'
configure-datacenter.yaml
:
# Configure datacenter
- name: Configure datacenter
ansible.builtin.debug:
msg: Configure datacenter {{ datacenter.name }}.
# Configure clusters
- name: Configure clusters
ansible.builtin.include_tasks: configure-cluster.yaml
loop: '{{ datacenter.clusters }}'
loop_control:
loop_var: cluster
label: '{{ cluster.name }}'
configure-cluster.yaml
:
# Configure cluster
- name: Configure cluster
ansible.builtin.debug:
msg: Configure cluster {{ cluster.name }} of datacenter {{ datacenter.name }}.
# Configure hosts
- name: Configure hosts
ansible.builtin.debug:
msg: Configure host {{ my_host }} of cluster {{ cluster.name }} of datacenter {{ datacenter.name }}.
loop: '{{ cluster.hosts }}'
loop_control:
loop_var: my_host
Upvotes: 0
Reputation: 44779
A bit far-fetched but the following playbook will achieve your goal:
---
- hosts: localhost
gather_facts: false
vars:
datacenters:
- name: Datacenter1
clusters:
- name: ClusterA
hosts:
- 192.168.0.1
- 192.168.0.2
- name: ClusterB
hosts:
- 192.168.1.1
- 192.168.1.2
- name: Datacenter2
clusters:
- name: ClusterC
hosts:
- 192.168.2.1
- 192.168.2.2
# Get the list of datacenters
_dcs: "{{ datacenters | map(attribute='name') }}"
# Get the corresponding list of clusters with subelements on hosts
_clusters: "{{ datacenters | map(attribute='clusters') | map('subelements', 'hosts') }}"
# Recreate a list with the sublisted hosts per clusters and create subelements on that final result
_overall: "{{ dict(_dcs | zip(_clusters)) | dict2items(key_name='datacenter', value_name='clusters') | subelements('clusters') }}"
tasks:
- name: Loop on the result
debug:
msg:
- "DC: {{ item.0.datacenter }}"
- "Cluster: {{ item.1.0.name }}"
- "Host: {{ item.1.1 }}"
loop: "{{ _overall }}"
loop_control:
label: "{{ item.0.datacenter }} - {{ item.1.0.name }}"
This gives:
PLAY [localhost] **************************************************************************************************************************************************
TASK [Loop on the result] *****************************************************************************************************************************************
ok: [localhost] => (item=Datacenter1 - ClusterA) => {
"msg": [
"DC: Datacenter1",
"Cluster: ClusterA",
"Host: 192.168.0.1"
]
}
ok: [localhost] => (item=Datacenter1 - ClusterA) => {
"msg": [
"DC: Datacenter1",
"Cluster: ClusterA",
"Host: 192.168.0.2"
]
}
ok: [localhost] => (item=Datacenter1 - ClusterB) => {
"msg": [
"DC: Datacenter1",
"Cluster: ClusterB",
"Host: 192.168.1.1"
]
}
ok: [localhost] => (item=Datacenter1 - ClusterB) => {
"msg": [
"DC: Datacenter1",
"Cluster: ClusterB",
"Host: 192.168.1.2"
]
}
ok: [localhost] => (item=Datacenter2 - ClusterC) => {
"msg": [
"DC: Datacenter2",
"Cluster: ClusterC",
"Host: 192.168.2.1"
]
}
ok: [localhost] => (item=Datacenter2 - ClusterC) => {
"msg": [
"DC: Datacenter2",
"Cluster: ClusterC",
"Host: 192.168.2.2"
]
}
PLAY RECAP ********************************************************************************************************************************************************
localhost : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Upvotes: 3