From fd19ff231055c439c6a2e9bb590fef09818b2afc Mon Sep 17 00:00:00 2001 From: Martin Krizek Date: Tue, 6 Sep 2022 17:11:49 +0200 Subject: [PATCH] Propagate ansible_failed_task to an outer rescue (#78676) Fixes #43191 Fixes #72638 Fixes #78042 Co-authored-by: Sandra McCann --- .../43191-72638-ansible_failed_task-fixes.yml | 2 ++ .../rst/playbook_guide/playbooks_blocks.rst | 4 ++++ lib/ansible/executor/play_iterator.py | 6 +++++- lib/ansible/plugins/strategy/__init__.py | 13 ++++--------- test/integration/targets/blocks/43191-2.yml | 17 +++++++++++++++++ test/integration/targets/blocks/43191.yml | 18 ++++++++++++++++++ test/integration/targets/blocks/runme.sh | 3 +++ 7 files changed, 53 insertions(+), 10 deletions(-) create mode 100644 changelogs/fragments/43191-72638-ansible_failed_task-fixes.yml create mode 100644 test/integration/targets/blocks/43191-2.yml create mode 100644 test/integration/targets/blocks/43191.yml diff --git a/changelogs/fragments/43191-72638-ansible_failed_task-fixes.yml b/changelogs/fragments/43191-72638-ansible_failed_task-fixes.yml new file mode 100644 index 00000000000..411369729f8 --- /dev/null +++ b/changelogs/fragments/43191-72638-ansible_failed_task-fixes.yml @@ -0,0 +1,2 @@ +bugfixes: + - Propagate ``ansible_failed_task`` and ``ansible_failed_result`` to an outer rescue (https://github.com/ansible/ansible/issues/43191) diff --git a/docs/docsite/rst/playbook_guide/playbooks_blocks.rst b/docs/docsite/rst/playbook_guide/playbooks_blocks.rst index 3c28d16ba11..87dec31bd64 100644 --- a/docs/docsite/rst/playbook_guide/playbooks_blocks.rst +++ b/docs/docsite/rst/playbook_guide/playbooks_blocks.rst @@ -177,6 +177,10 @@ ansible_failed_task ansible_failed_result The captured return result of the failed task that triggered the rescue. This would equate to having used this var in the ``register`` keyword. +.. note:: + + In ``ansible-core`` 2.14 or later, both variables are propagated from an inner block to an outer ``rescue`` portion of a block. + .. seealso:: :ref:`playbooks_intro` diff --git a/lib/ansible/executor/play_iterator.py b/lib/ansible/executor/play_iterator.py index a60848a521a..6049b236f14 100644 --- a/lib/ansible/executor/play_iterator.py +++ b/lib/ansible/executor/play_iterator.py @@ -571,10 +571,14 @@ class PlayIterator: Given the current HostState state, determines if the current block, or any child blocks, are in rescue mode. ''' - if state.run_state == IteratingStates.RESCUE: + if state.get_current_block().rescue: return True if state.tasks_child_state is not None: return self.is_any_block_rescuing(state.tasks_child_state) + if state.rescue_child_state is not None: + return self.is_any_block_rescuing(state.rescue_child_state) + if state.always_child_state is not None: + return self.is_any_block_rescuing(state.always_child_state) return False def get_original_task(self, host, task): diff --git a/lib/ansible/plugins/strategy/__init__.py b/lib/ansible/plugins/strategy/__init__.py index c3c1a390ea0..fd295055eff 100644 --- a/lib/ansible/plugins/strategy/__init__.py +++ b/lib/ansible/plugins/strategy/__init__.py @@ -560,20 +560,15 @@ class StrategyBase: else: iterator.mark_host_failed(original_host) - # grab the current state and if we're iterating on the rescue portion - # of a block then we save the failed task in a special var for use - # within the rescue/always state, _ = iterator.get_next_task_for_host(original_host, peek=True) if iterator.is_failed(original_host) and state and state.run_state == IteratingStates.COMPLETE: self._tqm._failed_hosts[original_host.name] = True - # Use of get_active_state() here helps detect proper state if, say, we are in a rescue - # block from an included file (include_tasks). In a non-included rescue case, a rescue - # that starts with a new 'block' will have an active state of IteratingStates.TASKS, so we also - # check the current state block tree to see if any blocks are rescuing. - if state and (iterator.get_active_state(state).run_state == IteratingStates.RESCUE or - iterator.is_any_block_rescuing(state)): + # if we're iterating on the rescue portion of a block then + # we save the failed task in a special var for use + # within the rescue/always + if iterator.is_any_block_rescuing(state): self._tqm._stats.increment('rescued', original_host.name) iterator._play._removed_hosts.remove(original_host.name) self._variable_manager.set_nonpersistent_facts( diff --git a/test/integration/targets/blocks/43191-2.yml b/test/integration/targets/blocks/43191-2.yml new file mode 100644 index 00000000000..4beda4e12b4 --- /dev/null +++ b/test/integration/targets/blocks/43191-2.yml @@ -0,0 +1,17 @@ +- hosts: localhost + gather_facts: false + tasks: + - block: + - block: + - name: EXPECTED FAILURE + fail: + always: + - block: + - debug: + always: + - debug: + rescue: + - assert: + that: + - ansible_failed_task is defined + - ansible_failed_result is defined diff --git a/test/integration/targets/blocks/43191.yml b/test/integration/targets/blocks/43191.yml new file mode 100644 index 00000000000..d69e43898fa --- /dev/null +++ b/test/integration/targets/blocks/43191.yml @@ -0,0 +1,18 @@ +- hosts: localhost + gather_facts: false + tasks: + - block: + - block: + - name: EXPECTED FAILURE + fail: + always: + - block: + - block: + - debug: + rescue: + - block: + - block: + - assert: + that: + - ansible_failed_task is defined + - ansible_failed_result is defined diff --git a/test/integration/targets/blocks/runme.sh b/test/integration/targets/blocks/runme.sh index 4c345df1a99..06e3ddee830 100755 --- a/test/integration/targets/blocks/runme.sh +++ b/test/integration/targets/blocks/runme.sh @@ -124,3 +124,6 @@ ansible-playbook -i host1,host2 -vv 78612.yml | tee 78612.out set -e [ "$(grep -c 'PASSED' 78612.out)" -eq 1 ] rm -f 78612.out + +ansible-playbook -vv 43191.yml +ansible-playbook -vv 43191-2.yml