How to exit/skip loop in Ansible for include_task - loops

Below is my ansible playbook that checks for file/dir permission and should exit the loop (BUT not stop the play) with the details of the file/dir not meeting the permission check (others should not have execute, or write permissions)
---
- name: "Play 1"
hosts: localhost
serial: true
any_errors_fatal: false
tasks:
- name: Checking permissions with include task
include_tasks: "{{ playbook_dir }}/internalcheckperm.yml"
loop:
- "~/.ssh/known_hosts"
- "~/.ssh/authorized_keys"
- "~/"
- "~/.ssh"
when: permchangeflag | default(false) == false
- name: Send Email
debug:
msg: "Sending out email and other stuff below"
cat internalcheckperm.yml
- stat:
path: "{{ item }}"
register: fp
- set_fact:
permchangeflag: true
when: fp.stat.exists == true and fp.stat.xoth == true and fp.stat.woth == true and fp.stat.isdir == false
- debug:
msg: "permchangeflag value: {{ permchangeflag | default(false) }} on {{ inventory_hostname }} for file {{ item }}"
when: fp.stat.exists == true
Run as ansible-playbook test.yml
Requirement: if ~/.ssh/known_hosts is permission 777 then the condition when: permchangeflag | default(false) == false should fail and loop should not run (skip) for the remaining files/dir in the loop.
However, despite permchangeflag becoming true as evident from the output below the loop continues to run for all the files.
TASK [debug] *******************************************************************************************************************
Tuesday 15 November 2022 08:12:17 -0600 (0:00:00.031) 0:00:02.454 ******
ok: [myremote7] => {
"msg": "permchangeflag value: True on myremote7 for file ~/.ssh/known_hosts and when not True"
From the output it seems the when condition does not kick for one element of the loop at a time but all at once which is something I don't want.
TASK [Checking permissions with include task] ******************************************************************************************************************************************************************
included: /root/internalcheckperm.yml for localhost => (item=~/.ssh/known_hosts)
included: /root/internalcheckperm.yml for localhost => (item=~/.ssh/authorized_keys)
included: /root/internalcheckperm.yml for localhost => (item=~/)
included: /root/internalcheckperm.yml for localhost => (item=~/.ssh)
Can you please suggest how can i break/skip the loop as soon as the first when permission condition is not met?

Related

Looping or repeating a group of tasks until success

I currently have a playbook which includes a task file. In that task file, I would like to check for a condition. If the exit code of that condition is not equal to 0, all steps in the task file should be repeated. I have tried a few variations with block and loops but I have not figured out a way to make it do what I described above.
Currently I have something like this:
tasks:
- name: call task file
include: task_file.yml
In task_file.yml,
- name: perform an operations
shell: do A
- name: check
shell: do B
register: result
Next, I would like to tell the main playbook that if result.rc != 0, please repeat task_file.yml until result.rc == 0.
Any pointers would be greatly appreciated
The playbook seems to end no matter what the exit code.
There's no direct way to reach your goal as include_tasks does not support the retry/until loop keywords.
There is an attempt to circumvent that limitation by teaching ansible a new loop_control.until keyword for loops which could be used for includes. Unfortunately, the pull request has been opened since Sep. 2019 and has still not reached a realease.
The good news is you can implement that with some work by using include recursion with a block. The below example is largely inspired by a blog article on https://dev.to. I adapted to the current context, fixed some good practice and added features like flexible retries number and delay between retries. Here we go:
The tasks to be retried go in task_file.yml
---
- name: group of tasks to repeat until success
block:
- name: increment attempts counter
ansible.builtin.set_fact:
attempt_number: "{{ attempt_number | d(0) | int + 1 }}"
- name: dummy task
ansible.builtin.debug:
msg: "I'm a dummy task"
- name: task to check for success.
# Just for the example. Will return success on attempt number 3
ansible.builtin.command: "[ {{ attempt_number | int }} -eq 3 ]"
changed_when: false
rescue:
- name: "Fail if we reached the max of {{ max_attempts | d(3) }} attempts"
# Default will be 3 attempts if max_attempts is not passed as a parameter
ansible.builtin.fail:
msg: Maximum number of attempts reached
when: attempt_number | int == max_attempts | int | d(3)
- ansible.builtin.debug:
msg: "group of tasks failed on attempt {{ attempt_number }}. Retrying"
- name: add delay if needed
# no delay if retry_delay is not passed as parameter
ansible.builtin.wait_for:
timeout: "{{ retry_delay | int | d(omit) }}"
when: retry_delay is defined
# include ourselves to retry.
- ansible.builtin.include_tasks: task_file.yml
As you can see, the file includes itself again in case of failure until success of max attempts is reached. Also, note that a retry will happen if any task fails inside the block, not only the last one. If you have a more complex scenario, you can implement more checks for fail/not fail in the rescue section an even add an always section if needed. See anbile blocks
Then you can call this file from your playbook:
---
- hosts: localhost
gather_facts: false
tasks:
- name: Include tasks to retry. 7 attempts max with 1 second delay
ansible.builtin.include_tasks: task_file.yml
vars:
max_attempts: 7
retry_delay: 1
Playing this example succeeds on third attempt as hardcoded and expected. (You can play around with the parameters to test a fail scenario)
$ ansible-playbook playbook.yml
PLAY [localhost] ***********************************************************************************************************************************************************************************************************************
TASK [Include tasks to retry] **********************************************************************************************************************************************************************************************************
included: /tmp/toto/task_file.yml for localhost
TASK [increment attempts counter] ******************************************************************************************************************************************************************************************************
ok: [localhost]
TASK [dummy task] **********************************************************************************************************************************************************************************************************************
ok: [localhost] => {
"msg": "I'm a dummy task"
}
TASK [task to check for success.] *******************************************************************************************************************************************************************************************************
fatal: [localhost]: FAILED! => {"changed": false, "cmd": ["[", "1", "-eq", "3", "]"], "delta": "0:00:00.002104", "end": "2022-12-08 14:16:27.850578", "msg": "non-zero return code", "rc": 1, "start": "2022-12-08 14:16:27.848474", "stderr": "", "stderr_lines": [], "stdout": "", "stdout_lines": []}
TASK [Fail if we reached the max of 7 attempts] ****************************************************************************************************************************************************************************************
skipping: [localhost]
TASK [ansible.builtin.debug] ***********************************************************************************************************************************************************************************************************
ok: [localhost] => {
"msg": "group of tasks failed on attempt 1. Retrying"
}
TASK [add delay if needed] *************************************************************************************************************************************************************************************************************
ok: [localhost]
TASK [ansible.builtin.include_tasks] ***************************************************************************************************************************************************************************************************
included: /tmp/toto/task_file.yml for localhost
TASK [increment attempts counter] ******************************************************************************************************************************************************************************************************
ok: [localhost]
TASK [dummy task] **********************************************************************************************************************************************************************************************************************
ok: [localhost] => {
"msg": "I'm a dummy task"
}
TASK [task to check for success.] *******************************************************************************************************************************************************************************************************
fatal: [localhost]: FAILED! => {"changed": false, "cmd": ["[", "2", "-eq", "3", "]"], "delta": "0:00:00.004009", "end": "2022-12-08 14:16:29.496509", "msg": "non-zero return code", "rc": 1, "start": "2022-12-08 14:16:29.492500", "stderr": "", "stderr_lines": [], "stdout": "", "stdout_lines": []}
TASK [Fail if we reached the max of 7 attempts] ****************************************************************************************************************************************************************************************
skipping: [localhost]
TASK [ansible.builtin.debug] ***********************************************************************************************************************************************************************************************************
ok: [localhost] => {
"msg": "group of tasks failed on attempt 2. Retrying"
}
TASK [add delay if needed] *************************************************************************************************************************************************************************************************************
ok: [localhost]
TASK [ansible.builtin.include_tasks] ***************************************************************************************************************************************************************************************************
included: /tmp/toto/task_file.yml for localhost
TASK [increment attempts counter] ******************************************************************************************************************************************************************************************************
ok: [localhost]
TASK [dummy task] **********************************************************************************************************************************************************************************************************************
ok: [localhost] => {
"msg": "I'm a dummy task"
}
TASK [task to check for success.] *******************************************************************************************************************************************************************************************************
ok: [localhost]
PLAY RECAP *****************************************************************************************************************************************************************************************************************************
localhost : ok=14 changed=0 unreachable=0 failed=0 skipped=2 rescued=2 ignored=0

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(',') }}"

