Inner loop in loop over dict in Ansible - ansible

How to loop over dict in this example?
I can loop over list of strings (lorem:), but I can not loop over objects: dict.
I am using module which needs as input list and dict, so I have be able use both cases, but before module call I have to do some workaround with keys from dict so I have to be able reference it.
I am not sure what I am doing wrong. Can anyone show me a proper example?
Thanks
---
- name: test
hosts: localhost
connection: local
vars:
persons:
foo:
name: john
state: us
objects:
phone: samsung
color: black
capacity: 32
lorem:
- 1
- 2
- 3
bar:
name: helmut
state: de
objects:
phone: lg
color: red
capacity: 16
lorem:
- 4
- 5
- 6
tasks:
- name: List of strings is OK
debug:
msg: "{{ item.0.value.name }} and object: {{ item.1 }}"
loop: "{{ persons | dict2items |subelements('value.lorem',{ 'skip_missing': True }) }}"
- name: Dict referencing key:value is not OK
debug:
msg: "Name: {{ item.0.value.name }} and object: {{ item.1.[value] }} with key name: {{ item.1.[key]}}"
loop: "{{ persons | dict2items |subelements('value.objects',{ 'skip_missing': True }) }}"
Produces an error: fatal: [localhost]: FAILED! => {"msg": "the key 'objects' should point to a list, got {u'color': u'black', u'phone': u'samsung', u'capacity': 32}"}

Best I can think of is to do something like this. Using jinja to generate a list that returns what you need.
---
- name: test
hosts: localhost
connection: local
vars:
persons:
foo:
name: john
state: us
objects:
phone: samsung
color: black
capacity: 32
lorem:
- 1
- 2
- 3
bar:
name: helmut
state: de
objects:
phone: lg
color: red
capacity: 16
lorem:
- 4
- 5
- 6
tasks:
- debug:
msg: |
[
{% for p in persons %}
{% for o in persons[p].objects %}
{
"name": "{{ persons[p].name }}",
"key": "{{ o }}",
"value": "{{ persons[p].objects[o] }}"
},
{% endfor %}
{% endfor %}
]
Output
TASK [debug] *******************************************************************
ok: [localhost] => {
"msg": [
{
"key": "color",
"name": "john",
"value": "black"
},
{
"key": "phone",
"name": "john",
"value": "samsung"
},
{
"key": "capacity",
"name": "john",
"value": "32"
},
{
"key": "color",
"name": "helmut",
"value": "red"
},
{
"key": "phone",
"name": "helmut",
"value": "lg"
},
{
"key": "capacity",
"name": "helmut",
"value": "16"
}
]
}
Oh, and if you wanted to use that in a loop, just something like this.
- debug:
msg: "{{ item.name }} {{ item.key }} {{ item.value }}"
loop_control:
label: "{{ item.name }} {{ item.key }} {{ item.value }}"
loop: |
[
{% for p in persons %}
{% for o in persons[p].objects %}
{
"name": "{{ persons[p].name }}",
"key": "{{ o }}",
"value": "{{ persons[p].objects[o] }}"
},
{% endfor %}
{% endfor %}
]
Output
TASK [debug] *******************************************************************
ok: [localhost] => (item=john color black) => {
"msg": "john color black"
}
ok: [localhost] => (item=john phone samsung) => {
"msg": "john phone samsung"
}
ok: [localhost] => (item=john capacity 32) => {
"msg": "john capacity 32"
}
ok: [localhost] => (item=helmut color red) => {
"msg": "helmut color red"
}
ok: [localhost] => (item=helmut phone lg) => {
"msg": "helmut phone lg"
}
ok: [localhost] => (item=helmut capacity 16) => {
"msg": "helmut capacity 16"
}

