How to skip null values in ansible loop - loops

I need to skip the value null from a list in ansible loop. I am using the when condition, still the null value gets printed.
Below is my playbook:
- hosts: localhost
vars:
show:
- read
- write
- null
- test
val: []
tasks:
- name: Fact
set_fact:
val: "{{val+[item]}}"
loop: "{{show}}"
when: item != "null"
- name: Print
debug:
msg: "{{val}}"
Output:
TASK [Print] ***
ok: [localhost] => {
"msg": [
"read",
"write",
null,
"test"
]
}
Please advise.

Quoting from YAML 10.2.1.1. Null
"Represents the lack of a value. This is typically bound to a native null-like value (e.g., undef in Perl, None in Python). ..."
There are more options on how to test null.
Compare to Python None
when: item != None
Use Jinja test none
when: item is not none
If you for whatever reason have to compare to a string the Jinja filter string converts null to string 'None'
when: item|string != 'None'
The most efficient way is to remove null values from the list before the iteration
loop: "{{ show|reject('none')|list }}"

I don't know if you want to check if the variable is "null" as a text or if the variable doesn't have a value and it's nullified so I will write both examples :), make null value a string and compare as a string:
- hosts: localhost
vars:
show:
- read
- write
- "null"
- test
val: []
tasks:
- name: Fact
set_fact:
val: "{{val+[item]}}"
loop: "{{show}}"
when: "'{{item}}' != 'null'"
- name: Print
debug:
msg: "{{val}}"
Test if the value is nullified and it really doesn't have any value we will use, is none for that example:
- hosts: localhost
vars:
show:
- read
- write
- null
- test
val: []
tasks:
- name: Fact
set_fact:
val: "{{val+[item]}}"
loop: "{{show}}"
when: item is not none
- name: Print
debug:
msg: "{{val}}"

Related

Ansible - How to save output from loop cycle 1 that I can use in the following loop cycles (2,3...)

I have a task with an API call that returns dictionary as output. From this output I need only an ID. This API call is triggered only once (when item == "1"), but I need it's output available also in the following cycles. Here is the code example I used:
register: output
when: item == "1"
ignore_errors: yes
- debug:
var: output.json.id
- name: show id
debug:
msg: output.json.id is "{{ output.json.id }}"
This is filtered output result I get in 1st cycle:
ok: [localhost] => {
"msg": "output.json.id is \"kjfld4343009394\""
}
In the 2nd cycle API call is skipped (item is not 1) but output from previous cycle is not available any more :
ok: [localhost] => {
"output.json.id": "VARIABLE IS NOT DEFINED!: 'dict object' has no attribute 'json'"
}
BTW In case "debug: var: output.json.id" should be executed just in first cycle, I tried with putting it with conditional item=1 and ignore_errors=yes but that didn't help.
- debug:
var: output.json.id
when: item == "1"
ignore_errors: yes
What can I do to have this output available in other cycles?
Thanks!
I just found solution with set_fact.
- name: Set var id (set_fact)
set_fact:
var_id: "{{ output.json.id }}"
when: item == "1"
- debug:
msg: id is "{{ var_id }}"
When saved like this var_id can be used in the following loop cycles.

Ansible nested loops using value form one loop to another

