Collectives™ on Stack Overflow
Find centralized, trusted content and collaborate around the technologies you use most.
Learn more about Collectives
Teams
Q&A for work
Connect and share knowledge within a single location that is structured and easy to search.
Learn more about Teams
I want to exit without an error (I know about
assert
and
fail
modules) when I meet a certain condition. The following code exits but with a failure:
tasks:
- name: Check if there is something to upgrade
shell: if apt-get --dry-run upgrade | grep -q "0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded"; then echo "no"; else echo "yes"; fi
register: upgrading
- name: Exit if nothing to upgrade
fail: msg="Nothing to upgrade"
when: upgrading.stdout == "no"
You can also specify when
for conditionally ending the play:
- meta: end_play
when: upgrading.stdout == "no"
Note, though, that the task is not listed in the output of ansible-playbook, regardless of whether or not the play actually ends. Also, the task is not counted in the recap. So, you could do something like:
- block:
- name: "end play if nothing to upgrade"
debug:
msg: "nothing to upgrade, ending play"
- meta: end_play
when: upgrading.stdout == "no"
which will announce the end of the play right before ending it, only when the condition is met. If the condition is not met, you'll see the task named end play if nothing to upgrade
appropriately skipped, which would provide more info to the user as to why the play is, or is not, ending.
Of course, this will only end the current play and not all remaining plays in the playbook.
UPDATE June 20 2019:
As reto mentions in comments, end_play
ends the play for all hosts. In Ansible 2.8, end_host
was added to meta:
end_host (added in Ansible 2.8) is a per-host variation of end_play. Causes the play to end for the current host without failing it.
UPDATE Feb 2021: fixed broken link to meta module
–
–
–
–
–
Just a little note: meta: end_play
ends just the play, not a playbook. So this playbook:
- name: 1st play with end play
hosts: localhost
connection: local
gather_facts: no
tasks:
- name: I'll always be printed
debug:
msg: next task terminates first play
- name: Ending the 1st play now
meta: end_play
- name: I want to be printed!
debug:
msg: However I'm unreachable so this message won't appear in the output
- name: 2nd play
hosts: localhost
connection: local
gather_facts: no
tasks:
- name: I will also be printed always
debug:
msg: "meta: end_play ended just the 1st play. This is 2nd one."
will produce this output:
$ ansible-playbook -i localhost, playbooks/end_play.yml
PLAY [1st play with end play] **************************************************
TASK [I'll always be printed] **************************************************
ok: [localhost] => {
"msg": "next task terminates first play"
PLAY [2nd play] ****************************************************************
TASK [I will also be printed always] *******************************************
ok: [localhost] => {
"msg": "meta: end_play ended just the 1st play. This is 2nd one."
PLAY RECAP *********************************************************************
localhost : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
–
–
A better and more logical way to solve it may be to do the reverse and rather than fail if there is nothing to upgrade (which is a separate step that does only that) you could append all your upgrading tasks with a conditional depending on the upgrade
variable. In essence just add
when: upgrading.changed
to tasks that should be only executed during an upgrade.
It is a bit more work, but it also brings clarity and self contains the logic that affects given task within itself, rather than depending on something way above which may or may not terminate it early.
–
–
–
Lets use what Tymoteusz suggested for roles:
Split your play into two roles where first role will execute the check (and sets some variable holding check's result) and second one will act based on result of the check.
I have created aaa.yaml
with this content:
- hosts: all
remote_user: root
roles:
- check
- { role: doit, when: "check.stdout == '0'" }
then role check
in roles/check/tasks/main.yaml
:
- name: "Check if we should continue"
shell:
echo $(( $RANDOM % 2 ))
register: check
- debug:
var: check.stdout
and then role doit
in roles/doit/tasks/main.yaml
:
- name: "Do it only on systems where check returned 0"
command:
And this was the output:
TASK [check : Check if we should continue] *************************************
Thursday 06 October 2016 21:49:49 +0200 (0:00:09.800) 0:00:09.832 ******
changed: [capsule.example.com]
changed: [monitoring.example.com]
changed: [satellite.example.com]
changed: [docker.example.com]
TASK [check : debug] ***********************************************************
Thursday 06 October 2016 21:49:55 +0200 (0:00:05.171) 0:00:15.004 ******
ok: [monitoring.example.com] => {
"check.stdout": "0"
ok: [satellite.example.com] => {
"check.stdout": "1"
ok: [capsule.example.com] => {
"check.stdout": "0"
ok: [docker.example.com] => {
"check.stdout": "0"
TASK [doit : Do it only on systems where check returned 0] *********************
Thursday 06 October 2016 21:49:55 +0200 (0:00:00.072) 0:00:15.076 ******
skipping: [satellite.example.com]
changed: [capsule.example.com]
changed: [docker.example.com]
changed: [monitoring.example.com]
It is not perfect: looks like you will keep seeing skipping status for all tasks for skipped systems, but might do the trick.
The following was helpful in my case, as meta: end_play
seems to stop the execution for all hosts, not just the one that matches.
First establish a fact:
- name: Determine current version
become: yes
slurp:
src: /opt/app/CHECKSUM
register: version_check
ignore_errors: yes
- set_fact:
is_update_needed: "{{ ( version_check['checksum'] | b64decode != installer_file.stat.checksum) }}"
Now include that part that should only executed on this condition:
# update-app.yml can be placed in the same role folder
- import_tasks: update-app.yml
when: is_update_needed
Running into the same issue, ugly as it might look to you, I decided temporarily to tweak the code to allow for a end_playbook
value for the ansible.builtin.meta
plugin. So I added below condition to file /path/to/python.../site-packages/ansible/plugins/strategy/__init__.py
:
elif meta_action == 'end_playbook':
if _evaluate_conditional(target_host):
for host in self._inventory.get_hosts(iterator._play.hosts):
if host.name not in self._tqm._unreachable_hosts:
iterator.set_run_state_for_host(host.name, IteratingStates.COMPLETE)
sys.exit(0)
msg = "ending playbook"
else:
skipped = True
skip_reason += ', continuing play'
As you can see, pretty simple end effective to stop the entire process using sys.exit(0)
.
Here's an example playbook to test with:
- name: test
hosts: localhost
connection: local
gather_facts: no
become: no
tasks:
- meta: end_playbook
when: true
- fail:
- name: test2
hosts: localhost
connection: local
gather_facts: no
become: no
tasks:
- debug:
msg: hi
PLAY [test] ****************
TASK [meta] ****************
When I switch to when: false
, it skips to next task.
PLAY [test] ********************
TASK [meta] ********************
2023-01-05 15:04:39 (0:00:00.021) 0:00:00.021 ***************************
skipping: [localhost]
TASK [fail] ********************
2023-01-05 15:04:39 (0:00:00.013) 0:00:00.035 ***************************
fatal: [localhost]: FAILED! => changed=false
msg: Failed as requested from task
PLAY RECAP *****************
localhost : ok=0 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.