Looping over an ansible dict for specific value - loops

With ansible, looping over a list of items that return a dict(?) with listed values.
Basically I want to check a dict of packages to see if they are installed or not (and later on return a message for any missing packages).
Any loop/with_dict combo tried so far return various errors that the specific variable can't be found.
When querying the status itself (yum module) it does put the full output into the {{ pkg }} var, per package.
---
- name: Ansible tests playbook
hosts: vms
remote_user: root
vars:
pkgs:
- yum-utils
- mariadb-libs
tasks:
- name: Check packages
yum:
list: "{{ item }}"
disablerepo: '*'
register: pkg
loop: "{{ pkgs }}"
If I then simply output the contents of {{ pkg.results }} with :
- name: list
debug:
msg: "{{ item }}"
loop: "{{ pkg.results }}"
I get :
root#vm011:~/ovirt# ansible-playbook check.yml
PLAY [Ansible tests playbook] **************************************************************************************************************************************************************
TASK [Gathering Facts] *********************************************************************************************************************************************************************
ok: [vm017.warp]
TASK [Check paclages] **********************************************************************************************************************************************************************
ok: [vm017.warp] => (item=yum-utils)
ok: [vm017.warp] => (item=mariadb-libs)
TASK [list] ********************************************************************************************************************************************************************************
ok: [vm017.warp] => (item=None) =>
msg:
changed: false
failed: false
invocation:
module_args:
allow_downgrade: false
conf_file: null
disable_gpg_check: false
disable_plugin: []
disablerepo: '*'
enable_plugin: []
enablerepo: null
exclude: null
install_repoquery: true
installroot: /
list: yum-utils
name: null
security: false
skip_broken: false
state: installed
update_cache: false
update_only: false
validate_certs: true
item: yum-utils
results:
- arch: noarch
envra: 0:yum-utils-1.1.31-50.el7.noarch
epoch: '0'
name: yum-utils
release: 50.el7
repo: installed
version: 1.1.31
yumstate: installed
ok: [vm017.warp] => (item=None) =>
msg:
changed: false
failed: false
invocation:
module_args:
allow_downgrade: false
conf_file: null
disable_gpg_check: false
disable_plugin: []
disablerepo: '*'
enable_plugin: []
enablerepo: null
exclude: null
install_repoquery: true
installroot: /
list: mariadb-libs
name: null
security: false
skip_broken: false
state: installed
update_cache: false
update_only: false
validate_certs: true
item: mariadb-libs
results:
- arch: x86_64
envra: 1:mariadb-libs-5.5.60-1.el7_5.x86_64
epoch: '1'
name: mariadb-libs
release: 1.el7_5
repo: installed
version: 5.5.60
yumstate: installed
How can I get just the yumstate value (installed, or otherwise), per package, into a separate variable ?
If, for instance, I try to debug the msg with
- name: list
debug:
msg: "{{ item.yumstate }}"
loop: "{{ pkg.results }}"
I get:
fatal: [vm017.warp]: FAILED! =>
msg: |-
The task includes an option with an undefined variable. The error was: 'dict object' has no attribute 'yumstate'

you are looping correctly through the pkg.results, but in each item the yumstate is under another results key. you should use:
- name: list
debug:
msg: "{{ item.results.yumstate }}"
loop: "{{ pkg.results }}"
i tried your code on my fedora, and the results for one of the 2 packages has as value a list, so the item.results.yumstate would not work: Fedora response for yum utils:
"item.results": [
{
"arch": "noarch",
"epoch": "0",
"name": "yum-utils",
"nevra": "0:yum-utils-1.1.31-517.fc29.noarch",
"release": "517.fc29",
"repo": "fedora",
"version": "1.1.31",
"yumstate": "available"
},
{
"arch": "noarch",
"epoch": "0",
"name": "yum-utils",
"nevra": "0:yum-utils-1.1.31-518.fc29.noarch",
"release": "518.fc29",
"repo": "updates",
"version": "1.1.31",
"yumstate": "available"
}
]
for mariadb-libs, no packages found, response was:
"item.results": []
hope it helps

You did indeed point me in the correct way.
- name: list
debug:
msg: "Package {{ item.results[0].name }} is {{ item.results[0].yumstate }}"
loop: "{{ pkg.results }}"
Did the trick, with just "{{ item.results.yumstate }}" it still returned an error
TASK [list] ********************************************************************************************************************************************************************************
fatal: [vm017.warp]: FAILED! =>
msg: |-
The task includes an option with an undefined variable. The error was: 'list object' has no attribute 'yumstate'
Adding the [0] got me:
TASK [list] ********************************************************************************************************************************************************************************
ok: [vm017.warp] => (item=None) =>
msg: Package yum-utils is installed
ok: [vm017.warp] => (item=None) =>
msg: Package mariadb-libs is installed