I am facing issue while writing nested loops in ansible, for below iteration I am getting error
ansible role I am using looks like below
The conditional check 'item[0]['value']['from'] == COMPONENT_NAME' failed. The error was: error while evaluating conditional (item[0]['value']['from'] == COMPONENT_NAME): 'item' is undefined
Role
- name: create config file
template:
src: "{{template_source}}/{{COMPONENT_NAME}}/sessions/{{ item[0]['value']['protocol']}}/{{item[0]['value']['from_template']}}"
dest: "{{dest_folder}}/{{app}}/{{instance_name}}/{{COMPONENT_NAME}}/{{VENUE}}/Config/Repo/session/{{item[0]['value']['protocol']}}/{{item[1]}}.json"
mode: 0755
vars:
dest_file_name: "{{instance_name}}_{{COMPONENT_NAME}}_{{VENUE}}_{{item[0]['key']}}"
mbFlag: "{{item[0]['value']['broker'] | default('False')}}"
when: item[0]['value']['from'] == COMPONENT_NAME
with_nested:
- "{{ connections | dict2items }}"
- "{{myFileList['from_' + COMPONENT_NAME +'_'+ VENUE +'_'+ item[0]['value']['to']].split(',') }}"
yml file having below variable and I am iterating over it.
myFileList:
{
from_et_AG_zp: DevOpsTest_et_zp_AG_zp,
from_et_AG_sSP: 'DevOpsTest_et_sSP_AG_US,DevOpsTest_et_sSP_AG_BA,DevOpsTest_et_sSP_AG_chex',
from_et_AG_esb: DevOpsTest_et_esb_AG_ABC,
from_et_BA_Y_zp: DevOpsTest_et_zp_BA_Y_zp,
from_et_BA_Y_sSP: 'DevOpsTest_et_sSP_BA_Y_US,DevOpsTest_et_sSP_BA_Y_BA,DevOpsTest_et_sSP_BA_Y_chex',
from_et_BA_Y_esb: DevOpsTest_et_esb_BA_Y_ABC,
from_et_BA_zp: DevOpsTest_et_zp_BA_zp,
from_et_BA_sSP: 'DevOpsTest_et_sSP_BA_US,DevOpsTest_et_sSP_BA_BA,DevOpsTest_et_sSP_BA_chex',
from_et_BA_esb: DevOpsTest_et_esb_BA_ABC,
from_zp_zp_esb: DevOpsTest_zp_esb_zp_ABC,
from_SP_SP_esb: DevOpsTest_SP_esb_SP_ABC,
from_SSS_SSS_esb: DevOpsTest_SSS_esb_SSS_ABC,
to_zp_zp_es: DevOpsTest_et_zp_zp_es,
to_sSP_BA_es: DevOpsTest_et_sSP_BA_es,
to_sSP_chex_es: DevOpsTest_et_sSP_chex_es,
to_sSP_US_es: DevOpsTest_et_sSP_US_es,
to_esb_ABC_es: DevOpsTest_et_esb_ABC_es,
to_esb_ABC_zp: DevOpsTest_zp_esb_ABC_zp,
to_esb_ABC_SP: DevOpsTest_SP_esb_ABC_SP,
to_esb_ABC_SSS: DevOpsTest_SSS_esb_ABC_SSS
}
connections:
et_etb:
from: et
to: etb
from_template: et_etb.json
to_template: etb_et.json
protocol: ZERO
After too many permutation and combination, I realize that we can not pass outer loop variable value from outer loop to inner loop, So as work around I write first loop in first role and second loop in another role and call second role from first by iterating over first loop that full fill my ends
---
- name: create config file
include_role:
name: patch_template
vars:
file_key: "{{ 'from_' + COMPONENT_NAME +'_'+ VENUE +'_'+ role_item.value.to }}"
myprotocol: "{{role_item.value.protocol}}"
myTemplate: "{{role_item.value.from_template}}"
subComponent: "{{VENUE}}"
when: role_item.value.from == COMPONENT_NAME
with_items: "{{ connections | dict2items }}"
loop_control:
loop_var: role_item
patch_template role file
- name: patch template
template:
src: "{{template_source}}/{{COMPONENT_NAME}}/sessions/{{myprotocol}}/{{myTemplate}}"
dest: "{{dest_folder}}/{{app}}/{{instance_name}}/{{COMPONENT_NAME}}/{{subComponent}}/Config/Repo/session/{{myprotocol}}/{{item}}.json"
mode: 0755
vars:
dest_file_name: "{{item}}"
with_items:
- "{{ myFileList[file_key].split(',') }}"

Iterate over a dictionary and changing the value of each item based on a condition in a loop in Ansible

