Ansible fail task but continue running - ansible

I'm currently writing a bunch of checks for Ansible.
It involves contacting remote machines and performing a check.
Based on the result of this check, I make a decision whether it failed or not.
This is the important bit: the task itself never fails. It merely returns a message. I register the result and then analyze it. And based on that I decide if it's a failure or not.
The thing is, I want to add a flag that allows tests to keep running instead of failing.
So the code looks like this:
- name: Check fail.
fail:
msg: "Test failed"
when: "failfast_flag and <the actual check>"
The problem there is, if I make failfast_flag false, it doesn't output red anymore.
I want it to continue with the next tests, in that case, but I also want it to color red, indicating it's an error/fail.
How do I accomplish this?
EDIT: Thanks for the suggestions, I'll give them a try in a bit.

Not sure i fully understood your question, but you could use a - block and rescue structure in order to treat the failures and continue the playbook in failure scenarios:
example:
- block:
- name: Check fail.
fail:
msg: "Test failed"
when: "failfast_flag and <the actual check>"
rescue:
- name: do somthing when the task above fails
command: <do somthing>

This is a combination of #edrupado's answer and #Jack's comment.
The idea is to move the error up to the task where you register the value and use a rescue block to fail with an informative message, skipping the fail with a flag.
I have no idea what your actual task to collect data is. I used a dummy example cheking for the presence of a dir/file. You should be able to adapt to you actual scenario.
---
- name: Dummy POC just to test feasability
hosts: localhost
gather_facts: false
tasks:
- block:
- name: Make sure /whatever dir exists
stat:
path: /whatever
register: whatever
failed_when: not whatever.stat.exists | bool
- debug:
msg: "/whatever exists: Good !"
rescue:
- fail:
msg: /whatever dir must exist.
ignore_errors: "{{ ignore_flag | default(false) | bool }}"
- block:
- name: Make sure /tmp dir exists
stat:
path: /tmp
register: tmpdir
failed_when: not tmpdir.stat.exists | bool
- debug:
msg: "/tmp exists: Good !"
rescue:
- fail:
msg: /tmp dir must exist.
ignore_errors: "{{ ignore_flag | default(false) | bool }}"
Which gives:
$ ansible-playbook /tmp/test.yml
PLAY [Dummy POC just to test feasability] *************************************************************************************************************************************************************************
TASK [Make sure /whatever dir exists] *****************************************************************************************************************************************************************************
fatal: [localhost]: FAILED! => {"changed": false, "failed_when_result": true, "stat": {"exists": false}}
TASK [fail] *******************************************************************************************************************************************************************************************************
fatal: [localhost]: FAILED! => {"changed": false, "msg": "/whatever dir must exist."}
PLAY RECAP ********************************************************************************************************************************************************************************************************
localhost : ok=0 changed=0 unreachable=0 failed=1 skipped=0 rescued=1 ignored=0
$ ansible-playbook /tmp/test.yml -e ignore_flag=true
PLAY [Dummy POC just to test feasability] *************************************************************************************************************************************************************************
TASK [Make sure /whatever dir exists] *****************************************************************************************************************************************************************************
fatal: [localhost]: FAILED! => {"changed": false, "failed_when_result": true, "stat": {"exists": false}}
TASK [fail] *******************************************************************************************************************************************************************************************************
fatal: [localhost]: FAILED! => {"changed": false, "msg": "/whatever dir must exist."}
...ignoring
TASK [Make sure /tmp dir exists] **********************************************************************************************************************************************************************************
ok: [localhost]
TASK [debug] ******************************************************************************************************************************************************************************************************
ok: [localhost] => {
"msg": "/tmp exists: Good !"
}
PLAY RECAP ********************************************************************************************************************************************************************************************************
localhost : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=1 ignored=1

Related

Ansible command module expecting nonzero result

