Inner Loop in Ansible - loops

I have read the docs on with_nested and loops etc but not sure that they apply to my scenario.
My structure is:
org
-- org has networks
---- networks have devices
------ devices have licenses
The goal is to delete my orgs. This can only occur when all the devices have been unlicensed, deleted, and networks delete.
Therefore what I need to do is grab a list of orgs.
-- Then loop thru that list and using the org_id get all the networks
---- For each network I then get the list of devices.
------ Then loop thru the list of devices and delete license keys
------ Delete device.
---- Delete network
-- Delete org
loop
With there only being the one item.x then not sure how to get the item of the inner loops.
Could add code but would look like crap as I simply can't visualize this in my head.

There are more options. For example, given the data
orgs:
o1:
n1:
d1:
license: k1
d2:
license: k2
n2:
d3:
license: k3
o2:
n3:
d4:
license: k4
d5:
license: k5
Use to_paths lookup
orgs_paths: "{{ lookup('ansible.utils.to_paths', orgs) }}"
gives
orgs_paths:
o1.n1.d1.license: k1
o1.n1.d2.license: k2
o1.n2.d3.license: k3
o2.n3.d4.license: k4
o2.n3.d5.license: k5
Then, declare the lists
_key: "{{ orgs_paths.keys()|list }}"
_dev: "{{ _key|map('splitext')|map('first')|unique }}"
_net: "{{ _dev|map('splitext')|map('first')|unique }}"
_org: "{{ _net|map('splitext')|map('first')|unique }}"
_arr: "{{ item.split('.') }}"
and delete the tree
- debug:
msg: "Delete license: {{ orgs_paths[item] }} for {{ _arr[0:3] }}"
loop: "{{ _key }}"
- debug:
msg: "Delete device: {{ _arr.2 }} for {{ _arr[0:2] }}"
loop: "{{ _dev }}"
- debug:
msg: "Delete network: {{ _arr.1 }} for {{ _arr[0:1] }}"
loop: "{{ _net }}"
- debug:
msg: "Delete org: {{ item }}"
loop: "{{ _org }}"
- debug:
msg: "Delete orgs"
gives (abridged)
msg: 'Delete license: k1 for [''o1'', ''n1'', ''d1'']'
msg: 'Delete license: k2 for [''o1'', ''n1'', ''d2'']'
msg: 'Delete license: k3 for [''o1'', ''n2'', ''d3'']'
msg: 'Delete license: k4 for [''o2'', ''n3'', ''d4'']'
msg: 'Delete license: k5 for [''o2'', ''n3'', ''d5'']'
msg: 'Delete device: d1 for [''o1'', ''n1'']'
msg: 'Delete device: d2 for [''o1'', ''n1'']'
msg: 'Delete device: d3 for [''o1'', ''n2'']'
msg: 'Delete device: d4 for [''o2'', ''n3'']'
msg: 'Delete device: d5 for [''o2'', ''n3'']'
msg: 'Delete network: n1 for [''o1'']'
msg: 'Delete network: n2 for [''o1'']'
msg: 'Delete network: n3 for [''o2'']'
msg: 'Delete org: o1'
msg: 'Delete org: o2'
msg: Delete orgs
Example of a complete playbook
- hosts: localhost
vars:
orgs:
o1:
n1:
d1: {license: k1}
d2: {license: k2}
n2:
d3: {license: k3}
o2:
n3:
d4: {license: k4}
d5: {license: k5}
orgs_paths: "{{ lookup('ansible.utils.to_paths', orgs) }}"
_key: "{{ orgs_paths.keys()|list }}"
_dev: "{{ _key|map('splitext')|map('first')|unique }}"
_net: "{{ _dev|map('splitext')|map('first')|unique }}"
_org: "{{ _net|map('splitext')|map('first')|unique }}"
_arr: "{{ item.split('.') }}"
tasks:
- debug:
msg: "Delete license: {{ orgs_paths[item] }} for {{ _arr[0:3] }}"
loop: "{{ _key }}"
- debug:
msg: "Delete device: {{ _arr.2 }} for {{ _arr[0:2] }}"
loop: "{{ _dev }}"
- debug:
msg: "Delete network: {{ _arr.1 }} for {{ _arr[0:1] }}"
loop: "{{ _net }}"
- debug:
msg: "Delete org: {{ item }}"
loop: "{{ _org }}"
- debug:
msg: "Delete orgs"
The next option is the inclusion of tasks in loops. For example, create files
shell> cat del_dev.yml
- block:
- debug:
msg: >-
Delete license {{ item.value }} for
[{{ item_org.key }}, {{ item_net.key }}, {{ item_dev.key }}]
loop: "{{ item_dev.value|dict2items }}"
- debug:
msg: >-
Delete device {{ item_dev.key }} for
[{{ item_org.key }}, {{ item_net.key }}]
shell> cat del_net.yml
- block:
- include_tasks: del_dev.yml
loop: "{{ item_net.value|dict2items }}"
loop_control:
loop_var: item_dev
- debug:
msg: >-
Delete network {{ item_net.key }} for
[{{ item_org.key }}]
shell> cat del_org.yml
- block:
- include_tasks: del_net.yml
loop: "{{ item_org.value|dict2items }}"
loop_control:
loop_var: item_net
- debug:
msg: >-
Delete org {{ item_org.key }}
Recursively include the tasks in a playbook
- hosts: localhost
vars:
orgs:
o1:
n1:
d1: {license: k1}
d2: {license: k2}
n2:
d3: {license: k3}
o2:
n3:
d4: {license: k4}
d5: {license: k5}
tasks:
- block:
- include_tasks: del_org.yml
loop: "{{ orgs|dict2items }}"
loop_control:
loop_var: item_org
- debug:
msg: Delete orgs
gives (abridged)
msg: Delete license k1 for [o1, n1, d1]
msg: Delete device d1 for [o1, n1]
msg: Delete license k2 for [o1, n1, d2]
msg: Delete device d2 for [o1, n1]
msg: Delete network n1 for [o1]
msg: Delete license k3 for [o1, n2, d3]
msg: Delete device d3 for [o1, n2]
msg: Delete network n2 for [o1]
msg: Delete org o1
msg: Delete license k4 for [o2, n3, d4]
msg: Delete device d4 for [o2, n3]
msg: Delete license k5 for [o2, n3, d5]
msg: Delete device d5 for [o2, n3]
msg: Delete network n3 for [o2]
msg: Delete org o2
msg: Delete orgs
Getting the structure on the fly makes the code simpler. For example, let's imagine the structure is the same as before. As the first step in each loop, we find the list of org, their networks, and devices. This is the meaning of the argument in the loop statement. In real life, you would probably get the lists in a separate task before the loop. Create files
shell> cat del_dev.yml
- block:
- debug:
msg: >-
Delete license {{ _item }} for
[{{ item_org }}, {{ item_net }}, {{ item_dev }}]
vars:
_item: "{{ orgs[item_org][item_net][item_dev]['license'] }}"
- debug:
msg: >-
Delete device {{ item_dev }} for
[{{ item_org }}, {{ item_net }}]
shell> cat del_net.yml
- block:
- include_tasks: del_dev.yml
loop: "{{ orgs[item_org][item_net].keys()|list }}"
loop_control:
loop_var: item_dev
- debug:
msg: >-
Delete network {{ item_net }} for
[{{ item_org }}]
shell> cat del_org.yml
- block:
- include_tasks: del_net.yml
loop: "{{ orgs[item_org].keys()|list }}"
loop_control:
loop_var: item_net
- debug:
msg: >-
Delete org {{ item_org }}
The playbook below gives the same results
- hosts: localhost
vars:
orgs:
o1:
n1:
d1: {license: k1}
d2: {license: k2}
n2:
d3: {license: k3}
o2:
n3:
d4: {license: k4}
d5: {license: k5}
tasks:
- block:
- include_tasks: del_org.yml
loop: "{{ orgs.keys()|list }}"
loop_control:
loop_var: item_org
- debug:
msg: Delete orgs
Q: "How to propagate the org_id due to only a single item?"
A: Simply put this single item into the list. For example
- hosts: localhost
vars:
orgs:
o1:
n1:
d1: {license: k1}
d2: {license: k2}
n2:
d3: {license: k3}
o2:
n3:
d4: {license: k4}
d5: {license: k5}
tasks:
- block:
- include_tasks: del_org.yml
loop:
- o2
loop_control:
loop_var: item_org
- debug:
msg: Delete orgs
gives
msg: Delete license k4 for [o2, n3, d4]
msg: Delete device d4 for [o2, n3]
msg: Delete license k5 for [o2, n3, d5]
msg: Delete device d5 for [o2, n3]
msg: Delete network n3 for [o2]
msg: Delete org o2
msg: Delete orgs

