How to use comparison operators with dictionary values? - ansible

I am trying to write a task that stores the dictionary values in variable based on the condition .
I'm new to this technology. Please anyone help on the below request.
I tried with the below code. Please check below.
- set_fact:
v1: "{{ v1|default([]) + item.keys() if item.values() == false else 1 }}"
loop: "{{ dv }}"
'dv' is a dictionary.
[{1A:True},{2A:True},{3A:False},{4A:False}]
Actually, here I'm trying to store false values in v1 by using comparison operators only.
Expected output:
v1 should contain following list:
[3A,4A]
Ansible Version: 2.5.15

Below works for me:
---
- hosts: localhost
vars:
dv:
1A: 'True'
2A: 'False'
3A: 'True'
4A: 'False'
tasks:
- name: debug
debug:
msg: "{{ item.value }}"
loop: "{{ dv | dict2items }}"
- set_fact:
v1: "{{ v1| default([]) + item.key if (item.value in 'False') else('') }}"
loop: "{{ dv | dict2items }}"
- debug:
var: v1
output -->
TASK [set_fact] *********************************************************************************************************
ok: [localhost] => (item={'key': u'1A', 'value': u'True'})
ok: [localhost] => (item={'key': u'3A', 'value': u'True'})
ok: [localhost] => (item={'key': u'2A', 'value': u'False'})
ok: [localhost] => (item={'key': u'4A', 'value': u'False'})
TASK [debug] ************************************************************************************************************
ok: [localhost] => {
"v1": "2A4A"
}

You can try the below code.
- hosts: localhost
connection: local
vars:
dv: [{1A:True},{2A:True},{3A:False},{4A:False}]
v2: []
v1: []
tasks:
- set_fact:
v1: "{{ v1|default([]) }} + [ {{ v1.append((item.keys()|first).split(':')[0]) if (item.keys()|first).split(':')[1] == 'False' else v2.append('1') }} ]"
with_items: "{{ dv }}"
- debug:
msg: "{{ v1 }}"
Here v2 is a variable declared to direct if the conditions are not met.
Output of the a above code is a below:
ok: [localhost] => {
"msg": [
"3A",
"4A"
]
}

Related

Register variables with dynamic names from with_items

I would like to register variables from lookup results.
My inventory:
rrules:
- name: r1
start_date: '2022-01-01 13:00:00'
- name: r2
start_date: '2022-02-02 12:00:00'
Task looks like:
- name: Create a string for a schedule
debug:
msg: "{{ query('awx.awx.schedule_rrule', 'minute', start_date=item.start_date) }}"
register: "{{ item.name }}"
with_items:
- "{{ rrules }}"
When I run the playbook, I get the error:
fatal: [127.0.0.1]: FAILED! => {"msg": "Invalid variable name in 'register' specified: '{{ item.name }}'"}
Using the register of a debug task is a terrible idea.
Do not do that, instead, use the proper module to register variables, which is the set_fact module.
With it, you can register variable with a dynamic name, as you intended it:
- set_fact:
"{{ item.name }}": >-
{{ query(
'awx.awx.schedule_rrule',
'minute',
start_date=item.start_date
) }}
loop: "{{ rrules }}"
Given those tasks:
- set_fact:
"{{ item.name }}": >-
{{ query(
'awx.awx.schedule_rrule',
'minute',
start_date=item.start_date
) }}
loop: "{{ rrules }}"
vars:
rrules:
- name: r1
start_date: '2022-01-01 13:00:00'
- name: r2
start_date: '2022-02-02 12:00:00'
- debug:
var: r1
- debug:
var: r2
It will yield:
TASK [set_fact] ***************************************************************
ok: [localhost] => (item={'name': 'r1', 'start_date': '2022-01-01 13:00:00'})
ok: [localhost] => (item={'name': 'r2', 'start_date': '2022-02-02 12:00:00'})
TASK [debug] ******************************************************************
ok: [localhost] =>
r1: DTSTART;TZID=Europe/Paris:20220101T130000 RRULE:FREQ=MINUTELY;INTERVAL=1
TASK [debug] ******************************************************************
ok: [localhost] =>
r2: DTSTART;TZID=Europe/Paris:20220202T120000 RRULE:FREQ=MINUTELY;INTERVAL=1

Unicode error while increment variable value

