Print custom message from until loop in ansible - 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 }}"

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 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.

Ansible start a process and wait check until telnet condition is successful

I trigger multiple Tomcat startup scripts and then need to check if all process listens on their specific port across multiple hosts in the quickest time possible.
For the test case, I m writing 3 scripts that run on a single host and listen on ports 4443, 4445, 4447 respectively as below.
/tmp/startapp1.sh
while test 1 # infinite loop
sleep 10
do
nc -l localhost 4443 > /tmp/app1.log
done
/tmp/startapp2.sh
while test 1 # infinite loop
sleep 30
do
nc -l localhost 4445 > /tmp/app2.log
done
/tmp/startapp3.sh
while test 1 # infinite loop
sleep 20
do
nc -l localhost 4447 > /tmp/app3.log
done
Below is my code to trigger the script and check if the telnet is successful:
main.yml
- include_tasks: "internal.yml"
loop:
- /tmp/startapp1.sh 4443
- /tmp/startapp2.sh 4445
- /tmp/startapp3.sh 4447
internal.yml
- shell: "{{ item.split()[0] }}"
async: 600
poll: 0
- name: DEBUG CHECK TELNET
shell: "telnet {{ item.split()[1] }}"
delegate_to: localhost
register: telnetcheck
until: telnetcheck.rc == 0
async: 600
poll: 0
delay: 6
retries: 10
- name: Result of TELNET
async_status:
jid: "{{ item.ansible_job_id }}"
register: _jobs
until: _jobs.finished
delay: 6
retries: 10
with_items: "{{ telnetcheck.results }}"
To run: ansible-playbook main.yml
Requirement: the above three scripts should start along with telnet check in about 30 seconds.
Thus, the basic check that needs to be done here is telnet until: telnetcheck.rc == 0 but due to async the telnet shell module does not have entries for rc and hence I get the below error:
"msg": "The conditional check 'telnetcheck.rc == 0' failed. The error was: error while evaluating conditional (telnetcheck.rc == 0): 'dict object' has no attribute 'rc'"
In the above code where and how can I check if telnet had succeeded i.e telnetcheck.rc == 0 and make sure the requirement is met?
Currently I am not aware a solution with which one could start a shell script and wait for a status of it in one task. It might be possible to just change the shell script according the necessary behavior and let it provide self checks and exit codes. Or you could implement two or more tasks, whereby one is executing the shell script and the others later check on certain conditions.
Regarding your requirement
wait until telnet localhost 8076 is LISTENING (successful).
you may have a look into the module wait_for.
---
- hosts: localhost
become: false
gather_facts: false
tasks:
- name: "Test connection to local port"
wait_for:
host: localhost
port: 8076
delay: 0
timeout: 3
active_connection_states: SYN_RECV
check_mode: false # because remote module (wait_for) does not support it
register: result
- name: Show result
debug:
msg: "{{ result }}"
Further Q&A
How to use Ansible module wait_for together with loop?
Firewall Functional Test
An other approach of testing from Control Node on Remote Node if there is a LISTENER on localhost could be
---
- hosts: test.example.com
become: true
gather_facts: false
vars:
PORT: "8076"
tasks:
- name: "Check for LISTENER on remote localhost"
shell:
cmd: "lsof -Pi TCP:{{ PORT }}"
changed_when: false
check_mode: false
register: result
failed_when: result.rc != 0 and result.rc != 1
- name: Report missing LISTENER
debug:
msg: "No LISTENER on PORT {{ PORT }}"
when: result.rc == 1
Using an asynchronous action and an until in the same task makes nearly no sense.
As for your requirement to have the answer in the quickest time possible, you will have to rethink it through. With your three ports case, if you want them all to be opened before you move on the task, it will always be as slow as the slowest port to open, no matter what. Even if the first we probe is indeed the slowest, the two other will then probe in no time, so, trying to optimise it in an async is, to my point of view, an unnecessary optimisation.
Either you want to use until, and then each port probe would be stuck until they answer, or you want to run them asynchronously and the async_status will catch the return as it should if you wrap the telnet in a shell until loop.
In your until loop, the issue is that the return code won't be set until the command does indeed return, so you just have to check if the rc key of the dictionary is defined.
Mind that for all the examples below, I am manually opening port with nc -l -p <port>, this is why they do gradually open.
With until:
- shell: "telnet localhost {{ item.split()[1] }}"
delegate_to: localhost
register: telnetcheck
until:
- telnetcheck.rc is defined
- telnetcheck.rc == 0
delay: 6
retries: 10
This will yield:
TASK [shell] *****************************************************************
FAILED - RETRYING: [localhost]: shell (10 retries left).
changed: [localhost] => (item=/tmp/startapp1.sh 4443)
FAILED - RETRYING: [localhost]: shell (10 retries left).
changed: [localhost] => (item=/tmp/startapp2.sh 4445)
FAILED - RETRYING: [localhost]: shell (10 retries left).
changed: [localhost] => (item=/tmp/startapp3.sh 4447)
With async:
- shell: "until telnet 127.0.0.1 {{ item.split()[1] }}; do sleep 2; done"
delegate_to: localhost
register: telnetcheck
async: 600
poll: 0
- async_status:
jid: "{{ item.ansible_job_id }}"
register: _jobs
until: _jobs.finished
delay: 6
retries: 10
loop: "{{ telnetcheck.results }}"
loop_control:
label: "{{ item.item }}"
This will yield:
TASK [shell] *****************************************************************
changed: [localhost] => (item=/tmp/startapp1.sh 4443)
changed: [localhost] => (item=/tmp/startapp2.sh 4445)
changed: [localhost] => (item=/tmp/startapp3.sh 4447)
TASK [async_status] **********************************************************
FAILED - RETRYING: [localhost]: async_status (10 retries left).
changed: [localhost] => (item=/tmp/startapp1.sh 4443)
FAILED - RETRYING: [localhost]: async_status (10 retries left).
changed: [localhost] => (item=/tmp/startapp2.sh 4445)
FAILED - RETRYING: [localhost]: async_status (10 retries left).
changed: [localhost] => (item=/tmp/startapp3.sh 4447)
This said, you have to seriously consider #U880D's answer, as this is a more native answer for Ansible:
- wait_for:
host: localhost
port: "{{ item.split()[1] }}"
delay: 6
timeout: 60
This will yield:
TASK [wait_for] **************************************************************
ok: [localhost] => (item=/tmp/startapp1.sh 4443)
ok: [localhost] => (item=/tmp/startapp2.sh 4445)
ok: [localhost] => (item=/tmp/startapp3.sh 4447)

