Ansible; using a loop to cycle through variables - loops

I have an inventory in which many host_vars are set. Each host will contain a different number of datasets.
e.g.
host: host1
ip-addr: 192.0.2.12/24
datasets:
set1:
var1: 'east'
var2: 'west'
set2:
var1: 'north'
var2: 'south'
I can create a loop to count the datasets, but I don't seem able to use it to reference [varX]:
- name: "test loop"
debug:
msg:
- "{{ item }}"
- "{{ 'datasets.set' + item + '.var1' }}"
- "{{ datasets.set1.var1 }}"
loop: "{{ query('sequence', 'start=1 end='+((datasets|length)|string)) }}"
This appears to assemble the variable name I'm attempting to reference, however does not return the value associated with it. Manually calling that variable does return the interesting value.
ok: [host1] => (item=1) => {
"msg": [
"1",
"datasets.set1.var1",
"east"
]
}
ok: [host1] => (item=2) => {
"msg": [
"2",
"datasets.set2.var1",
"east"
]
}
Is what I'm doing possible, or should I be approaching it from another angle?
thanks in advance.

The task
- debug:
msg: "set{{ item }}.var1 = {{ datasets['set' ~ item].var1 }}"
loop: "{{ range(1, datasets|length+1)|list }}"
gives
"msg": "set1.var1 = east"
"msg": "set2.var1 = north"

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 to loop over hostvars of register output

