Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Don't use the task for a cache, return a special cache var #47243

Merged
merged 6 commits into from
Oct 18, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions changelogs/fragments/delegate_to_loop_hostvars.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
bugfixes:
- delegate_to - When templating ``delegate_to`` in a loop, don't use the task for a cache, return a special cache through ``get_vars`` allowing looping over a hostvar (https://github.com/ansible/ansible/issues/47207)
7 changes: 6 additions & 1 deletion lib/ansible/executor/task_executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,12 @@ def _get_loop_items(self):

templar = Templar(loader=self._loader, shared_loader_obj=self._shared_loader_obj, variables=self._job_vars)
items = None
if self._task.loop_with:
loop_cache = self._job_vars.get('_ansible_loop_cache')
if loop_cache is not None:
# _ansible_loop_cache may be set in `get_vars` when calculating `delegate_to`
# to avoid reprocessing the loop
items = loop_cache
elif self._task.loop_with:
if self._task.loop_with in self._shared_loader_obj.lookup_loader:
fail = True
if self._task.loop_with == 'first_found':
Expand Down
13 changes: 6 additions & 7 deletions lib/ansible/vars/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -430,7 +430,7 @@ def plugins_by_groups():
# if we have a task and we're delegating to another host, figure out the
# variables for that host now so we don't have to rely on hostvars later
if task and task.delegate_to is not None and include_delegate_to:
all_vars['ansible_delegated_vars'] = self._get_delegated_vars(play, task, all_vars)
all_vars['ansible_delegated_vars'], all_vars['_ansible_loop_cache'] = self._get_delegated_vars(play, task, all_vars)

# 'vars' magic var
if task or play:
Expand Down Expand Up @@ -594,16 +594,15 @@ def _get_delegated_vars(self, play, task, existing_variables):
include_hostvars=False,
)

_ansible_loop_cache = None
if has_loop and cache_items:
# delegate_to templating produced a change, update task.loop with templated items,
# delegate_to templating produced a change, so we will cache the templated items
# in a special private hostvar
# this ensures that delegate_to+loop doesn't produce different results than TaskExecutor
# which may reprocess the loop
# Set loop_with to None, so we don't do extra unexpected processing on the cached items later
# in TaskExecutor
task.loop_with = None
task.loop = items
_ansible_loop_cache = items

return delegated_host_vars
return delegated_host_vars, _ansible_loop_cache

def clear_facts(self, hostname):
'''
Expand Down
6 changes: 4 additions & 2 deletions test/integration/targets/delegate_to/runme.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ ansible-playbook test_loop_control.yml -v "$@"

ansible-playbook test_delegate_to_loop_randomness.yml -v "$@"

ansible-playbook delegate_and_nolog.yml -v "$@"
ansible-playbook delegate_and_nolog.yml -i ../../inventory -v "$@"

ansible-playbook delegate_facts_block.yml -v "$@"
ansible-playbook delegate_facts_block.yml -i ../../inventory -v "$@"

ansible-playbook test_delegate_to_loop_caching.yml -i ../../inventory -v "$@"
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
- hosts: testhost,testhost2
gather_facts: false
vars:
delegate_to_host: "localhost"
tasks:
- set_fact:
gandalf:
shout: 'You shall not pass!'
when: inventory_hostname == 'testhost'

- set_fact:
gandalf:
speak: 'Run you fools!'
when: inventory_hostname == 'testhost2'

- name: works correctly
debug: var=item
delegate_to: localhost
with_dict: "{{ gandalf }}"
register: result1

- name: shows same item for all hosts
debug: var=item
delegate_to: "{{ delegate_to_host }}"
with_dict: "{{ gandalf }}"
register: result2

- debug:
var: result2.results[0].item.value

- assert:
that:
- result1.results[0].item.value == 'You shall not pass!'
- result2.results[0].item.value == 'You shall not pass!'
when: inventory_hostname == 'testhost'

- assert:
that:
- result1.results[0].item.value == 'Run you fools!'
- result2.results[0].item.value == 'Run you fools!'
when: inventory_hostname == 'testhost2'

- assert:
that:
- _ansible_loop_cache is undefined