I am trying to use the Ansible command module to get information from my inventory, but I am expecting the servers to return a non-zero value as a result. The non-zero values are causing the inventory to report as fail.
I was expecting the following sequence to fail when a certain group of process names are present on any of the inventory.
- name: Make sure all expected processes are down
tags: ensure_processes_down
block:
- name: Find list of unexpected processes
command:
argv:
- /usr/bin/pgrep
- --list-name
- unexpected_processes_pattern_here
register: pgrep_status
- name: Ensure no processes were found
# pgrep returns 0 if matches were found
assert:
that: pgrep_status.rc != 0
msg: 'Cannot run while these these processes are up: {{ pgrep_status.stdout }}'
I am new to Ansible and suspect that I am not using the command module correctly.
Is there another way to run a command where the expected return code is non-zero?
If the command in a command task fails, then the entire task fails, which means no more tasks will be executed on that particular host. Your second task in which you check pgrep_status will never run. You have a couple options here.
You can set ignore_errors: true, which will ignore the error condition and move on to the next task. E.g:
- hosts: localhost
gather_facts: false
tasks:
- command:
argv:
- false
register: status
ignore_errors: true
- assert:
that: status.rc != 0
Which will produce this result:
PLAY [localhost] ***************************************************************
TASK [command] *****************************************************************
fatal: [localhost]: FAILED! => {"changed": false, "cmd": "False", "msg": "[Errno 2] No such file or directory: b'False'", "rc": 2, "stderr": "", "stderr_lines": [], "stdout": "", "stdout_lines": []}
...ignoring
TASK [assert] ******************************************************************
ok: [localhost] => {
"changed": false,
"msg": "All assertions passed"
}
PLAY RECAP *********************************************************************
localhost : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=1
However, rather than running the command task and then checking the result in a second task, you can set a failed_when condition on the command task:
- name: Find list of unexpected processes
command:
argv:
- /usr/bin/pgrep
- --list-name
- unexpected_processes_pattern_here
register: pgrep_status
failed_when: pgrep_status.rc == 0
This command will only fail when the command returns successfully (i.e.,with an exit code of 0).
See the "Defining failure" section of the docs for more information.

How to ignore specific errors in an Ansible task

If have an Ansible task, that can fails sometimes, due to some error during the creation of an user account. Especially if the user account is already in use and the user is logged in. If the task fails with a specific error message, like "user account in use" the play must continue. There is no need to fail then, but only on predefined error messages. The task looks like this.
- name: modify user
user:
state: "{{ user.state | default('present') }}"
name: "{{ user.name }}"
home: "{{ user_base_path }}/{{ user.name }}"
createhome: true
Since it's not a shell command, I cannot simply register a var and check the output of .rc. Also I don't get stderr or stdout, when i register a var and print it in debug mode. That was my first approach on check for the error message. I am running out of ideas, how to filter for a specific error and passing the task, but failing on everything else. ignore_errors: yes is not a good solution, because the task should fail in some cases.
As per ansible doc we get stdout and stderr as return fields.
I would suggest to use flag ignore_errors: yes and catch the return as per this example
---
- hosts: localhost
vars:
user:
name: yash
user_base_path: /tmp
tasks:
- name: modify user
user:
state: "{{ user.state | default('present') }}"
name: "{{ user.name }}"
home: "{{ user_base_path }}/{{ user.name }}"
createhome: true
register: user_status
ignore_errors: yes
- name: stdout_test
debug:
msg: "{{ user_status.err }}"
- name: Fail on not valid
fail:
msg: failed
when: '"user account in use" not in user_status.err'
Output:
PLAY [localhost] *************************************************************************************************************************************************************************************
TASK [Gathering Facts] *******************************************************************************************************************************************************************************
ok: [localhost]
TASK [modify user] ***********************************************************************************************************************************************************************************
fatal: [localhost]: FAILED! => {"changed": false, "err": "<main> attribute status: eDSPermissionError\n<dscl_cmd> DS Error: -14120 (eDSPermissionError)\n", "msg": "Cannot create user \"yash\".", "out": "", "rc": 40}
...ignoring
TASK [stdout_test] ***********************************************************************************************************************************************************************************
ok: [localhost] => {
"msg": "<main> attribute status: eDSPermissionError\n<dscl_cmd> DS Error: -14120 (eDSPermissionError)\n"
}
TASK [Fail on not valid] *****************************************************************************************************************************************************************************
fatal: [localhost]: FAILED! => {"changed": false, "msg": "failed"}
PLAY RECAP *******************************************************************************************************************************************************************************************
localhost : ok=3 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=1
You can use when, save the return value by the register and set_fact and then bet what will happen according to that value.