How to use Ansible fail modules to print multiple custom messages

I want my playbook to fail under two conditions.
If the SQL query's results is not returned by the database
If the SQL query's result returned has the string "775"
Below Playbook works fine and does the job.
- name: "Play 1"
hosts: localhost
- name: "Search for Number"
command: >
mysql --user="{{ DBUSER }}" --password="{{ DBPASS }}" deployment
--host=localhost --batch --skip-column-names
--execute="SELECT num FROM deploy_tbl"
register: command_result
failed_when: command_result.stdout is search('775') or command_result.rc != 0
when: Layer == 'APP'
I now wanted to print custom messages for both the conditions so I added the below lines after the above playbook code.
- name: Make Sure the mandatory input values are passed
fail:
msg: "This REQ is already Deployed. Hence retry with a Unique Number."
when: command_result.stdout is search('775')
- name: Make Sure the mandatory input values are passed
fail:
msg: "Database is not reachable."
when: command_result.rc != 0
This give me syntax error when running the playbook.
I'm on the latest version of Ansible.
Can you please tell me how can we update my plabook to print either of the custom message based on condition check ?
You should probably post your entire playbook at once in a single code block because there are absolutely no syntax problems in the bits you pasted out.
Meanwhile, if you want to achieve your requirement, you first have to understand that, by default, your playbook will stop as soon as an error is raised on a task. So in your case, the fail tasks will never be played as they are after the failing task.
One way to go around this problem would be to use ignore_error on your task.
In most cases, I find that blocks and their error handling feature are better suited to handle such scenarios.
The example below uses a block. I faked the sql call with a playbook var (that you can modify to play around) and a debug task using the same when conditions as yours.
I also modified the way to call fail so that you only have to do it once with the ternary filter.
If you are not familiar with the >- notation in yaml, have a look at a definition of scalar blocks. I used that to limit line length and ease readability.
---
- hosts: localhost
gather_facts: false
vars:
command_result:
stdout: "775"
rc: "0"
tasks:
- block:
- name: Fake task to mimic sql query
debug:
var: command_result
failed_when: command_result.stdout is search('775') or command_result.rc != 0
rescue:
- name: Make Sure the mandatory input values are passed
fail:
msg: >-
{{
command_result.stdout is search('775') |
ternary(
"This REQ is already Deployed. Hence retry with a Unique Number.",
"Database is not reachable."
)
}}
which gives:
PLAY [localhost] *********************************************************************
TASK [Fake task to mimic sql query] **************************************************
fatal: [localhost]: FAILED! => {
"command_result": {
"rc": "0",
"stdout": "775"
},
"failed_when_result": true
}
TASK [Make Sure the mandatory input values are passed] *******************************
fatal: [localhost]: FAILED! => {"changed": false, "msg": "This REQ is already Deployed. Hence retry with a Unique Number."}
PLAY RECAP ***************************************************************************
localhost : ok=0 changed=0 unreachable=0 failed=1 skipped=0 rescued=1 ignored=0

Condition based execution in ansible

I have a scenario if first condition is successful then execute condition two and if condition two is successful then execute condition three.
Below is what i tried.
vi testme.yml
---
- hosts: all
tasks:
- name: run this command and ignore the result
shell: "grep -ci Hello /tmp/data.out"
register: pingout
ignore_errors: yes
- debug: msg="{{ pingout.rc }}"
- name: run the server if Hello is found in the above task
command: echo "The server is UP since `uptime`"
register: output1
when: pingout.rc == 0
- debug: "{{ output1.stdout }}"
When the string is found I was expecting to see this executed and shown in the output: The server is UP since uptime
However, I do not see this printed in the output.
ansible-playbook -i /tmp/myhost /root/testme.yml
Output:
PLAY [all] ******************************************************************************************************************************************************************
TASK [Gathering Facts] ******************************************************************************************************************************************************
ok: [95.76.121.113]
TASK [run this command and ignore the result] *******************************************************************************************************************************
changed: [95.76.121.113]
TASK [debug] ****************************************************************************************************************************************************************
ok: [95.76.121.113] => {
"msg": "0"
}
TASK [run the server if Hello is found in the above task] *******************************************************************************************************************
changed: [95.76.121.113]
TASK [debug] ****************************************************************************************************************************************************************
ok: [95.76.121.113] => {
"msg": "Hello world!"
}
PLAY RECAP ******************************************************************************************************************************************************************
95.76.121.113 : ok=5 changed=2 unreachable=0 failed=0
You do not need to check rc. Ansible knows, when a command failed.
---
- hosts: localhost
connection: local
tasks:
- name: First
shell: "true"
register: first
ignore_errors: yes
- name: Second
shell: "true"
register: second
ignore_errors: yes
when: first is not failed
- name: Third
shell: "true"
register: third
ignore_errors: yes
when: second is not failed
Correct syntax is
- debug: msg="{{ output1.stdout }}"
, or
- debug: var=output1.stdout

Resources