You might want to consider nested loops , also known aa "loops and includes" if you're attempting fancy things.
https://docs.ansible.com/ansible/latest/user_guide/playbooks_loops.html#defining-inner-and-outer-variable-names-with-loop-var

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

Ansible playbook get values from dict

I want to retrieve the id from a json dict based on a certain name. In this case I would like to get the ID from the "WebLogic" and store it into a variable to use the ID in a next task.
The playbook:
- name: Get Tags
uri:
url: "https://xx.xx-xx.xx{{ uat_env }}api/config/v1/autoTags"
method: GET
headers:
Content-Type: application/json; charset=utf-8
Authorization: xxx
return_content: yes
register: data
- name: Print returned json dictionary
debug:
var: data.json
- debug:
msg: "{{ data.json['values'] | json_query(query) }}"
vars:
name: 'WebLogic'
query: "[?name=='{{ name }}'].id"
- name: TEST
set_fact:
test: "{{ data.json['values'] | json_query([?name=='WebLogic'].id) }}"
Test run:
PLAY [TEST] ********************************************************************
TASK [Get all UAT autoTags] ****************************************************
ok: [localhost]
TASK [Print returned json dictionary] ******************************************
ok: [localhost] => {
"data.json": {
"values": [
{
"id": "5c3849a4-a044-4a98-a67a-c1ea42d652ca",
"name": "Apache"
},
{
"id": "b37511f4-d4e8-4c77-a628-841dba5c65d8",
"name": "WebLogic"
}
]
}
}
TASK [debug] *******************************************************************
ok: [localhost] => {
"msg": [
"b37511f4-d4e8-4c77-a628-841dba5c65d8"
]
}
TASK [TEST] ********************************************************************
fatal: [localhost]: FAILED! => {"msg": "template error while templating string: unexpected char '?' at 37. String: {{ data.json['values'] | json_query([?name=='WebLogic'].id) }}"}
PLAY RECAP *********************************************************************
localhost : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
The message returns empty.
the problem is at the data.json.values syntax, please replace with data.json["values"]
the two tasks to show the difference:
- debug:
msg: "{{ data.json.values | json_query(query) }}"
vars:
name: 'WebLogic'
query: "[?name=='{{ name }}'].id"
- debug:
msg: "{{ data.json['values'] | json_query(query) }}"
vars:
name: 'WebLogic'
query: "[?name=='{{ name }}'].id"
update:
To assign the value to a variable, below task should do it:
- set_fact:
my_var: "{{ data.json['values'] | json_query(query) }}"
vars:
name: 'WebLogic'
query: "[?name=='{{ name }}'].id"
- debug:
var: my_var

How to use proper loop in Ansible uri module

I'm using the Ansible uri module to trigger the pfSense API.
Now I want to create firewall rules in a task (code is truncated).
---
# tasks file for creating firewall rules
- name: "Create firewall rules"
uri:
url: "https://{{ pf_hostname }}/api/v1/firewall/rule"
method: "POST"
body: "{ \
\"client-id\": \"{{ pf_user }}\",
\"client-token\": \"{{ pf_password }}\",
\"type\": \"{{ pf_fw_type_01 }}\",
\"interface\": \"{{ pf_fw_interface_01 }}\",
}"
The vars file looks like this.
---
# vars file for creating firewall rules
# Authentication
pf_hostname: "pfsense.local"
pf_user: "admin"
pf_password: "pfsense"
# Rule 01
pf_fw_type_01: "pass"
pf_fw_interface_01: "wan"
How can I now repeat the task without unnecessary redundancy (e.g. with loop) for other rules?
I only come up with the following idea, but it doesn't seem ideal to me.
loop:
- "{{ item.client-id: {{ pf_user }}, item.type: {{ pf_fw_type_01 }} }}"
- "{{ item.client-id: {{ pf_user }}, item.type: {{ pf_fw_type_02 }} }}"
How about putting the rules as a dynamic parameter in the list?
For example, here's like.
vars.yml
---
# vars file for creating firewall rules
# Authentication
pf_hostname: "pfsense.local"
pf_user: "admin"
pf_password: "pfsense"
rules:
- num: 01
type: "pass"
pf_fw_interface: "wan"
- num: 02
type: "pass"
pf_fw_interface: "wan"
playbook
---
- hosts: localhost
gather_facts: false
vars_files:
- vars.yml
tasks:
- debug:
msg: |
{
"client-id": "{{ pf_user }}",
"client-token": "{{ pf_password }}",
"type": "{{ item.type }}",
"interface": "{{ item.pf_fw_interface }}"
}
loop: "{{ rules }}"
result
$ ansible-playbook main.yml
(snip)
PLAY [localhost] *********************************************************************************************************************************************************************
TASK [debug] *************************************************************************************************************************************************************************
ok: [localhost] => (item={'type': 'pass', 'pf_fw_interface': 'wan'}) => {
"msg": {
"client-id": "admin",
"client-token": "pfsense",
"interface": "wan",
"type": "pass"
}
}
ok: [localhost] => (item={'type': 'pass', 'pf_fw_interface': 'wan'}) => {
"msg": {
"client-id": "admin",
"client-token": "pfsense",
"interface": "wan",
"type": "pass"
}
}
(snip)