How to skip null values in ansible loop

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}}"

traversal through the loop in ansible playbook

I am newbie to ansible and trying to write my first playbook.
- name: create volume
volume:
state: present
username: "{{ username }}"
password: "{{ password }}"
hostname: "{{ inventory_hostname }}"
vserver: "{{item[0]}}"
name: "{{item[1]}}"
aggregate_name: "{{output}}"
with_nested:
- [ 'vs10' , 'vs11' ]
- [ 'vol1' , 'vol2', 'vol3' , 'vol4' ,'vol5', ''vol6']
connection: local
Actual output:
vs10-vol1 vol2 vol3 vol4
vs11- vol1 vol2 vol3 vol4
Expected output:
vs10-vol1, vol3 vol5
vs11-vol2, vol4 vol6
This would probably work. I'm basically looping the task against volumes and calculating the vserver in the task.
- name: create volume
volume:
state: present
username: "{{ username }}"
password: "{{ password }}"
hostname: "{{ inventory_hostname }}"
# Calculate which vserver to use based on 'current volume index in the volumes list' and 'length of vservers list'.
# The logic uses modulus to try and distribute volumes across given vcenters
vserver: "{{vservers[(current_index % (vservers|length))]}}"
# Name is item itself because you are looping volumes
name: "{{item}}"
aggregate_name: "{{output}}"
# Loop the volumes
loop: [ 'vol1' , 'vol2', 'vol3' , 'vol4' ,'vol5', 'vol6']
# This is make a loop_control variable available. This will give us 'current_index'
loop_control:
index_var: current_index
# Vservers are defined here
vars:
vservers: [ 'vs10' , 'vs11' ]

Resources