You can use with_items as well.
Please refer below code:
playbook -->
---
- name: test hosts: localhost gather_facts: no tasks:
- include_vars: vars.yml
- name: debug
debug:
msg: "{{ item.vars.persons }}"
with_items:
- "{{ vars }}"
output --->
"msg": {
"bar": {
"lorem": [
4,
5,
6
],
"name": "helmut",
"objects": {
"capacity": 16,
"color": "red",
"phone": "lg"
},
"state": "de"
},
"foo": {
"lorem": [
1,
2,
3
],
"name": "john",
"objects": {
"capacity": 32,
"color": "black",
"phone": "samsung"
},
"state": "us"
}
}
}

Related

Ansible nested loop

Is it possible for Ansible to do a nested loop as I want to do a filtering and set fact.
The first fact that I have is
"username_and_role": [
{
"Name": "Jack",
"Role": "Admin User"
},
{
"Name": "Tom",
"Role": "Buyer"
},
{
"Name": "Jill",
"Role": "Seller"
}
]
The second fact that I have is
"username_and_status": [
{
"isLockedOut": "no",
"Name": "Jack"
},
{
"isLockedOut": "no",
"Name": "Tom"
},
{
"isLockedOut": "no",
"Name": "Jill"
},
{
"isLockedOut": "no",
"Name": "Brody"
},
{
"isLockedOut": "no",
"Name": "Steve"
}
]
My objective is that I want to retrieve isLockedOut from second data. If name in username_and_role is equal to name in username_and_status, retrieve the isLockedOut from username and status. and set a fact that show name, role and isLockedOut.
I've tried a few methods and it doesn't work as it didn't do the checking.
set_fact:
my_new_list: "{{[username_and_role, {'status': nameIsMatch}] }}"
cacheable: yes
with_items: "{{username_and_role}}"
vars:
nameIsMatch: "{% if item.Name == username%}item.isLockedOut{% else %}Name Does not Match{%endif %}"
loop: "{{username_and_status}}"
This is code has no error but it just did not do the checking between username_and_role & username_and_status
Convert the lists to dictionaries, e.g.
- set_fact:
username_and_role: "{{ username_and_role|
items2dict(key_name='Name',
value_name='Role') }}"
gives
username_and_role:
Jack: Admin User
Jill: Seller
Tom: Buyer
- set_fact:
username_and_status: "{{ username_and_status|
items2dict(key_name='Name',
value_name='isLockedOut') }}"
gives
username_and_status:
Brody: 'no'
Jack: 'no'
Jill: 'no'
Steve: 'no'
Tom: 'no'
Now you can retrieve the status by using the dictionary, e.g.
- debug:
msg: >
name: {{ item }}
role: {{ username_and_role[item]|d('NA') }}
status: {{ username_and_status[item]|d('NA') }}
loop: "{{ username_and_status.keys()|list }}"
gives
msg: |-
name: Jack role: Admin User status: no
msg: |-
name: Tom role: Buyer status: no
msg: |-
name: Jill role: Seller status: no
msg: |-
name: Brody role: NA status: no
msg: |-
name: Steve role: NA status: no
just to precise the nice answer of #vladimir-botka, if you just want the persons which are in both variables,
you could add a condition when
- debug:
msg: >
name: {{ item }}
role: {{ username_and_role[item] }}
status: {{ username_and_status[item] }}
when: username_and_role[item] is defined and username_and_status[item] is defined
loop: "{{ username_and_status.keys()|list }}"
- debug:
var: username_and_role | to_nice_json
result:
ok: [localhost] =>
username_and_role | to_nice_json: |-
{
"Jack": "Admin User",
"Jill": "Seller",
"Tom": "Buyer"
}

How do I work with Ansible list of dicts?