How to make `with_fileglob` conditional

I am testing with this:
#test.yml
- hosts: localhost
tasks:
- set_fact:
host_name: "project-specific"
- stat:
path: /home/user/work/infrastructure/{{ host_name }}
register: file_exists
- debug:
var: file_exists
- name: Dont create a file
template:
src: "{{item}}"
dest: /home/user/work/infrastructure/project-specific-2/
mode: 0755
with_fileglob: /home/user/work/infrastructure/{{ host_name }}/*
when: file_exists
I want to copy all the files in project-specific to project-specific-2 (as a demonstration), but only if the project-specific directory actually exists. The project-specific directory does not exist, so it should do skip this step.
This is the output:
user#laptop:~/work/infrastructure/ansible$ ansible-playbook test.yml
PLAY [localhost] *************************************************************************************************************************************
TASK [Gathering Facts] *******************************************************************************************************************************
ok: [localhost]
TASK [set_fact] **************************************************************************************************************************************
ok: [localhost]
TASK [stat] ******************************************************************************************************************************************
ok: [localhost]
TASK [debug] *****************************************************************************************************************************************
ok: [localhost] => {
"file_exists": {
"changed": false,
"failed": false,
"stat": {
"exists": false
}
}
}
TASK [Dont create a file] ****************************************************************************************************************************
[WARNING]: Unable to find '/home/user/work/infrastructure/project-specific' in expected paths (use -vvvvv to see paths)
PLAY RECAP *******************************************************************************************************************************************
localhost : ok=4 changed=0 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0
https://github.com/ansible/ansible/issues/13296
states that with_ clauses get executed before when. How do I make it so that it skips this step if the path does not exist? Or is it fine to leave it to 'execute' with a warning as no files get copied anyway?
I think you are stuck with this one. As noted in the documentation the when statement is evaluated separately for each item in the loop.
When combining Conditionals with a loop, the when: statement is processed separately for each item.
So what you're seeing is expected behavior.
The end result in your case is that the task is actually skipped when the directory can't be found, no action is performed. It just does this while printing a warning.
Bit late but the question is still relevant. My solution to this problem is to put with_fileglob tasks in a separate file and put the conditional on an include_tasks statement. E.g.
- name: Do fileglob operation
include_tasks: fileglob.yml
when:
- dir_info.stat.exists
- dir_info.stat.isdir

How do I handle rollback in case of failure using handlers in Ansible?

I wrote below yml file which will install the SSM and cloudwatch agent but I want to rollback the installation in case of any failures during the installation. I tried use FAIL but not working..Please advise..
---
# tasks file for SSMAgnetInstall
- name: status check
command: systemctl status amazon-ssm-agent
register: s_status
- debug:
msg: "{{ s_status }}"
- name: Get CPU architecture
command: getconf LONG_BIT
register: cpu_arch
changed_when: False
check_mode: no
when: s_status.stdout == ""
ignore_errors: true
- name: Install rpm file for Redhat Family (Amazon Linux, RHEL, and CentOS) 32/64-bit
yum:
name: "https://s3.amazonaws.com/ec2-downloads-windows/SSMAgent/latest/linux_386/amazon-ssm-agent.rpm"
state: present
when: s_status.stdout == ""
become: yes
ignore_errors: true
- name: cloud status check
command: systemctl status amazon-cloudwatch-agent
register: cld_status
become: yes
- debug:
msg: "{{ cld_status }}"
- name: Register to cloud watch service
become: yes
become_user: root
service:
name: amazon-ssm-agent
enabled: yes
state: started
- name: copy the output to a local file
copy:
content: "{{ myshell_output.stdout }}"
dest: "/home/ansible/rama/output.txt"
delegate_to: localhost
You should have a look at the the documentation on blocks, more specifically the error handling part. This is the general idea with an oversimplified example, you will have to adapt to your specific case.
The test.yml playbook
---
- hosts: localhost
gather_facts: false
tasks:
- block:
- name: I am a task that can fail
debug:
msg: "I {{ gen_fail | default(false) | bool | ternary('failed', 'succeeded') }}"
failed_when: gen_fail | default(false) | bool
- name: I am a task that will never fail
debug:
msg: I succeeded
rescue:
- name: I am a task in a block played when a failure happens
debug:
msg: rescue task
always:
- name: I am a task always played whatever happens
debug:
msg: always task
Played normally (no fail)
$ ansible-playbook test.yml
PLAY [localhost] ************************************************************************
TASK [I am a task that can fail] ********************************************************
ok: [localhost] => {
"msg": "I succeeded"
}
TASK [I am a task that will never fail] *************************************************
ok: [localhost] => {
"msg": "I succeeded"
}
TASK [I am a task always played whatever happens] ***************************************
ok: [localhost] => {
"msg": "always task"
}
PLAY RECAP ******************************************************************************
localhost : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Played forcing a fail
$ ansible-playbook test.yml -e gen_fail=true
PLAY [localhost] ************************************************************************
TASK [I am a task that can fail] ********************************************************
fatal: [localhost]: FAILED! => {
"msg": "I failed"
}
TASK [I am a task in a block played when a failure happens] *****************************
ok: [localhost] => {
"msg": "rescue task"
}
TASK [I am a task always played whatever happens] ***************************************
ok: [localhost] => {
"msg": "always task"
}
PLAY RECAP ******************************************************************************
localhost : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=1 ignored=0

Ansible: move on to the next task if the task is completed on one host

In ansible what i require is to check for a file is available on two hosts. But if the file is available on even one host i need to cancel the task on other host and move onto the next task. The reason why i require this is because a the next task can only be done if that particular file is available and that file can be randomly written to any of the hosts.
The following play does exactly what you want:
---
- hosts:
- server1
- server2
gather_facts: False
vars:
file_name: 'foo.bar'
tasks:
- name: wait for file
wait_for:
path: '{{ file_name }}'
state: present
timeout: 30
ignore_errors: True
- name: stat
stat:
path: '{{ file_name }}'
register: result
- name: next
debug:
msg: "File {{ file_name }} available on {{ ansible_host }}"
when: result.stat.isreg is defined and result.stat.isreg
The output is:
PLAY [server1,server2] *********************************************************
TASK [wait for file] ***********************************************************
ok: [server1]
fatal: [server2]: FAILED! => {"changed": false, "elapsed": 3, "msg": "Timeout when waiting for file foo.bar"}
...ignoring
TASK [stat] ********************************************************************
ok: [server1]
ok: [server2]
TASK [next] ********************************************************************
skipping: [server2]
ok: [server1] => {
"msg": "File foo.bar available on server1"
}
PLAY RECAP *********************************************************************
server1 : ok=3 changed=0 unreachable=0 failed=0
server2 : ok=0 changed=0 unreachable=0 failed=0
You can use the stat module to check the status like below and for also you can add the serial:1 below hosts: in your playbook
stat:
path: /path/to/something
register: p
debug:
msg: "Path exists and is a directory"
when: p.stat.isdir is defined and p.stat.isdir
https://docs.ansible.com/ansible/latest/modules/stat_module.html for more details

Resources