Skip to content

Commit

Permalink
Only mark a role as complete once a task in it executes for the targe…
Browse files Browse the repository at this point in the history
…t host (ansible#81565)

* If all tasks in the role are skipped or unreachable, the role is not marked as complete for the host.

* Only mark the role as complete if a task in the role succeeds or fails for the host.
  • Loading branch information
s-hertel authored Sep 8, 2023
1 parent 3eb96f2 commit 8034651
Show file tree
Hide file tree
Showing 8 changed files with 67 additions and 2 deletions.
2 changes: 2 additions & 0 deletions changelogs/fragments/role-deduplication-condition.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
bugfixes:
- role deduplication - don't deduplicate before a role has had a task run for that particular host (https://github.com/ansible/ansible/issues/81486).
5 changes: 3 additions & 2 deletions lib/ansible/plugins/strategy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1033,8 +1033,9 @@ def _evaluate_conditional(h):
# How would this work with allow_duplicates??
if task.implicit:
role_obj = self._get_cached_role(task, iterator._play)
role_obj._completed[target_host.name] = True
msg = 'role_complete for %s' % target_host.name
if target_host.name in role_obj._had_task_run:
role_obj._completed[target_host.name] = True
msg = 'role_complete for %s' % target_host.name
elif meta_action == 'reset_connection':
all_vars = self._variable_manager.get_vars(play=iterator._play, host=target_host, task=task,
_hosts=self._hosts_cache, _hosts_all=self._hosts_cache_all)
Expand Down
47 changes: 47 additions & 0 deletions test/integration/targets/roles/role_complete.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
- name: test deduping allows for 1 successful execution of role after it is skipped
hosts: testhost
gather_facts: false
tags: [ 'conditional_skipped' ]
roles:
# Skipped the first time it executes
- role: a
when: role_set_var is defined

- role: set_var

# No longer skipped
- role: a
when: role_set_var is defined
# Deduplicated with the previous success
- role: a
when: role_set_var is defined

- name: test deduping allows for successful execution of role after host is unreachable
hosts: fake,testhost
gather_facts: false
tags: [ 'unreachable' ]
ignore_unreachable: yes
roles:
# unreachable by the first host
- role: test_connectivity

# unreachable host will try again,
# the successful host will not because it's deduplicated
- role: test_connectivity

- name: test deduping role for failed host
hosts: testhost,localhost
gather_facts: false
tags: [ 'conditional_failed' ]
ignore_errors: yes
roles:
# Uses run_once to fail on the first host the first time it executes
- role: failed_when

- role: set_var
- role: recover

# Deduplicated after the failure, ONLY runs for localhost
- role: failed_when
# Deduplicated with the previous success
- role: failed_when
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
- debug:
msg: "{{ role_set_var is undefined | ternary('failed_when task failed', 'failed_when task succeeded') }}"
failed_when: role_set_var is undefined
run_once: true
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- meta: clear_host_errors
2 changes: 2 additions & 0 deletions test/integration/targets/roles/roles/set_var/tasks/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
- set_fact:
role_set_var: true
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
- ping:
data: 'reachable'
6 changes: 6 additions & 0 deletions test/integration/targets/roles/runme.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ set -eux
# but still dupe across plays
[ "$(ansible-playbook no_dupes.yml -i ../../inventory "$@" | grep -c '"msg": "A"')" = "3" ]

# and don't dedupe before the role successfully completes
[ "$(ansible-playbook role_complete.yml -i ../../inventory -i fake, --tags conditional_skipped "$@" | grep -c '"msg": "A"')" = "1" ]
[ "$(ansible-playbook role_complete.yml -i ../../inventory -i fake, --tags conditional_failed "$@" | grep -c '"msg": "failed_when task succeeded"')" = "1" ]
[ "$(ansible-playbook role_complete.yml -i ../../inventory -i fake, --tags unreachable "$@" | grep -c '"data": "reachable"')" = "1" ]
ansible-playbook role_complete.yml -i ../../inventory -i fake, --tags unreachable "$@" | grep -e 'ignored=2'

# include/import can execute another instance of role
[ "$(ansible-playbook allowed_dupes.yml -i ../../inventory --tags importrole "$@" | grep -c '"msg": "A"')" = "2" ]
[ "$(ansible-playbook allowed_dupes.yml -i ../../inventory --tags includerole "$@" | grep -c '"msg": "A"')" = "2" ]
Expand Down

0 comments on commit 8034651

Please sign in to comment.