I'm so confused with this. If I have a file containing:
users:
- name: jconnor
first: john
last: connor
uid: 3003
- name: sconnor
first: sarah
last: connor
uid: 3001
How do I get the details of each user? With this simple playbook:
- name: create users
hosts: localhost
gather_facts: false
tasks:
- name: Include vars
include_vars:
file: user_list.yml
name: users
- name: debug
debug:
msg: "{{ item }}"
with_dict: "{{ users }}"
I get the following which I can't use:
ok: [localhost] => (item={'value': [{u'last': u'connor', u'uid': 3003, u'name': u'jconnor', u'first': u'john'}, {u'last': u'connor', u'uid': 3001, u'name': u'sconnor', u'first': u'sarah'}], 'key': u'users'}) => {
"msg": {
"key": "users",
"value": [
{
"first": "john",
"last": "connor",
"name": "jconnor",
"uid": 3003
},
{
"first": "sarah",
"last": "connor",
"name": "sconnor",
"uid": 3001
}
]
}
}
I want to create user accounts with this but I simply don't understand how to use this structure.
Note that this is part of a larger structure and I can't change it.
Thanks
Since the users variable is a list of dicts, you should loop with loop or with_items. Then we can access the key of each dict with item.key. E.g.: item.name, item.uid, etc.
Note that you are importing the variables from the file with the name users. So this variable now contains the users hash of that file. If you skip name: users in include_var, then you can directly access the users dict while looping.
tasks:
- include_vars:
file: user_list.yml
name: users
- debug:
msg: "Username is {{ item.name }}, full name is {{ item.first }} {{ item.last }}, userid is {{ item.uid }}"
with_items: "{{ users.users }}"
This outputs message (showing 1 item):
ok: [localhost] => (item={u'last': u'connor', u'uid': 3003, u'name': u'jconnor', u'first': u'john'}) => {
"msg": "Username is jconnor, full name is john connor, userid is 3003"
}

Ansible set_fact is overwriting the items