Related

Ansible: iterate over output with loop and get values for each list

I'm unsure if the title matches to what I'm trying to ask but I didn't know what other title to use.
So my question:
What I'm trying to do is gather disk info from VMWare and post the output of capacity_in_kb for EACH disk in a file (but currently only debug).
- name: Gather disk info from virtual machine
community.vmware.vmware_guest_disk_info:
hostname: "{{ lookup('ansible.builtin.env', 'VMWARE_HOST') }}"
username: "{{ lookup('ansible.builtin.env', 'VMWARE_USER') }}"
password: "{{ lookup('ansible.builtin.env', 'VMWARE_PASSWORD') }}"
validate_certs: no
name: "{{ ansible_hostname }}"
datacenter: "{{ lookup('ansible.builtin.env', 'VMWARE_HOST') }}"
delegate_to: 127.0.0.1
register: disk_info
- debug:
var: disk_info
This outputs the following:
{
"disk_info": {
"guest_disk_info": {
"0": {
"key": 2000,
"label": "Hard disk 1",
"summary": "16,777,216 KB",
"backing_filename": "[xxx] xxx.vmdk",
"backing_datastore": "xxx_log",
"controller_key": 1000,
"unit_number": 0,
"capacity_in_kb": 16777216,
"capacity_in_bytes": 17179869184,
"backing_type": "FlatVer2",
"backing_writethrough": false,
"backing_thinprovisioned": false,
"backing_eagerlyscrub": false,
"backing_diskmode": "persistent",
"backing_disk_mode": "persistent",
"backing_uuid": "xxxxxxxxxxxxxxxx",
"controller_bus_number": 0,
"controller_type": "paravirtual"
},
"1": {
"key": 2001,
"label": "Hard disk 2",
"summary": "262,144,000 KB",
"backing_filename": "[xxx] xxx.vmdk",
"backing_datastore": "xxx_log",
"controller_key": 1000,
"unit_number": 1,
"capacity_in_kb": 262144000,
"capacity_in_bytes": 268435456000,
"backing_type": "FlatVer2",
"backing_writethrough": false,
"backing_thinprovisioned": false,
"backing_eagerlyscrub": false,
"backing_diskmode": "persistent",
"backing_disk_mode": "persistent",
"backing_uuid": "xxx",
"controller_bus_number": 0,
"controller_type": "paravirtual"
}
},
"failed": false,
"changed": false
},
"_ansible_verbose_always": true,
"_ansible_no_log": false,
"changed": false
}
We now want to get capacity_in_kb for every disk. Some VMs only have one disk attached, some have two, some three and so on...
We tried
- name: with_dict
ansible.builtin.debug:
msg: "{{ item.value.label }} - {{ item.value.capacity_in_kb }}"
loop: "{{ disk_info.guest_disk_info|dict2items }}"
but that also doesn't work for us and only gives us the output of the first disk [0] in the list.
Does anyone have an idea? Thanks in advance :)
Edit: as #β.εηοιτ.βε pointet out it's
- loop: "{{ disk_info|dict2items }}"
+ loop: "{{ disk_info.guest_disk_info|dict2items }}"
but still it only iterates over the first disk and not the second, third, etc..
EDIT2: it worked, but I just didn't see it...dumb me
If you want to iterate the data, the task below
- debug:
msg: "{{ item.value.label }}: {{ item.value.capacity_in_kb }}"
loop: "{{ disk_info.guest_disk_info|dict2items }}"
loop_control:
label: "{{ item.key }}"
gives
ok: [localhost] => (item=0) =>
msg: 'Hard disk 1: 16777216'
ok: [localhost] => (item=1) =>
msg: 'Hard disk 2: 262144000'
The next option is to avoid the iteration and create a dictionary
label_capacity_q: '*.[label, capacity_in_kb]'
label_capacity: "{{ dict(disk_info.guest_disk_info|
json_query(label_capacity_q)) }}"
gives
label_capacity:
Hard disk 1: 16777216
Hard disk 2: 262144000
Example of a complete playbook for testing
- hosts: localhost
vars:
label_capacity_q: '*.[label, capacity_in_kb]'
label_capacity: "{{ dict(disk_info.guest_disk_info|
json_query(label_capacity_q)) }}"
tasks:
- debug:
var: label_capacity
- debug:
msg: "{{ item.key }}: {{ item.value }}"
loop: "{{ label_capacity|dict2items }}"
- debug:
msg: |-
{% for k,v in label_capacity.items() %}
{{ k }}: {{ v }}
{% endfor %}
gives (abridged)
TASK [debug] *********************************************************************************
ok: [localhost] =>
label_capacity:
Hard disk 1: 16777216
Hard disk 2: 262144000
TASK [debug] *********************************************************************************
ok: [localhost] => (item={'key': 'Hard disk 1', 'value': 16777216}) =>
msg: 'Hard disk 1: 16777216'
ok: [localhost] => (item={'key': 'Hard disk 2', 'value': 262144000}) =>
msg: 'Hard disk 2: 262144000'
TASK [debug] *********************************************************************************
ok: [localhost] =>
msg: |-
Hard disk 1: 16777216
Hard disk 2: 262144000
Given the data, for example in group_vars
shell> cat group_vars/all/disk_info.yml
---
disk_info:
changed: false
failed: false
guest_disk_info:
'0':
backing_datastore: xxx_log
backing_disk_mode: persistent
backing_diskmode: persistent
backing_eagerlyscrub: false
backing_filename: '[xxx] xxx.vmdk'
backing_thinprovisioned: false
backing_type: FlatVer2
backing_uuid: xxxxxxxxxxxxxxxx
backing_writethrough: false
capacity_in_bytes: 17179869184
capacity_in_kb: 16777216
controller_bus_number: 0
controller_key: 1000
controller_type: paravirtual
key: 2000
label: Hard disk 1
summary: 16,777,216 KB
unit_number: 0
'1':
backing_datastore: xxx_log
backing_disk_mode: persistent
backing_diskmode: persistent
backing_eagerlyscrub: false
backing_filename: '[xxx] xxx.vmdk'
backing_thinprovisioned: false
backing_type: FlatVer2
backing_uuid: xxx
backing_writethrough: false
capacity_in_bytes: 268435456000
capacity_in_kb: 262144000
controller_bus_number: 0
controller_key: 1000
controller_type: paravirtual
key: 2001
label: Hard disk 2
summary: 262,144,000 KB
unit_number: 1

How can I build list from the values of a dictionary?

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=***"
]
]
}

how can I update a dictionary in Ansible

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

split string in subelements ansible

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"

Back-references in Ansible loops

Is it possible to reference the "previous" value of a loop variable in an Ansible playbook?
What I would like to do is find some form of loop that effectively makes the following functional:
---
vars:
nums:
- 1
- 2
- 3
- 4
tasks:
- name: show fibonacci
command: echo {{ item }} * {{ item.prev }}
loop: nums
Use include_tasks. The play below
tasks:
- name: show fibonacci
include_tasks: fibonacci.yml
with_sequence: start=1 end=4
with include file fibonacci.yml
- debug:
msg: "{{ item|int * item_prev|default('1')|int }}"
- set_fact:
item_prev: "{{ item }}"
gives:
"msg": "1"
"msg": "2"
"msg": "6"
"msg": "12"

Resources