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