Here is my main.yml
---
- name: Gathering VCenter facts
vmware_vm_info:
hostname: "{{ vcenter_server }}"
username: "{{ vcenter_user }}"
password: "{{ vcenter_pass }}"
validate_certs: false
register: vcenter_facts
delegate_to: localhost
- debug:
var: vcenter_facts.virtual_machines
- name: Find all test-vms to run IO
set_fact:
vm_ip: "{{ item.ip_address }}"
loop: "{{ vcenter_facts.virtual_machines }}"
when: item.guest_name is regex("test_vm*")
- name: print vm_ip variable value
debug:
var: vm_ip
- name: Mount 16TB dropbox in each test vm
shell: mount-16tb-dropbox.sh
args:
chdir: /usr/local/bin/
with_items: "{{ vm_ip }}"
And here is the recap:
ok: [localhost] => {
"vcenter_facts.virtual_machines": [
{
"attributes": {},
"cluster": "Compute Cluster",
"esxi_hostname": "100.80.90.179",
"guest_fullname": "CentOS 7 (64-bit)",
"guest_name": "test_vm4",
"ip_address": "192.168.202.13",
"mac_address": [
"00:50:56:9d:d2:99"
],
"power_state": "poweredOn",
"tags": [],
"uuid": "421d7b54-1359-14e8-3ec4-74b568cb96d2",
"vm_network": {
"00:50:56:9d:d2:99": {
"ipv4": [
"192.168.202.13"
],
"ipv6": [
"fe80::44f6:a395:cde3:4dd1",
"fe80::a357:a163:e44f:2086",
"fe80::cd0c:e7d7:1356:2830"
]
}
}
},
{
"attributes": {},
"cluster": "Compute Cluster",
"esxi_hostname": "100.80.90.178",
"guest_fullname": "CentOS 7 (64-bit)",
"guest_name": "test_vm3",
"ip_address": "192.168.202.12",
"mac_address": [
"00:50:56:9d:a9:e8"
],
"power_state": "poweredOn",
"tags": [],
"uuid": "421d9239-0980-80c1-bca4-540efd726452",
"vm_network": {
"00:50:56:9d:a9:e8": {
"ipv4": [
"192.168.202.12"
],
"ipv6": [
"fe80::cd0c:e7d7:1356:2830"
]
}
}
},
{
"attributes": {},
"cluster": "Compute Cluster",
"esxi_hostname": "100.80.90.178",
"guest_fullname": "CentOS 7 (64-bit)",
"guest_name": "Test_Automation_CentOS8_Linux_VM",
"ip_address": "192.168.202.6",
"mac_address": [
"00:50:56:9d:13:14"
],
"power_state": "poweredOn",
"tags": [],
"uuid": "421d53ba-4824-57e4-06fd-fba0f2b1dbea",
"vm_network": {
"00:50:56:9d:13:14": {
"ipv4": [
"192.168.202.6"
],
"ipv6": [
"fe80::cd0c:e7d7:1356:2830",
"fe80::44f6:a395:cde3:4dd1"
]
}
}
},
{
"attributes": {},
"cluster": "Compute Cluster",
"esxi_hostname": "100.80.90.180",
"guest_fullname": "CentOS 7 (64-bit)",
"guest_name": "test_vm5",
"ip_address": "192.168.202.14",
"mac_address": [
"00:50:56:9d:85:b6"
],
"power_state": "poweredOn",
"tags": [],
"uuid": "421d6855-e60e-cd80-f113-39f11927d63b",
"vm_network": {
"00:50:56:9d:85:b6": {
"ipv4": [
"192.168.202.14"
],
"ipv6": [
"fe80::44f6:a395:cde3:4dd1",
"fe80::cd0c:e7d7:1356:2830",
"fe80::a357:a163:e44f:2086"
]
}
}
}
]
}
I am not able to loop through all the ip_address variable (i.e. 192.168.202.12, 192.168.202.13, 192.168.202.14).
It just reads the last item (i.e. 192.168.202.14).
What am I possibly doing wrong with set_fact that it is not reading all the variable and performing the set of tasks that follows?
An alternate solution using json_query
---
- name: Gathering VCenter facts
vmware_vm_info:
hostname: "{{ vcenter_server }}"
username: "{{ vcenter_user }}"
password: "{{ vcenter_pass }}"
validate_certs: false
register: vcenter_facts
delegate_to: localhost
- name: Mount 16TB dropbox in each test vm
shell: mount-16tb-dropbox.sh
args:
chdir: /usr/local/bin/
vars:
query: >-
[?contains("guest_name", 'test_vm')].ip_address[]
with_items: "{{ vcenter_facts.virtual_machines | to_json | from_json | json_query(query) | list }}"
Note: to_json | from_json is a workaround for a bug between ansible and jmespath so that all values can be converted to real strings and can be used with the jmespath contains function.
This should give all the IP. You correctly assumed where the code may need correction. In the code, vm_ip variable was overwritten by each loop and the last IP remained. What you need is a list and then append each IP to the list.
- set_fact:
vm_ip: "{{ vm_ip | default([]) + [item.ip_address] }}"
loop: "{{ vcenter_facts.virtual_machines | flatten }}"
when: item.guest_name is regex("test_vm*")
- debug:
var: vm_ip
Alternative solution using Jinja2 filters.
- set_fact:
vm_ip: >-
{{ vcenter_facts.virtual_machines | flatten
| rejectattr('guest_name', 'match', '^(?!test_vm).*')
| map(attribute='ip_address') | list }}

How to pull deeply nested data from a dictionary in ansible