I'm struggling to loop over hostvars of the registered output of all the hosts in 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') }}"
loops:
- "{{ ansible_play_batch }}"
- "{{ hostvars[item].result.containers }}"
Error i get is item not defined. I would want the hosts refer to their respective result.containers. Not sure on how to use hostvars for the host to refer their respective result.containers.
Here is result.container output.
TASK [Debug dockerInfo]
ok: [vm1.nodekite.com] => {
"result.containers": [
{
"Image": "ca.docker/webproxy:1.0.0",
},
{
"Image": "docker.local/egacustomer:1.0.1",
},
]}
ok: [vm2.nodekite.com ] => {
"result.containers": [
{
"Image": "ca.docker/webproxyui:1.0.0",
},
{
"Image": "cna-docker-local/lega-customer:1.0.1",
},
]}
Here is the what i'm trying to achieve
changed: [vm1.nodekite.com] => {
"add_host": {
"groups": [
"webproxy"
],
"host_name": "vm1.nodekite.com",
},
changed: [vm1.nodekite.com] => {
"add_host": {
"groups": [
"egacustomer"
],
"host_name": "vm1.nodekite.com",
},
changed: [vm2.nodekite.com] => {
"add_host": {
"groups": [
"webproxy" >> this should be webproxyui
],
"host_name": "vm2.nodekite.com",
},
changed: [vm2.nodekite.com] => {
"add_host": {
"groups": [
"egacustomer" >> this should be lega-customer
],
"host_name": "vm2.nodekite.com",
},
Any help would be greatly appreciated.
I would run this task using Images Names
- hosts: "{{ group }}"
gather_facts: false
become: true
become_method: sudo
tasks:
- name: stop or restart docker containers
command: "docker {{ state }} {{ container_name }}"
How about just group_by paired with the loop?
- hosts: "{{ env }}"
become: True
tasks:
- name: Get dockerinfo
docker_host_info:
containers: yes
register: result
- debug:
var: result.containers
- group_by:
key: "container_{{ item.image | regex_replace('.*?/(.*?):.*', '\\1') }}"
loop: "{{ result.containers }}"
- debug:
var: group_names
You don't need to add the prefix. But this would add each host to groups with their container image prefixes. You should be able to use the group later in the play or playbook.

Ansible: How to loop over registered output of dynamic hosts in the next task

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

Combining the output of multiple loops in Ansible

I am working with Ansible and in my playbook I am running the following task, in which I have multiple loops so as to retrieve different tags from a specific xml:
- name: Retrieve multiple xml tags valuei
xml:
xmlstring: "{{ item.string }}"
xpath: "{{ item.path }}"
content: text
loop:
- { path: "/rpc-reply/vlan-instance-information/vlan-instance-group/vlan-member/vlan-tag", string: "{{topology.xml}}" }
- { path: "/rpc-reply/vlan-instance-information/vlan-instance-group/vlan-member/vlan-member-interface", string: "{{topology.xml}}" }
register: tags_value
- debug:
msg: "{{ item.matches }}"
loop: "{{ tags_value.results }}"
loop_control:
label: "{{ item.matches }}"
So I am getting the following:
ok: [sss-sd1-02] => (item=[{u'vlan-member-interface': u'et-0/0/0.0*'}, {u'vlan-member-interface': u'et-0/0/1.0*'}]) => {
"msg": [
{
"vlan-member-interface": "et-0/0/0.0*"
},
{
"vlan-member-interface": "et-0/0/1.0*"
}
]
}
and this
ok: [sss-sd1-02] => (item=[{u'vlan-tag': u'4071'}, {u'vlan-tag': u'4072'}]) => {
"msg": [
{
"vlan-tag": "4071"
},
{
"vlan-tag": "4072"
}
]
}
Is there a way to group "vlan-member-interface": "et-0/0/0.0*" and "vlan-tag": "4071" in 1 result , either in this task or in a different one ? And also is there a way to create a list with only {4071, 4072} ? Because i cannot handle it as it is right now !!!
The question can be simplified. Let's have the data
results:
- [A: a1, A: a2]
- [B: b1, B: b2]
Q1: "Group "A:a1" and "B:b1" in 1 result"
A: Use zip function. For example
- set_fact:
my_groups: "{{ results.0|zip(results.1)|list }}"
- debug:
var: my_groups
gives
"my_groups": [
[
{
"A": "a1"
},
{
"B": "b1"
}
],
[
{
"A": "a2"
},
{
"B": "b2"
}
]
]
}
Q2: "Create a list with only [b1, b2]"
A: Use dict2items and json_query functions. For example
- set_fact:
my_values: "{{ results.1|map('dict2items')|list|json_query('[].value') }}"
- debug:
var: my_values
gives
"my_values": [
"b1",
"b2"
]
Question 1 is substitution of: Group "vlan-member-interface": "et-0/0/0.0*" and "vlan-tag": "4071" in 1 result
Question 2 is substitution of: Create a list with only {4071, 4072}
Variable results is substitution of tags_value.results

Ansible: iterate over a list of dictionaries - loop vs. with_items

I'm getting different results when using loop vs with_items when trying to iterate over a list of dictionaries.
I've tried using loop|dict2items (the structure isn't a dictionary, & it tells me as much. heh) and loop with the flatten filter.
Here is the list of dictionaries:
"msg": [
{
"id": "id1",
"ip": "ip1",
"name": "name1"
},
{
"id": "id2",
"ip": "ip2",
"name": "name2"
},
{
"id": "id3",
"ip": "ip3",
"name": "name3"
},
{
"id": "id4",
"ip": "ip4",
"name": "name4"
}
]
}
Here is the task in the playbook:
- name: Add privateIp windows_instances to inventory
add_host:
name: "{{ item.ip }}"
aws_name: "{{ item.name }}"
groups: windows_instances
aws_instanceid: "{{ item.id }}"
ansible_user: "{{ windows_user }}"
ansible_password: "{{ windows_password }}"
ansible_port: 5985
ansible_connection: winrm
ansible_winrm_server_cert_validation: ignore
loop:
- "{{ list1 | flatten(levels=1) }}"
When attempting to run the above code, I get the "list object has no attribute" error. I've tried different flatten levels to no avail.
HOWEVER...
If I simply replace the loop above with:
with_items:
- "{{ list1 }}"
Everything works perfectly. I'm missing something in the with_items > loop translation here...
Don't put a - before your list.
And here, you have a list of dicts, so you don't need to flatten neither.
This playbook:
- hosts: localhost
gather_facts: no
vars:
demo_list:
- ip: 1.2.3.4
id: 1
name: demo1
- ip: 2.2.3.4
id: 2
name: demo2
- ip: 3.2.3.4
id: 3
name: demo3
tasks:
- name: the list
debug:
msg: "{{ demo_list }}"
- name: unflattened list
debug:
msg: "{{ item.id }} {{ item.ip }} {{ item.name }}"
loop:
"{{ demo_list }}"
- name: flattened list == unflattened list in this case
debug:
msg: "{{ item.id }} {{ item.ip }} {{ item.name }}"
loop:
"{{ demo_list | flatten(levels=1) }}"
gives this result:
PLAY [localhost] ***************************************************************************************
TASK [the list] ****************************************************************************************
ok: [localhost] => {
"msg": [
{
"id": 1,
"ip": "1.2.3.4",
"name": "demo1"
},
{
"id": 2,
"ip": "2.2.3.4",
"name": "demo2"
},
{
"id": 3,
"ip": "3.2.3.4",
"name": "demo3"
}
]
}
TASK [unflattened list] ********************************************************************************
ok: [localhost] => (item=None) => {
"msg": "1 1.2.3.4 demo1"
}
ok: [localhost] => (item=None) => {
"msg": "2 2.2.3.4 demo2"
}
ok: [localhost] => (item=None) => {
"msg": "3 3.2.3.4 demo3"
}
TASK [flattened list == unflattened list in this case] *************************************************
ok: [localhost] => (item=None) => {
"msg": "1 1.2.3.4 demo1"
}
ok: [localhost] => (item=None) => {
"msg": "2 2.2.3.4 demo2"
}
ok: [localhost] => (item=None) => {
"msg": "3 3.2.3.4 demo3"
}
PLAY RECAP *********************************************************************************************
localhost : ok=3 changed=0 unreachable=0 failed=0

Resources