I have a file in host_vars folder which looks like this:
tags:
tag1: value1
tag2: value2
tag3: '' <<-- This is an empty value which I would like to fill with some string
Now I wanted to go through each of these items and check if the value is empty and if it is add "NA" as a string to it, I used the following code for this:
- name: Clean any Tag inconsistencies
set_fact:
tags: "{{ item }} + NA"
when: (item | length == 0)
with_items: tags
- debug: var=tags
However, this does not do anything, it just prints the same list when checking the debug. What is wrong with this approach?
Edit:
I changed my code to respect that I am using a dictionary. This is my current approach. It prints out the values with key and name but does not change any item. I also tried using the jinja default filter which did not do anything:
- name: "Clean Tags"
set_fact:
tags: "{{ (tags | default('NA', true)) }}"
loop: "{{ tags | dict2items }}"
What I would like to achieve in the end is that it checks each dict value and if it is empty, it should add the value "NA" to it, so in the end above tags dictionary will look like this:
tags:
tag1: value1
tag2: value2
tag3: 'NA'
The play below
- hosts: localhost
vars:
my_tags:
tag1: value1
tag2: ''
tag3: ''
tasks:
- set_fact:
my_tags: "{{ my_tags|combine(dict(my_empty_tags|product(['NA']))) }}"
vars:
my_empty_tags: "{{ my_tags|dict2items|json_query(query) }}"
query: "[?value == ''].key"
- debug:
var: my_tags
gives (abridged)
my_tags:
tag1: value1
tag2: NA
tag3: NA
The same result can be achieved without json_query. Replace the task vars
vars:
my_empty_tags: "{{ my_tags|dict2items|
selectattr('value', 'eq', '')|
map(attribute='key')|
list }}"
tags is a playbook's keyword. You should have seen:
[WARNING]: Found variable using reserved name: tags
Q: "What happens in the set_fact section?"
A: Decompose the filter. First, create the product of the empty tags and the string 'NA'
- debug:
msg: "{{ my_empty_tags|product(['NA'])|list }}"
gives
msg:
- - tag2
- NA
- - tag3
- NA
Then create dictionaries from the items in the list
- debug:
msg: "{{ dict(my_empty_tags|product(['NA'])) }}"
gives
msg:
tag2: NA
tag3: NA
The last step in the filter is to combine the dictionaries.

Ansible Debug array of object for specific value using Loop?

I have the following array of record with the value:
myRecord.record.0.number = "Number 0"
myRecord.record.1.number = "Number 1"
myRecord.record.2.number = "Number 2"
myRecord.record.3.number = "Number 3"
How to create a playbook to debug the above value using loop dynamically/for every array?
for other common languange it can be done as follows:
for(int i = 0; i < myRecord.length(); i++)
{
echo myRecord.record.[i].number
}
for the repeating task of the playbook, it will looks like this:
---
- hosts: localhost
name: Array of Object
gather_facts: false
tasks:
- name: using debugMsg
debug:
msg:
- "{{ myRecord.record.0.number }}"
- "{{ myRecord.record.1.number }}"
- "{{ myRecord.record.2.number }}"
- "{{ myRecord.record.3.number }}"
I have figured out how to do this. Basically I just need to use loop_control to filter which specific value I need. Here is the playbook:
---
- hosts: localhost
name: Array of Object
gather_facts: false
tasks:
- name: using loop_control
debug:
msg: "{{ item.number }}"
with_items:
- "{{ myRecord.record }}" #this will become 'item'
loop_control:
label: "{{ item.number }}" #filter to display the value of number only
There are two methods. The first method uses the with_* keyword, and depends on the Lookup plugin. The second method uses loop keyword, which is equivalent to 'with_' + 'list lookup plugin' (so you get 'with_list').
Now, assuming your data structure looks like this:
---
# vars file for print_variable_from_list
myRecord:
record:
- number: "Number 0"
- number: "Number 1"
- number: "Number 2"
- number: "Number 3"
Every number is indexable with the "number" key.
- name: loop through myRecord
debug:
msg: "{{ item.number }}"
loop: "{{ myRecord.record }}"
Kindly refer to other posts for more complex queries.

Ansible with items in range

I would like to achieve something like this with ansible
- debug:
msg: "{{ item }}"
with_items:
- "0"
- "1"
But to be generated from a range(2) instead of having hardcoded the iterations. How would yo do that?
- debug:
var: item
with_sequence: 0-1
or
with_sequence: start=0 end=1
or
with_sequence: start=0 count=2
Pay attention that sequences are string values, not integers (you can cast with item|int)
Reference: Looping over Integer Sequences
Because with_sequence is replaced by loop and the range function you can also use loop with range function like this example:
- hosts: localhost
tasks:
- name: loop with range functions
ansible.builtin.debug:
msg: "{{ 'number: %s' | format(item) }}"
loop: "{{ range(0, 2, 1)|list }}"

Resources