I am trying to print nested data from an ansible dictionary. However, I cannot figure out how to pull the nested values. Specifically, in the data below, I am trying to print the two values returned under "ipv4". Currently, the output of item.value.ipv4 is the portion in brackets below:
"msg": "GigabitEthernet0/0/0,[{'address': '192.168.3.1', 'subnet': '28'}]"
I would like to just use the value like so:
"msg": "GigabitEthernet0/0/0, 192.168.3.1, 28"
I cannot figure out how to pull this nested data out of to do this. To put it simply, it would be nice if something like this worked: item.value.ipv4['address']. How is this done?
tasks:
- name: get config for Cisco IOS
ios_facts:
gather_subset: all
gather_network_resources: interfaces
- name: create dictionary with ansible_net_interfaces
set_fact:
foo_value: "{{ query('dict', ansible_net_interfaces) }}"
- name: display the results of foo_value
debug:
msg: "{{ foo_value }}"
- name: display certain detalys values from foo_value
debug:
msg: "{{ item.key }},{{ item.value.ipv4 }}"
with_items: "{{ foo_value }}"
These tasks produce the following:
TASK [display the results of foo] **********************************************************************************************************************
ok: [192.168.3.1] => {
"msg": [
{
"key": "GigabitEthernet0/0/0",
"value": {
"bandwidth": 1000000,
"description": "This is an interface description",
"duplex": "Full",
"ipv4": [
{
"address": "192.168.3.1",
"subnet": "28"
}
],
"lineprotocol": "up",
"macaddress": "50f7.123c.b0c0",
"mediatype": "RJ45",
"mtu": 1500,
"operstatus": "up",
"type": "ISR4331-3x1GE"
}
},
{
"key": "GigabitEthernet0/0/1",
"value": {
"bandwidth": 1000000,
"description": "This is another interface description",
"duplex": "Full",
"ipv4": [
{
"address": "192.168.3.33",
"subnet": "30"
}
],
"lineprotocol": "up",
"macaddress": "50f7.123c.b0c0",
"mediatype": "RJ45",
"mtu": 1500,
"operstatus": "up",
"type": "ISR4331-3x1GE"
}
},
]
}
ipv4 is a list of dictionaries. Assuming you need only the first dictionary,
- name: display certain detalys values from foo_value
debug:
msg: "{{ item.key }},{{ item.value.ipv4[0].address }},{{ item.value.ipv4[0].subnet }}"
when: item.value.ipv4 is defined and item.value.ipv4[0].subnet is defined and item.value.ipv4[0].address is defined
with_items: "{{ foo_value }}"

set_fact create a list with items

I have task which calls an API and I register the o/p in a varaible;
- name: Get Object storage account ID
uri:
url: 'https://api.softlayer.com/rest/v3.1/SoftLayer_Network_Storage_Hub_Cleversafe_Account/getAllObjects.json?objectFilter={"username":{"operation":"{{ item }}"}}'
method: GET
user: abxc
password: 66c94c447a6ed8a0cf058774fe38
validate_certs: no
register: old_existing_access_keys_sl
with_items: '{{ info["personal"].sl_cos_accounts }}'
old_existing_access_keys_sl holds:
"old_existing_access_keys_sl.results": [
{
"json": [
{
"accountId": 12345,
"id": 70825621,
"username": "xyz-11"
}
]
},
{
"json": [
{
"accountId": 12345,
"id": 70825621,
"username": "abc-12"
}
]
}
I want to make a list of id's for further processing an tried the following task but this did not work:
- name: Create a list of account ids
set_fact:
admin_usernames = "{{ item.json[0].id | list }}"
with_items: old_existing_access_keys_sl.results
I am not sure if that's even possible. I also tried this:
- name: create a list
set_fact:
foo: "{% set foo = [] %}{% for i in old_existing_access_keys_sl.results %}{{ foo.append(i) }}{% endfor %}"
foo always comes as blank and as a string:
TASK [result] *****************************************************************************************************************************************
ok: [localhost] => {
"foo": ""
}
Given your example data, you can extract a list of ids using the json_query filter, like this:
---
- hosts: localhost
gather_facts: false
vars:
old_existing_access_keys_sl:
results:
[
{
"json": [
{
"accountId": 12345,
"id": 70825621,
"username": "xyz-11"
}
]
},
{
"json": [
{
"accountId": 12345,
"id": 70825621,
"username": "abc-12"
}
]
}
]
tasks:
- debug:
var: old_existing_access_keys_sl|json_query('results[*].json[0].id')
This will output:
TASK [debug] **********************************************************************************************************************************************************************************
ok: [localhost] => {
"old_existing_access_keys_sl|json_query('results[*].json[0].id')": [
70825621,
70825621
]
}
If you want to store these in a new variable, you can replace that debug task with set_fact:
- set_fact:
admin_ids: "{{ old_existing_access_keys_sl|json_query('results[*].json[0].id') }}"
Update
For a list of dictionaries, just change the json_query expression:
- debug:
var: "old_existing_access_keys_sl|json_query('results[*].json[0].{id: id, username: username}')"
For more information, see the jmespath website for documentation and examples.

Resources