Prevent double failing hosts for includes in loops (#76928)

Fixes #23161
pull/78589/head
Martin Krizek 2 years ago committed by GitHub
parent 3d6a60b481
commit 42d8a9daa8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,2 @@
bugfixes:
- "Properly execute rescue section when an include task fails in all loop iterations (https://github.com/ansible/ansible/issues/23161)"

@ -826,8 +826,11 @@ class StrategyBase:
def _load_included_file(self, included_file, iterator, is_handler=False): def _load_included_file(self, included_file, iterator, is_handler=False):
''' '''
Loads an included YAML file of tasks, applying the optional set of variables. Loads an included YAML file of tasks, applying the optional set of variables.
'''
Raises AnsibleError exception in case of a failure during including a file,
in such case the caller is responsible for marking the host(s) as failed
using PlayIterator.mark_host_failed().
'''
display.debug("loading included file: %s" % included_file._filename) display.debug("loading included file: %s" % included_file._filename)
try: try:
data = self._loader.load_from_file(included_file._filename) data = self._loader.load_from_file(included_file._filename)
@ -863,15 +866,11 @@ class StrategyBase:
for r in included_file._results: for r in included_file._results:
r._result['failed'] = True r._result['failed'] = True
# mark all of the hosts including this file as failed, send callbacks,
# and increment the stats for this host
for host in included_file._hosts: for host in included_file._hosts:
tr = TaskResult(host=host, task=included_file._task, return_data=dict(failed=True, reason=reason)) tr = TaskResult(host=host, task=included_file._task, return_data=dict(failed=True, reason=reason))
iterator.mark_host_failed(host)
self._tqm._failed_hosts[host.name] = True
self._tqm._stats.increment('failures', host.name) self._tqm._stats.increment('failures', host.name)
self._tqm.send_callback('v2_runner_on_failed', tr) self._tqm.send_callback('v2_runner_on_failed', tr)
return [] raise AnsibleError(reason) from e
# finally, send the callback and return the list of blocks loaded # finally, send the callback and return the list of blocks loaded
self._tqm.send_callback('v2_playbook_on_include', included_file) self._tqm.send_callback('v2_playbook_on_include', included_file)

@ -234,6 +234,7 @@ class StrategyModule(StrategyBase):
if len(included_files) > 0: if len(included_files) > 0:
all_blocks = dict((host, []) for host in hosts_left) all_blocks = dict((host, []) for host in hosts_left)
failed_includes_hosts = set()
for included_file in included_files: for included_file in included_files:
display.debug("collecting new blocks for %s" % included_file) display.debug("collecting new blocks for %s" % included_file)
is_handler = False is_handler = False
@ -256,12 +257,12 @@ class StrategyModule(StrategyBase):
except AnsibleParserError: except AnsibleParserError:
raise raise
except AnsibleError as e: except AnsibleError as e:
if included_file._is_role:
# include_role does not have on_include callback so display the error
display.error(to_text(e), wrap_text=False)
for r in included_file._results: for r in included_file._results:
r._result['failed'] = True r._result['failed'] = True
failed_includes_hosts.add(r._host)
for host in included_file._hosts:
iterator.mark_host_failed(host)
display.warning(to_text(e))
continue continue
for new_block in new_blocks: for new_block in new_blocks:
@ -282,6 +283,10 @@ class StrategyModule(StrategyBase):
all_blocks[host].append(final_block) all_blocks[host].append(final_block)
display.debug("done collecting new blocks for %s" % included_file) display.debug("done collecting new blocks for %s" % included_file)
for host in failed_includes_hosts:
self._tqm._failed_hosts[host.name] = True
iterator.mark_host_failed(host)
display.debug("adding all collected blocks from %d included file(s) to iterator" % len(included_files)) display.debug("adding all collected blocks from %d included file(s) to iterator" % len(included_files))
for host in hosts_left: for host in hosts_left:
iterator.add_tasks(host, all_blocks[host]) iterator.add_tasks(host, all_blocks[host])

@ -273,6 +273,7 @@ class StrategyModule(StrategyBase):
all_blocks = dict((host, []) for host in hosts_left) all_blocks = dict((host, []) for host in hosts_left)
display.debug("done generating all_blocks data") display.debug("done generating all_blocks data")
included_tasks = [] included_tasks = []
failed_includes_hosts = set()
for included_file in included_files: for included_file in included_files:
display.debug("processing included file: %s" % included_file._filename) display.debug("processing included file: %s" % included_file._filename)
is_handler = False is_handler = False
@ -324,14 +325,17 @@ class StrategyModule(StrategyBase):
except AnsibleParserError: except AnsibleParserError:
raise raise
except AnsibleError as e: except AnsibleError as e:
if included_file._is_role:
# include_role does not have on_include callback so display the error
display.error(to_text(e), wrap_text=False)
for r in included_file._results: for r in included_file._results:
r._result['failed'] = True r._result['failed'] = True
failed_includes_hosts.add(r._host)
continue
for host in included_file._hosts: for host in failed_includes_hosts:
self._tqm._failed_hosts[host.name] = True self._tqm._failed_hosts[host.name] = True
iterator.mark_host_failed(host) iterator.mark_host_failed(host)
display.error(to_text(e), wrap_text=False)
continue
# finally go through all of the hosts and append the # finally go through all of the hosts and append the
# accumulated blocks to their list of tasks # accumulated blocks to their list of tasks

@ -0,0 +1,29 @@
- name: "Test rescue/always sections with includes in a loop, strategy={{ strategy }}"
hosts: localhost
gather_facts: false
strategy: "{{ strategy }}"
tasks:
- block:
- include_role:
name: "{{ item }}"
loop:
- a
- b
rescue:
- debug:
msg: rescue include_role in a loop
always:
- debug:
msg: always include_role in a loop
- block:
- include_tasks: "{{ item }}"
loop:
- a
- b
rescue:
- debug:
msg: rescue include_tasks in a loop
always:
- debug:
msg: always include_tasks in a loop

@ -11,3 +11,6 @@ set +e
result="$(ansible-playbook -i ../../inventory include_on_playbook_should_fail.yml -v "$@" 2>&1)" result="$(ansible-playbook -i ../../inventory include_on_playbook_should_fail.yml -v "$@" 2>&1)"
set -e set -e
grep -q "ERROR! 'include' is not a valid attribute for a Play" <<< "$result" grep -q "ERROR! 'include' is not a valid attribute for a Play" <<< "$result"
ansible-playbook includes_loop_rescue.yml --extra-vars strategy=linear "$@"
ansible-playbook includes_loop_rescue.yml --extra-vars strategy=free "$@"

Loading…
Cancel
Save