diff --git a/lib/ansible/executor/task_executor.py b/lib/ansible/executor/task_executor.py index a93a76c2507..086db41935e 100644 --- a/lib/ansible/executor/task_executor.py +++ b/lib/ansible/executor/task_executor.py @@ -535,7 +535,7 @@ class TaskExecutor: # update the local copy of vars with the registered value, if specified, # or any facts which may have been generated by the module execution if self._task.register: - vars_copy[self._task.register] = wrap_var(result.copy()) + vars_copy[self._task.register] = wrap_var(result) if self._task.async_val > 0: if self._task.poll > 0 and not result.get('skipped') and not result.get('failed'): @@ -575,10 +575,20 @@ class TaskExecutor: else: result['failed'] = False + # Make attempts and retries available early to allow their use in changed/failed_when + result['attempts'] = attempt + # set the changed property if it was missing. if 'changed' not in result: result['changed'] = False + # re-update the local copy of vars with the registered value, if specified, + # or any facts which may have been generated by the module execution + # This gives changed/failed_when access to additional recently modified + # attributes of result + if self._task.register: + vars_copy[self._task.register] = wrap_var(result) + # if we didn't skip this task, use the helpers to evaluate the changed/ # failed_when properties if 'skipped' not in result: @@ -588,7 +598,6 @@ class TaskExecutor: if retries > 1: cond = Conditional(loader=self._loader) cond.when = self._task.until - result['attempts'] = attempt if cond.evaluate_conditional(templar, vars_copy): break else: diff --git a/test/integration/targets/until/tasks/main.yml b/test/integration/targets/until/tasks/main.yml index 74a214756dd..4a09ff3bec6 100644 --- a/test/integration/targets/until/tasks/main.yml +++ b/test/integration/targets/until/tasks/main.yml @@ -29,4 +29,43 @@ - assert: that: runcount.stdout | int == 6 # initial + 5 retries -- file: path="{{ until_tempfile_path }}" state=absent +- file: + path: "{{ until_tempfile_path }}" + state: absent + +- name: Test failed_when impacting until + shell: 'true' + register: failed_when_until + failed_when: True + until: failed_when_until is successful + retries: 3 + delay: 0.5 + ignore_errors: True + +- assert: + that: + - failed_when_until.attempts == 3 + +- name: Test changed_when impacting until + shell: 'true' + register: changed_when_until + changed_when: False + until: changed_when_until is changed + retries: 3 + delay: 0.5 + ignore_errors: True + +- assert: + that: + - changed_when_until.attempts == 3 + +# This task shouldn't fail, previously .attempts was not available to changed_when/failed_when +# and would cause the conditional to fail due to ``'dict object' has no attribute 'attempts'`` +# https://github.com/ansible/ansible/issues/34139 +- name: Test access to attempts in changed_when/failed_when + shell: 'true' + register: changed_when_attempts + until: 1 == 0 + retries: 5 + delay: 0.5 + failed_when: changed_when_attempts.attempts > 6