Ansible command module expecting nonzero result - ansible

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.

Related

How do I add a custom message if a task does not meet the 'when' condition and is skipped?

In this task, I am searching for a file. If there is no file, the task will be skipped.
My question is, how do I write a custom message to output when the task is skipped?
- name: Search for files
win_find:
paths: C:\dataset\
register: data
- debug:
msg: "Data exists"
when: data | json_query('files[*].exists')
- name: set_fact
set_fact:
exists: "{{ data | json_query('files[*].exists') }}"
In a different playbook:
- name: Run if file exists
block:
- name: read content from file
win_shell: C:\day.txt
register: day
when: hostvars['10.11.18.190']['exists']
- name: print message
debug:
msg: "{{ hostvars['10.12.201.20']['day'] }}"
As there is no file, the task is skipped:
TASK [Run if file exists] *********************
skipping: [10.11.18.190] => {
"changed": false,
"skip_reason": "Conditional result was False"
}
TASK [print message] **************************************************************************************
ok: [10.11.18.190] => {
"msg": {
"changed": false,
"skip_reason": "Conditional result was False",
"skipped": true
}
}
As you can see from the output, the variable hostvars['10.12.201.20']['day'] is showing "changed": false, skip_reason, etc. But I do not want this, I want it to output a message like, "File does not exist".
How can I create a custom message for this variable hostvars['10.12.201.20']['day']?
A: Use the 'Do It Yourself' callback plugin community.general.diy. See
shell> ansible-doc -t callback community.general.diy
(or the online documentation)
For example, if the file /tmp/day.txt does not exist the playbook
shell> cat pb.yml
- hosts: localhost
tasks:
- stat:
path: /tmp/day.txt
register: stat_day
- command: cat /tmp/day.txt
register: day
when: stat_day.stat.exists
vars:
ansible_callback_diy_runner_on_skipped_msg: |
skipping: [{{ inventory_hostname }}]
msg: File does not exist.
ansible_callback_diy_runner_on_skipped_msg_color: green
will display the custom message
shell> ANSIBLE_STDOUT_CALLBACK=community.general.diy ansible-playbook pb.yml
PLAY [localhost] *****************************************************************************
TASK [stat] **********************************************************************************
ok: [localhost]
TASK [command] *******************************************************************************
skipping: [localhost]
msg: File does not exist.
PLAY RECAP ***********************************************************************************
localhost: ok=1 changed=0 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0
Optionally, you can use the block/rescue construct. See Handling errors with blocks.
For example, in Linux (I don't have access to Windows atm) when you try to access a nonexistent file by the module command on the localhost
- command: cat /tmp/day.txt
register: day
the command will fail
fatal: [localhost]: FAILED! => changed=true
ansible_facts:
discovered_interpreter_python: /usr/bin/python3
cmd:
- cat
- /tmp/day.txt
delta: '0:00:00.010884'
end: '2023-02-14 07:21:50.664051'
msg: non-zero return code
rc: 1
start: '2023-02-14 07:21:50.653167'
stderr: 'cat: /tmp/day.txt: No such file or directory'
stderr_lines: <omitted>
stdout: ''
stdout_lines: <omitted>
Put the command into the block and use the section rescue
- block:
- command: cat /tmp/day.txt
register: day
- debug:
var: day.stdout
rescue:
- debug:
var: ansible_failed_result
Now, if the command fails you'll see
ansible_failed_result:
ansible_facts:
discovered_interpreter_python: /usr/bin/python3
changed: true
cmd:
- cat
- /tmp/day.txt
delta: '0:00:01.007972'
end: '2023-02-14 07:24:43.791343'
failed: true
invocation:
module_args:
_raw_params: cat /tmp/day.txt
_uses_shell: false
argv: null
chdir: null
creates: null
executable: null
removes: null
stdin: null
stdin_add_newline: true
strip_empty_ends: true
msg: non-zero return code
rc: 1
start: '2023-02-14 07:24:42.783371'
stderr: 'cat: /tmp/day.txt: No such file or directory'
stderr_lines:
- 'cat: /tmp/day.txt: No such file or directory'
stdout: ''
stdout_lines: []
You can reduce the output to the standard error
rescue:
- debug:
var: ansible_failed_result.stderr
If the file exists
shell> echo 'This is the content of /tmp/day.txt' > /tmp/day.txt
The next task in the block will display the standard output of the command
day.stdout: This is the content of /tmp/day.txt
Of course, there will be differences in the error messages among the operating systems. Take a look at the data you get and fit the messages to your needs.
I see not direct solution for this problem. But you could do the following:
disable the output of skipped tasks at all, with
[defaults]
display_skipped_hosts = true
In the ansbile.cfg
For details see https://docs.ansible.com/ansible/latest/collections/ansible/builtin/default_callback.html
create a debug task with the message you want to display with an opposite when condition. So it will only run if the other task is skipped.

Cisco Ansible ios_command module failing Task if one of the command in list of commands not working

I am trying to save the output of commands using ansible ios_command module. This work well when all the commands are executed and cisco device doesnt throw error. But as soon as if i have any command which doesnt run on that device, playbook fails and doesnt save the ouput of rest of the commans which executed successfully. If command fail, i would like to have my task running without fail.
---
- name: Backup Play
hosts: all
gather_facts: false
connection: network_cli
tasks:
- name: Execute Commands on the device
ios_command:
commands:
- show run
- show version
- show inventory
- show ip bgp summary
ignore_errors: true
register: config
- name: save output to file
copy:
content: "{{ config.stdout | replace('\\n', '\n') }}"
dest: "/DEVICE_CONFIGS/CISCO/{{ ansible_host }}.cfg"
While executing the above play, i am getting below error
server01 playbooks]$ ansible-playbook cisco-backups.yml --limit Site_01 --ask-vault-pass -e 'ansible_python_interpreter=/usr/bin/python3'
Vault password:
PLAY [Backup Play] ***********************************************************************************************************************
TASK [Execute Commands on the device] *********************************************************************************************************************************
fatal: [Switch_01]: FAILED! => {"changed": false, "msg": "show ip bgp summary\r\nshow ip bgp summary\r\n ^\r\n% Invalid input detected at '^' marker.\r\n\r\nSwitch_01#"}
...ignoring
TASK [save output to file] *****************************************************************************************************************
fatal: [Switch_01]: FAILED! => {"msg": "The task includes an option with an undefined variable. The error was: 'dict object' has no attribute 'stdout'\n\nThe error appears to be in '/etc/ansible/playbooks/cisco-backups.yml': line 62, column 7, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n\n - name: save output to /etc/ansible/backups\n ^ here\n"}
PLAY RECAP *************************************************************************************************************************************************
Switch_01 : ok=3 changed=2 unreachable=0 failed=1 skipped=0 rescued=0 ignored=1
Output of Config when debugged:
TASK [Print output] ************************************************************************************************
task path: /etc/ansible/playbooks/cisco-backups.yml:64
ok: [switch-01] => {
"config": {
"changed": false,
"exception": " File \"/tmp/ansible_ios_command_payload_gluy0xuc/ansible_ios_command_payload.zip/ansible/module_utils/network/ios/ios.py\", line 135, in run_commands\n return connection.run_commands(commands=commands, check_rc=check_rc)\n File \"/tmp/ansible_ios_command_payload_gluy0xuc/ansible_ios_command_payload.zip/ansible/module_utils/connection.py\", line 190, in __rpc__\n raise ConnectionError(to_text(msg, errors='surrogate_then_replace'), code=code)\n",
"failed": true,
"msg": "show switch\r\nshow switch\r\n ^\r\n% Invalid input detected at '^' marker.\r\n\r\nswitch-01#"
}
}

