相关文章推荐
含蓄的红薯  ·  SQL Server ...·  1 年前    · 
长情的豆腐  ·  如何让 Jupyter Notebook ...·  1 年前    · 
腼腆的皮带  ·  linux ...·  1 年前    · 
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

Great find! Unfortunately, end_play ends the entire playbook, even when used in a sub playbook called from a main playbook using e.g. import_tasks. – ssc Jan 21, 2018 at 17:09 @ssc You are mixing up tasks and playbooks importing. With import_tasks execution remains in the same play so it is supposed to be ended with that command. To import separate playbook use import_playbook statement instead. – Yaroslav Shabalin Jan 25, 2018 at 8:52 Important: end_play seems to stop the execution on all hosts, not just the one where the condition is matched! github.com/ansible/ansible/issues/27973 – reto May 15, 2018 at 16:51 If you run the playbook using -vv, you'll see META: ending play in the output, if it stops. – William Turrell Dec 23, 2018 at 18:37 This is nice but if end_host is used inside a role and if you have a playbook with roles: then it ends the whole playbook for the host. Isn't it possible to just skip to next role in the list? Is this worth a new question? :) – Evren Yurtesen Jan 15, 2020 at 8:14

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
                This does not provide an answer to the question. Once you have sufficient reputation you will be able to comment on any post; instead, provide answers that don't require clarification from the asker. - From Review
– Rob
                Oct 10, 2019 at 20:26
                Hello Rob, yes. I'm not able to comment, I wasn't aware of ability to edit answers of other people and I was missing this piece of information under this question.
– djasa
                Oct 11, 2019 at 23:27

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.

This is inconvenient when there is lots of tasks after the one which is to decide if to continue or not :-/ – jhutar Oct 6, 2016 at 19:39 Well, this could also be combined with a block and when statement, effectively grouping tasks to be run or skipped. Which should drastically reduce the inconvenience. – Mitms Jan 7, 2019 at 8:29 This approach also works well with include_tasks or import_tasks to keep the output clean, so there aren't a bunch of skipped tasks every time. – thinkmassive May 23, 2019 at 22:04

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.