When I work with static variables it works absolutely fine. But when I try to use dynamic it does not work.
The playbook:
---
- hosts: Swi1
vars:
NewOne: 0
provider:
host: "192.168.0.30"
transport: "cli"
username: "cisco"
password: "cisco"
tasks:
- name: gather facts
register: iosfacts
ios_facts:
provider: "{{ provider }}"
- name: Display the value of the counter
debug:
msg: "NewOne={{ NewOne }} / Data type={{ NewOne | type_debug }}"
- name: interface description
set_fact:
NewOne: " {{ NewOne + 1 }}"
parents: "interface {{ item.key }}"
with_dict: "{{ iosfacts.ansible_facts.ansible_net_interfaces }}"
when: item.value.operstatus == "up"
- debug:
msg: " This is Debug {{ NewOne }}"
Gives the error:
fatal: [Swi1]: FAILED! => {"msg": "Unexpected templating type error
occurred on ({{ NewOne + 1 }}): coercing to Unicode: need string or
buffer, int found"}
If you want to do an increment on a variable, you need to recast it as an int, as set_fact will always make you end up with a string.
As an example, the two tasks:
- set_fact:
NewOne: "{{ NewOne | d(0) + 1 }}"
- debug:
var: NewOne | type_debug
Are giving
TASK [set_fact] ***************************************************************
ok: [localhost]
TASK [debug] ******************************************************************
ok: [localhost] =>
NewOne | type_debug: str
The fix is, then, to use the int filter.
Given:
- set_fact:
NewOne: "{{ NewOne | d(0) | int + 1 }}"
loop: "{{ range(1, 4) }}"
- debug:
var: NewOne
This yields the expected
TASK [set_fact] ***************************************************************
ok: [localhost] => (item=1)
ok: [localhost] => (item=2)
ok: [localhost] => (item=3)
TASK [debug] ******************************************************************
ok: [localhost] =>
NewOne: '3'
But then with your use case, there are more elaborated and shorter way to achieve the same:
- set_fact:
NewOne: >-
{{
iosfacts
.ansible_facts
.ansible_net_interfaces
| selectattr('value.operstatus', '==', 'up')
| length
}}
Given:
- debug:
msg: >-
{{
iosfacts
.ansible_facts
.ansible_net_interfaces
| selectattr('value.operstatus', '==', 'up')
| length
}}
vars:
iosfacts:
ansible_facts:
ansible_net_interfaces:
- value:
operstatus: up
- value:
operstatus: down
- value:
operstatus: up
This yields:
ok: [localhost] =>
msg: '2'
It seems you are trying to implement a loop counter with a programming paradigm, which isn't plain possible in that way since Ansible is not a programming language but a Configuration Management Tool in which you declare a state.
Your current issue is reproducible in the following way:
---
- hosts: localhost
become: false
gather_facts: false
vars:
NewOne: 0
tasks:
- name: Show var
debug:
msg: "{{ NewOne | type_debug }}"
- name: Add value
set_fact:
NewOne: " {{ NewOne + 1 }}"
loop: [1, 2, 3]
- name: Show result
debug:
msg: "{{ NewOne }}
resulting into an output of
TASK [Add value] *************
ok: [localhost] => (item=1)
fatal: [localhost]: FAILED! =>
msg: 'Unexpected templating type error occurred on ( {{ NewOne + 1 }}): coercing to Unicode: need string or buffer, int found'
Possible Solutions
You may have a look into Migrating from with_X to loop and Extended loop variables as an iteration counter is already provided there.
An other approach is given via type casting with filter in the answer of #β.εηοιτ.βε.
There as well if you are just interested in the amount of occurrences of certain status, like interface status up or down.
Further Q&A
Ansible set_fact type cast
Further Documentation
Discovering the data type
Forcing the data type

ansible Iterate var only if var is not empty

I m trying to iterate over some variables in an ansible role. However, I want to ignore if the var is empty ex:ns3 from below code? I m trying when item length is greater than 0 but it seems not working? any ideas on how to do it?
---
- hosts: localhost
gather_facts: no
vars:
ns1: adm_analytics
ns2: adm_snap
ns3: ""
ns4: adm_eck
tasks:
- name: print namespace loop
with_items:
- "{{ ns1 }}"
- "{{ ns2 }}"
- "{{ ns3 }}"
- "{{ ns4 }}"
include_role:
name: verify_pod_status
vars:
NAMESPACE: "{{ item }}"
when: "{{ item | lenght > 0 }}"
You may have a look into the documentation about Conditionals.
There are some typos in your when clause.
---
- hosts: localhost
gather_facts: false
vars:
ns1: adm_analytics
ns2: adm_snap
ns3: ""
ns4: adm_eck
tasks:
- name: Print namespace loop
debug:
msg: "{{ item }}"
when: item | length > 0
with_items:
- "{{ ns1 }}"
- "{{ ns2 }}"
- "{{ ns3 }}"
- "{{ ns4 }}"
result into an output of
TASK [Print namespace loop] **************
ok: [localhost] => (item=adm_analytics) =>
msg: adm_analytics
ok: [localhost] => (item=adm_snap) =>
msg: adm_snap
ok: [localhost] => (item=adm_eck) =>
msg: adm_eck
when: condition is expanded by default. Fix the syntax
when: item|length > 0
Make your life easier and put the ns* variables into a dictionary. Then you can simply reference the values in the loop instead of listing the variables again. For example, the playbook
shell> cat playbook.yml
- hosts: localhost
gather_facts: no
vars:
ns:
ns1: adm_analytics
ns2: adm_snap
ns3: ""
ns4: adm_eck
tasks:
- name: print namespace loop
include_role:
name: verify_pod_status
loop: "{{ ns.values()|list }}"
vars:
NAMESPACE: "{{ item }}"
when: item|length > 0
and the role
shell> cat roles/verify_pod_status/tasks/main.yml
- debug:
var: NAMESPACE
give (abridged)
TASK [verify_pod_status : debug] ***********************************
skipping: [localhost] => (item=)
TASK [verify_pod_status : debug] ***********************************
ok: [localhost] =>
NAMESPACE: adm_analytics
TASK [verify_pod_status : debug] ***********************************
ok: [localhost] =>
NAMESPACE: adm_snap
TASK [verify_pod_status : debug] ***********************************
ok: [localhost] =>
NAMESPACE: adm_eck

Iterating list of dictionaries with dictionary

I'm having trouble grasping data manipulation in Ansible. Here's the setup:
- hosts: emirr01
gather_facts: no
vars:
setup:
- emirr01: { label: "label1" }
- emirr02: { label: "label2" }
- emirr03: { label: "label3" }
lookup: [ "emirr01", "emirr02"]
use_labels: [ ]
tasks:
- debug: msg="setup={{ setup }}"
- debug: msg="lookup={{ lookup }}"
- debug: msg="item0={{ item.0 }} item1={{ item.1 }}"
when: inventory_hostname == item.1
with_nested:
- "{{ setup }}"
- "{{ lookup }}"
- set_fact:
use_labels: "{{ use_labels + [ item.1.label ] }}"
when: item.0 == item.1.label
with_nested:
- "{{ setup }}"
- "{{ lookup }}"
- debug: msg="use_labels={{ use_labels }}"
What I need is to set a fact use_labels which is a list of labels as defined in setup list for each host found in lookup list. What I expect is a list like this:
[ "label1", "label2" ]
My problem is not being able to "reach" label in a list I get in item.1 which is (example):
item1=[{'emirr02': {'label': 'label2'}}
Here's is the error:
$ ansible-playbook test.yml -l emirr01
PLAY [emirr01] ****************************************************************************************************************************************************************************************************************************************************************************************************************************
TASK [debug] ******************************************************************************************************************************************************************************************************************************************************************************************************************************
ok: [emirr01] =>
msg: 'setup=[{''emirr01'': {''label'': ''label1''}}, {''emirr02'': {''label'': ''label2''}}, {''emirr03'': {''label'': ''label3''}}]'
TASK [debug] ******************************************************************************************************************************************************************************************************************************************************************************************************************************
ok: [emirr01] =>
msg: lookup=['emirr01', 'emirr02']
TASK [debug] ******************************************************************************************************************************************************************************************************************************************************************************************************************************
ok: [emirr01] => (item=[{'emirr01': {'label': 'label1'}}, 'emirr01']) =>
msg: 'item0={''emirr01'': {''label'': ''label1''}} item1=emirr01'
skipping: [emirr01] => (item=[{'emirr01': {'label': 'label1'}}, 'emirr02'])
ok: [emirr01] => (item=[{'emirr02': {'label': 'label2'}}, 'emirr01']) =>
msg: 'item0={''emirr02'': {''label'': ''label2''}} item1=emirr01'
skipping: [emirr01] => (item=[{'emirr02': {'label': 'label2'}}, 'emirr02'])
ok: [emirr01] => (item=[{'emirr03': {'label': 'label3'}}, 'emirr01']) =>
msg: 'item0={''emirr03'': {''label'': ''label3''}} item1=emirr01'
skipping: [emirr01] => (item=[{'emirr03': {'label': 'label3'}}, 'emirr02'])
TASK [set_fact] ***************************************************************************************************************************************************************************************************************************************************************************************************************************
fatal: [emirr01]: FAILED! =>
msg: |-
The conditional check 'item.0 == item.1.label' failed. The error was: error while evaluating conditional (item.0 == item.1.label): 'ansible.utils.unsafe_proxy.AnsibleUnsafeText object' has no attribute 'label'
The error appears to be in '/home/ansible/test.yml': line 21, column 7, but may
be elsewhere in the file depending on the exact syntax problem.
The offending line appears to be:
- set_fact:
^ here
It all boils down to "digging" for a value from a dictionary in a list. Does anyone have an idea of how to reach item.1.label? It's a little bit frustrating since this is trivial in Python or other programming languages but in Ansible... Or maybe my mistake is to use a wrong kind of with_* loop for this purpose?
Q: "Expect a list like this [label1, label2]"
setup:
- emirr01: { label: "label1" }
- emirr02: { label: "label2" }
- emirr03: { label: "label3" }
lookup: [ "emirr01", "emirr02"]
A: This is a typical example of a wrong data structure for this use-case. A list of dictionaries can't be used in mapping the filter extract. Moreover, the keys in the dictionaries don't have to be unique, because they are hidden in the items of the list. The solution is simple with the data in the dictionary
setup:
emirr01: { label: "label1" }
emirr02: { label: "label2" }
emirr03: { label: "label3" }
- set_fact:
use_labels: "{{ lookup|map('extract', setup, 'label')|list }}"
gives
use_labels:
- label1
- label2
One of the options is creating a dictionary from the list first. For example
- set_fact:
setup2: "{{ setup2|default({})|combine(item) }}"
loop: "{{ setup }}"
Then the dictionary setup2 can be used to get the same result
It's possible to filter the list directly. For example, the task below gives the same result too
- set_fact:
use_labels: "{{ setup|map('dict2items')|map('first')|
selectattr('key', 'in', lookup )|
map(attribute='value.label')|
list }}"
Your issue with the with_nested is actually coming from how those "two-lists" loops are working.
When you are using loops like with_nested, the item.0 is the item of the first list, while the item.1 is the item of the second list.
Because lookup is a list and not a list of dictionaries like
lookup:
- label: "emirr01"
- label: "emirr02"
You can use the value of item.1 right away.
So the way to have you issue fixed is to use it this way:
- set_fact:
use_labels: "{{ use_labels + [ item.0[item.1].label ] }}"
when: item.0[item.1].label is defined
with_nested:
- "{{ setup }}"
- "{{ lookup }}"
Here is a playbook demonstrating this:
- hosts: localhost
gather_facts: no
tasks:
- set_fact:
use_labels: "{{ use_labels | default([]) + [ item.0[item.1].label ] }}"
when: item.0[item.1].label is defined
with_nested:
- "{{ setup }}"
- "{{ lookup }}"
vars:
setup:
- emirr01:
label: "label1"
- emirr02:
label: "label2"
- emirr03:
label: "label3"
lookup:
- "emirr01"
- "emirr02"
- debug:
msg: "{{ use_labels }}"
Wich gives:
PLAY [localhost] ***************************************************************************************************
TASK [set_fact] ****************************************************************************************************
ok: [localhost] => (item=[{'emirr01': {'label': 'label1'}}, 'emirr01'])
skipping: [localhost] => (item=[{'emirr01': {'label': 'label1'}}, 'emirr02'])
skipping: [localhost] => (item=[{'emirr02': {'label': 'label2'}}, 'emirr01'])
ok: [localhost] => (item=[{'emirr02': {'label': 'label2'}}, 'emirr02'])
skipping: [localhost] => (item=[{'emirr03': {'label': 'label3'}}, 'emirr01'])
skipping: [localhost] => (item=[{'emirr03': {'label': 'label3'}}, 'emirr02'])
TASK [debug] *******************************************************************************************************
ok: [localhost] => {
"msg": [
"label1",
"label2"
]
}
PLAY RECAP *********************************************************************************************************
localhost : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

Splitting Ansible output into 2 different strings

I have the following on my playbook:
- name: Debug
debug:
msg: "{{ item.stdout }}"
with_items:
- "{{ tapip.results }}"
- "{{ hostname.results }}"
Which returns me this:
TASK [Debug] ***************************************************************************************************************************************************************************************************
ok: [localhost] => (item=None) => {
"msg": "192.168.0.104"
}
ok: [localhost] => (item=None) => {
"msg": "hostname1"
}
I would like to break the hostname and ip into 2 different strings. What is the best way to achieve this?
btw, I have also tried the following:
- name: Debug
debug:
msg: "{{ item.stdout.0 }} {{ item.stdout.1 }}"
with_items:
- "{{ tapip.results }}"
- "{{ hostname.results }}"
Which returns this:
TASK [Debug] ***************************************************************************************************************************************************************************************************
ok: [localhost] => (item=None) => {
"msg": "1"
}
ok: [localhost] => (item=None) => {
"msg": "h"
}
Any ideas would be appreciated
Try this
name: Debug
debug:
msg: "{{ item.stdout }}"
with_items:
tapip.results
hostname.results
name: Debug
debug:
msg: "{{ item.stdout }}"
with_items:
tapip.results
hostname.results

Resources