Multiple loops in ansible task

I'm trying to use vars to perform loops in ansible but I'm struggling to get them to work. The documentation talks about the loop attribute but as far as I can tell, this only works if you have one loop in a task.
Is it possible to use different loops in a task or must it be split into different tasks?
- name: "Configure ufw"
ufw:
policy: "{{ defaults.policy }}"
direction: "{{ defaults.direction }}"
rule: allow
src: "{{ rules }}"
logging: on
state: enabled
vars:
defaults:
- { direction: incoming, policy: deny }
- { direction: outgoing, policy: allow }
rules:
- 80/tcp
- 443/tcp
When I run the code I get the error
"msg": "The task includes an option with an undefined variable. The error was: 'list object' has no attribute 'direction'
What you need is with_nested. This:
---
- hosts: localhost
become: no
connection: local
vars:
defaults:
- { direction: incoming, policy: deny }
- { direction: outgoing, policy: allow }
rules:
- 80/tcp
- 443/tcp
tasks:
- name: Change connect file to bulk mode
debug:
msg: "{{ item.0.direction }}: {{ item.1 }}"
with_nested:
- "{{ defaults }}"
- "{{ rules }}"
Gives this output:
TASK [Change connect file to bulk mode] *********************************************************************************************
ok: [localhost] => (item=None) => {
"msg": "incoming: 80/tcp"
}
ok: [localhost] => (item=None) => {
"msg": "incoming: 443/tcp"
}
ok: [localhost] => (item=None) => {
"msg": "outgoing: 80/tcp"
}
ok: [localhost] => (item=None) => {
"msg": "outgoing: 443/tcp"
}
with_nested may be an option, as per Jack's answer.
Alternately, and possibly the currently preferred mechanism, is to use loop: "{{ your_first_list | product (your_second_list) | list }}" to combine your multiple lists into a single list.
For example, this:
- name: test
ansible.builtin.debug:
msg: "{{ item.0.0 }} - {{ item.0.1}} - {{ item.1 }}"
loop: "{{ [ ['incoming','deny'], ['outgoing','allow']] | product( [ '80/tcp', '443/tcp' ] ) | list }}"
produces this output:
ok: [examplehost.my.example.com] => (item=[[u'incoming', u'deny'], u'80/tcp']) => {
"msg": "incoming - deny - 80/tcp"
}
ok: [examplehost.my.example.com] => (item=[[u'incoming', u'deny'], u'443/tcp']) => {
"msg": "incoming - deny - 443/tcp"
}
ok: [examplehost.my.example.com] => (item=[[u'outgoing', u'allow'], u'80/tcp']) => {
"msg": "outgoing - allow - 80/tcp"
}
ok: [examplehost.my.example.com] => (item=[[u'outgoing', u'allow'], u'443/tcp']) => {
"msg": "outgoing - allow - 443/tcp"
}

Ansible Parse JSON Array from Register

Related Post: ansible parse json array reply from api
I have an Ansible playlist which registers a return variable:
- name: Create Instance
ec2_instance:
aws_access_key: "{{access_key}}"
aws_secret_key: "{{secret_key}}"
key_name: ***
instance_type: t2.micro
security_group: ***
image_id: ami-39f8215b
region: ***
register: details
So the details is a JSON object like this:
{
"details": {
"changed": false,
"changes": [],
"failed": false,
"instance_ids": [
"i-1111abcde"
],
...
}
All I want to do is write a text file with each instance_id in there:
i-1111abcde
I've tried all of the following, none working:
debug:
var: item
with_items: details['instance_ids']
debug:
var: item.item
with_items: details['instance_ids']
debug:
var: details.instance_ids
with_items: details
# This works, but prints the entire JSON array...
Solution
- name: Debug Info
debug:
var: item
loop: "{{details.instance_ids}}"
- name: Write Temp File
lineinfile:
path: /tmp/temp.txt
line: "{{ item }}"
loop: "{{ details.instance_ids }}"
Note: loop is a more modern Ansible concept that with_items or with_*
Solution
- name: Debug Info
debug:
var: item
loop: "{{details.instance_ids}}"
- name: Write Temp File
lineinfile:
path: /tmp/temp.txt
line: "{{ item }}"
loop: "{{ details.instance_ids }}"
Note: loop is a more modern Ansible concept that with_items or with_*

Resources