Ansible fail task but continue running

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

Print custom message from until loop in ansible

I am trying to run a command multiple times and check if the output contains some string in it("hi"). I am purposefully simulating failure and expecting the until loop to fail. Everything is good till this point.
Now, I need to have some custom message stating why the until loop or the task failed. For Example: "Your command failed to print hi"
So Question is, How can I print custom message from until loop if the loop failed to pass withing the retries.
Playbook:
-->cat until.yml
---
- hosts: localhost
gather_facts: no
tasks:
- name: "check command"
shell: echo hello
register: var
until: var.stdout.find('hi') != -1
retries: 5
delay: 1
playbook output:
-->ansible-playbook until.yml
PLAY [localhost] *************************************************************************************************************************************************************************************************************************
TASK [check command] ********************************************************************************************************************************************************************************************************
FAILED - RETRYING: who triggered the playbook (5 retries left).
FAILED - RETRYING: who triggered the playbook (4 retries left).
FAILED - RETRYING: who triggered the playbook (3 retries left).
FAILED - RETRYING: who triggered the playbook (2 retries left).
FAILED - RETRYING: who triggered the playbook (1 retries left).
fatal: [localhost]: FAILED! => {
"attempts": 5,
"changed": true,
"cmd": "echo hello",
"delta": "0:00:00.003004",
"end": "2019-12-03 10:04:14.731488",
"rc": 0,
"start": "2019-12-03 10:04:14.728484"
}
STDOUT:
hello
PLAY RECAP *******************************************************************************************************************************************************************************************************************************
localhost : ok=0 changed=0 unreachable=0 failed=1
You can divide your task into two tasks:
First task will poll for the desired output using until loop. But we have used ignore_errors: True , so that until loop will not fail the playbook. We will just capture the result.
In second task , use assert to print success_msg for success case and fail_msg for failure case.
Following is tweaked ,minimum working example:
---
- hosts: localhost
gather_facts: no
tasks:
- name: "check command"
shell: echo hello
register: var
until: var.stdout.find('hi') != -1
retries: 5
delay: 1
ignore_errors: true
- name: "Print result"
assert:
that: var.stdout|regex_search('hi')
fail_msg: "COuld not find HI in command output"
success_msg: "Hi is present in Command output"
Have a look at block error handling which can be used for this purpose.
Basic overview:
- block:
- name: A task that may fail.
debug:
msg: "I may fail"
failed_when: true
register: might_fail_exec
rescue:
- name: fail nicely with a msg
fail:
msg: "The task that might fail has failed. Here is some info from the task: {{ might_fail_exec }}"

