I have a list of dicts and I want to loop through the list of dicts, and then have an inner loop that splits a string from each dict and loops through that split string. Is there a way to do this?
I've tried a few things:
- debug:
msg: '{{ "Group: " + item.Group + ", AddMembers: " + item.1 }}'
with_nested:
- '{{ domainGroups.list | selectattr("AddMembers", "ne", "") | list }}'
- '{{ AddMembers.split("|") }}'
- debug:
msg: '{{ "Group: " + item.Group + ", AddMembers: " + item.1 }}'
loop: '{{ domainGroups.list | selectattr("AddMembers", "ne", "" ) | list | subelements(AddMembers.split("|")) }}'
The list of dicts:
{
"AddMembers": "",
"Group": "Group1",
"Delete": "1",
},
{
"AddMembers": "members1|members2",
"Group": "Group2",
"Delete": "",
},
{
"AddMembers": "members1|members2|members3",
"Group": "Group3",
"Delete": "",
},
I hoped that one of the things I've tried would work but usually I get some variation of "AddMembers is not defined" or "unicode thing has no attribute "AddMembers""
Update
Declare the variables
my_groups: "{{ domainGroups|map(attribute='Group')|list }}"
my_member: "{{ domainGroups|map(attribute='AddMembers')|
map('split', '|')|
map('select')|list }}"
groups_members: "{{ dict(my_groups|zip(my_member)) }}"
gives
groups_members:
Group1: []
Group2:
- members1
- members2
Group3:
- members1
- members2
- members3
Iterate with subelements
- debug:
msg: "Group: {{ item.0.key }} AddMember: {{ item.1 }}"
with_subelements:
- "{{ groups_members|dict2items }}"
- value
gives (abridged)
msg: 'Group: Group2 AddMember: members1'
msg: 'Group: Group2 AddMember: members2'
msg: 'Group: Group3 AddMember: members1'
msg: 'Group: Group3 AddMember: members2'
msg: 'Group: Group3 AddMember: members3'
Example of a complete playbook for testing
- hosts: localhost
vars:
domainGroups:
- AddMembers: ''
Delete: '1'
Group: Group1
- AddMembers: members1|members2
Delete: ''
Group: Group2
- AddMembers: members1|members2|members3
Delete: ''
Group: Group3
my_groups: "{{ domainGroups|map(attribute='Group')|list }}"
my_member: "{{ domainGroups|map(attribute='AddMembers')|
map('split', '|')|
map('select')|list }}"
groups_members: "{{ dict(my_groups|zip(my_member)) }}"
tasks:
- debug:
var: groups_members
- debug:
msg: "Group: {{ item.0.key }} AddMember: {{ item.1 }}"
with_subelements:
- "{{ groups_members|dict2items }}"
- value
Deprecated
Let's simplify the dictionaries in the first step. The tasks below
- set_fact:
domainGroups_selected: "{{ domainGroups_selected|default([]) +
[ {'Group': item.Group,
'AddMembers': item.AddMembers.split('|')}] }}"
loop: "{{ domainGroups }}"
when: item.AddMembers|length > 0
- debug:
var: item
loop: "{{ domainGroups_selected }}"
give
"item": {
"AddMembers": [
"members1",
"members2"
],
"Group": "Group2"
}
...
"item": {
"AddMembers": [
"members1",
"members2",
"members3"
],
"Group": "Group3"
}
Then loop the list with subelements. The task below
- debug:
msg: "Group: {{ item.0.Group }} AddMember: {{ item.1 }}"
loop: "{{ domainGroups_selected|subelements('AddMembers')|list }}"
gives
"msg": "Group: Group2 AddMember: members1"
"msg": "Group: Group2 AddMember: members2"
"msg": "Group: Group3 AddMember: members1"
"msg": "Group: Group3 AddMember: members2"
"msg": "Group: Group3 AddMember: members3"
Related
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?
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
I'm using the Cisco ACI_REST module. I'm building a playbook to shutdown all of my ACI EPGs. The ACI_EPG module is used to create the dict.
- name: Get List of EPGs
cisco.aci.aci_epg:
host: "{{ inventory_hostname }}"
username: "{{ ansible_user }}"
password: "{{ ansible_password }}"
validate_certs: no
state: query
delegate_to: localhost
register: epg_list
It creates a dictionary that looks like this:
"item": {
"key": "current",
"value": [
{
"fvAEPg": {
"attributes": {
"dn": "uni/tn-PC/ap-PROD_AP/epg-VLAN80_EPG",
"shutdown": "no",
},
"children": [
{
"fvRsBd": {
"attributes": {
"annotation": "",
"tDn": "uni/tn-PC/BD-VLAN80_BD",
"tRn": "BD-VLAN80_BD",
"tType": "name",
"tnFvBDName": "VLAN80_BD",
"uid": "0"
},
"children": [
{
"fvSubnetBDDefCont": {
"attributes": {
"name": "",
"nameAlias": "",
"status": ""
}
}
}
]
}
}
]
}
}
I need to loop through the DN values.
"dn": "uni/tn-PC/ap-PROD_AP/epg-VLAN80_EPG"
I'm playing with dict2items but am stuck. How to I reference the nested items in my dictionary? Here is some test code that I'm working with.
- name: Display EPG List
ansible.builtin.debug:
msg: "{{ item.key }} - {{ item.value }}"
loop: "{{ epg_list | dict2items }}"
Thanks,
Q: "Loop through the DN values."
A: Try json_query, e.g.
- debug:
msg: "{{ item }}"
loop: "{{ epg_list.item.value|
json_query('[].*.attributes.dn')|
flatten }}"
gives
msg: uni/tn-PC/ap-PROD_AP/epg-VLAN80_EPG
Given the data
epg_list:
item:
key: current
value:
- fvAEPg:
attributes:
dn: uni/tn-PC/ap-PROD_AP/epg-VLAN80_EPG
shutdown: 'no'
children:
- fvRsBd:
attributes:
annotation: ''
tDn: uni/tn-PC/BD-VLAN80_BD
tRn: BD-VLAN80_BD
tType: name
tnFvBDName: VLAN80_BD
uid: '0'
children:
- fvSubnetBDDefCont:
attributes:
name: ''
nameAlias: ''
status: ''
If the data is
epg_list:
current:
- fvAEPg:
attributes:
dn: uni/tn-PC/ap-PROD_AP/epg-VLAN80_EPG
shutdown: 'no'
children:
- fvRsBd:
attributes:
annotation: ''
tDn: uni/tn-PC/BD-VLAN80_BD
tRn: BD-VLAN80_BD
tType: name
tnFvBDName: VLAN80_BD
uid: '0'
children:
- fvSubnetBDDefCont:
attributes:
name: ''
nameAlias: ''
status: ''
the task below gives the same result
- debug:
msg: "{{ item }}"
loop: "{{ epg_list.current|
json_query('[].*.attributes.dn')|
flatten }}"
I have a list of AD users and trying to create a distinguishedName-list
vars:
admin_users: "user1;user2"
tasks:
- set_fact:
admin: "{{ admin_users.split(';') }}"
- name: Search account
community.general.ldap_search:
<...>
loop: "{{ admin }}"
register: ldap_result
- name: Build DN-array
set_fact:
group: "{{ group | default([]) }} + {{ item.value | json_query(query) }}"
with_dict: "{{ ldap_result.results }}"
when: item.key == 'results'
vars:
query: "[*].distinguishedName"
- debug:
var: "{{ group }}"
And get
"<class 'list'>": "VARIABLE IS NOT DEFINED!"
Also tried
group: "{{ group | default([]) }} + [ {{ item.value | json_query(query) }} ]"
group: "{{ group | default([]) }} + {{ [ item.value | json_query(query) ] }}"
get same message
"<class 'list'>": "VARIABLE IS NOT DEFINED!"
and
group: "{{ group | default([]) }} + [ '{{ item.value | json_query(query) }}' ]"
Then I get the error
FAILED! => {"msg": "template error while templating string: expected
token ',', got 'CN'. String: {{[] + [ '['CN=***']' ] + [ '['CN=***']' ]}}"}
Any tips?
Ok, that's right, I just needed to use a construction like this
<...>
group: "{{ group | default([]) }} + [ {{ item.value | json_query(query) }} ]"
<...>
- debug: msg="{{ group }}"
not this
- debug:
var: "{{ group_list_two }}"
correct output
ok: [localhost] => {
"msg": [
[
"CN=***"
],
[
"CN=***"
]
]
}
I am creating a simple dictionary in ansible which is this one:
"types_dict": {
"name1": "Ethernet",
"name2": "Ethernet",
"name3": "Software-Pseudo",
"name4": "Ethernet",
"name5": "Software-Pseudo",
"name6": "Ethernet" }
My goal is to loop the dictionary and replace some specific values, the ones "Software-Pseudo" with "Virtual". I have tried the following:
- set_fact:
types_dict: "{{ types_dict | combine(new_item, recursive=true) }}"
vars:
new_item: "{ '{{ item.key }}': { 'type': 'Virtual' } }"
with_dict: "{{ types_dict }}"
but the problem here is that this one updates all the values in my dictionary, which is something I do not want at all. I tried also the following by adding the "when" statement, but also it is not working:
- set_fact:
types_dict: "{{ types_dict | combine(new_item, recursive=true) }}"
vars:
new_item: "{ '{{ item.key }}': { 'type': 'Virtual' } }"
when: "{{ item.value }} == Software-Pseudo"
with_dict: "{{ types_dict }}"
I also tried when: "{{ item.value }} == 'Software-Pseudo'" and many other things like this.
Any ideas on how to fix this ?
The task does the job. Items can be added to the list types_new if needed
- set_fact:
types_dict: "{{ types_dict|combine({item.0.key: item.1.replace}) }}"
with_nested:
- "{{ types_dict|dict2items }}"
- "{{ types_new }}"
when: item.0.value is search(item.1.regex)
vars:
types_new:
- {regex: 'Software-Pseudo', replace: 'Virtual'}
- debug:
var: types_dict
gives
types_dict:
name1: Ethernet
name2: Ethernet
name3: Virtual
name4: Ethernet
name5: Virtual
name6: Ethernet
Q: "I have null values like "name2": null in my dictionary, could I handle this in any way so as to replace it with sth else (another value)."
A: Add a line to the types_new. For example
vars:
types_new:
- {regex: 'Software-Pseudo', replace: 'Virtual'}
- {regex: 'None', replace: 'another_value'}
See the task below how null, None, and 'None' are treated by the search test
- debug:
msg: "{{ item.key }} {{ item.value is search('None') }}"
loop: "{{ my_vars|dict2items }}"
vars:
my_vars:
var1: abc
var2:
var3: None
var4: 'None'
gives
msg: var1 False
msg: var2 True
msg: var3 True
msg: var4 True
I am struggling to loop over registered results.containers for all the hosts in the dynamic inventory.
Here is the code.
$cat collect.yaml
---
- hosts: "{{ env }}"
become: True
tasks:
- name: Get dockerinfo
docker_host_info:
containers: yes
register: result
- name: Debug dockerInfo
debug:
var: result.containers
- name: dynamic grouping
add_host:
name: "{{ item[0] }}"
groups: "{{ item[1].Image | regex_replace('.*?/(.*?):.*', '\\1') }}"
container_name: '{{ item[1].Names[0] | regex_replace("^/", "") }}'
with_nested:
- "{{ ansible_play_batch }}"
- "{{ result.containers }}"
Here is result.containers output.
TASK [Debug dockerInfo]
ok: [vm1.nodekite.com] => {
"result.containers": [
{
"Image": "ca.docker/webproxy:1.0.0",
"Names": [
"/customer1"
],
},
{
"Image": "docker.local/egacustomer:1.0.1",
"Names": [
"/webproxy"
],
},
]}
ok: [vm2.nodekite.com ] => {
"result.containers": [
{
"Image": "ca.docker/webproxy:1.0.0",
"Names": [
"/webproxyui"
],
},
{
"Image": "cna-docker-local/lega-customer:1.0.1",
"Names": [
"/webproxy"
],
},
]}
ok: [vm3.nodekite.com ] => {
"result.containers": [
{
"Image": "ca.docker/webproxy:1.0.0",
"Names": [
"/webproxy"
],
},
{
"Image": "local.docker/saga-customer:1.0.1",
"Names": [
"/customerr
],
},
]}
Right now item[1].Image and item[1].Names[0] is only taken from first host's(vm1.nodekite.com) results.containers output. I would like to loop over for every hosts. So that, I could create dynamic group for all the hosts with their respective containers. With my code, hosts vm1,vm2,vm3 all are referring to vm1.nodekite.com's result.containers but i want the hosts to refer to their respective containers. Any help would be greatly appreciated.
I have update dynamic grouping task ouput for clarification.
changed: [vm1.nodekite.com] => {
"add_host": {
"groups": [
"webproxy"
],
"host_name": "vm1.nodekite.com",
"host_vars": {
"container_name": "customer1" }
},
changed: [vm1.nodekite.com] => {
"add_host": {
"groups": [
"egacustomer"
],
"host_name": "vm1.nodekite.com",
"host_vars": {
"container_name": "webproxy" }
},
changed: [vm2.nodekite.com] => {
"add_host": {
"groups": [
"webproxy" >> this should be webproxy
],
"host_name": "vm2.nodekite.com",
"host_vars": {
"container_name": "customer1" } >> this should be webproxyui
},
changed: [vm2.nodekite.com] => {
"add_host": {
"groups": [
"egacustomer" >> this should be lega-customer
],
"host_name": "vm2.nodekite.com",
"host_vars": {
"container_name": "webproxy" } >> this should be webproxy
},
if you see vm2 is still referring to vm1's result.containers output.
when i try this...i get item not defined error.
- name: adding it to groups using images
add_host:
name: "{{ item[0] }}"
groups: "{{ item[1].Image | regex_replace('.*?/(.*?):.*', '\\1') }}"
container_name: '{{ item[1].Names[0] | regex_replace("^/", "") }}'
loop:
- "{{ ansible_play_batch }}"
- "{{ myresult.containers }}"
vars:
myresult: "{{ hostvars[item].result }}"
run_once: true
Q: "Hosts shall refer to their respective containers."
A: Use hostvars. For example
- name: dynamic grouping
debug:
msg:
- "name: {{ item }}"
- "groups: {{ my_result.containers|
map(attribute='Image')|
map('regex_replace', '.*?/(.*?):.*', '\\1')|
list }}"
- "container_names: {{ my_result.containers|
map(attribute='Names')|
map('regex_replace', '\/', '')|
list }}"
loop: "{{ ansible_play_batch }}"
vars:
my_result: "{{ hostvars[item].result }}"
run_once: true
gives
ok: [vm1.nodekite.com] => (item=vm1.nodekite.com) =>
msg:
- 'name: vm1.nodekite.com'
- 'groups: [''webproxy'', ''egacustomer'']'
- 'container_names: ["[''customer1'']", "[''webproxy'']"]'
ok: [vm1.nodekite.com] => (item=vm2.nodekite.com) =>
msg:
- 'name: vm2.nodekite.com'
- 'groups: [''webproxy'', ''lega-customer'']'
- 'container_names: ["[''webproxyui'']", "[''webproxy'']"]'
ok: [vm1.nodekite.com] => (item=vm3.nodekite.com) =>
msg:
- 'name: vm3.nodekite.com'
- 'groups: [''webproxy'', ''saga-customer'']'
- 'container_names: ["[''webproxy'']", "[''customer'']"]'
(Feel free to fit the code to your needs.)
I was having an issue of getting the item passed into the name below to be a plain string of: item='nginx' and not item='[u'/nginx]'
To get around this, I did the following:
- name: Get docker containers
become: docker
community.docker.docker_container
containers: yes
register: docker_info
- name: Stop running containers
become: docker
community.docker.docker_container
name: "{{ item }}"
state: stopped
loop: "{{ docker_info.containers | sum(attribute='Names', start=[]) | map('regex_replace','\\/','') | list }}"
when: item in apps.split(,)
In this case the apps is a comma deliminated string variable I passed into the ansible playbook to limit which apps to stop.
The sum piece, flattens the Names of all the apps running into a single list.
The regex piece removes the / in the Names parameter