Evaluating return code in ansible conditional

I'm working on automating a task which needs to append the latest version of software to a file. I don't want it to do this multiple times for the same version.
It looks at the following example file:
var software releases = new Array(
"4.3.0",
"4.4.0",
"4.5.0",
"4.7.0",
"4.8.0",
"4.11.0",
"4.12.1",
"4.14.0",
"4.15.0",
"4.16.0",
);
the defaults main.yml would pass in something like
VERSION: 4.16.2
code
- name: register version check
shell: cat /root/versions.js | grep -q {{VERSION}}
register: current_version
- debug: msg="The registered variable output is {{ current_version.rc }}"
- name: append to versions.js
lineinfile:
dest: /root/versions.js
regexp: '^\);'
insertbefore: '^#\);'
line: " \"{{VERSION}}\",\n);"
owner: root
state: present
when: current_version.rc == 1
problem: the debug message is evaluating current_version.rc and showing me boolean values based on the grep commands output, but I can't re-use this in the when conditional to determine if the task should be run.
Edit: the output:
PLAY [localhost] **************************************************************
GATHERING FACTS ***************************************************************
ok: [localhost]
TASK: [test | register version check] *****************************************
failed: [localhost] => {"changed": true, "cmd": "cat /root/versions.js | grep -q 3.19.2", "delta": "0:00:00.003570", "end": "2015-12-17 00:24:49.729078", "rc": 1, "start": "2015-12-17 00:24:49.725508", "warnings": []}
FATAL: all hosts have already failed -- aborting
PLAY RECAP ********************************************************************
to retry, use: --limit #/root/site.retry
localhost : ok=1 changed=0 unreachable=0 failed=1
As nikobelia pointed out in the comments, grep returns an exit code of 1 when it doesn't match any lines. Ansible then interprets this (actually any status code other than 0 from a shell/command task) as an error and so promptly fails.
You can tell Ansible to ignore the response code from the shell/command task by using ignore_errors. Although with grep this will ignore actual errors (given by a return code of 2) so instead you might want to use failed_when like this:
- name: register version check
shell: cat /root/versions.js | grep -q {{VERSION}}
register: current_version
failed_when: current_version.rc == 2

Resources