diff --git a/changelogs/fragments/64906-always-delegate-fact-prefixes.yml b/changelogs/fragments/64906-always-delegate-fact-prefixes.yml new file mode 100644 index 00000000000..49bec488dc0 --- /dev/null +++ b/changelogs/fragments/64906-always-delegate-fact-prefixes.yml @@ -0,0 +1,4 @@ +bugfixes: +- Fact Delegation - Add ability to indicate which facts must always be delegated. Primarily + for ``discovered_interpreter_python`` right now, but extensible later. + (https://github.com/ansible/ansible/issues/61002) diff --git a/lib/ansible/plugins/strategy/__init__.py b/lib/ansible/plugins/strategy/__init__.py index 03edd50667c..c4a46dfcb20 100644 --- a/lib/ansible/plugins/strategy/__init__.py +++ b/lib/ansible/plugins/strategy/__init__.py @@ -55,6 +55,12 @@ display = Display() __all__ = ['StrategyBase'] +# This list can be an exact match, or start of string bound +# does not accept regex +ALWAYS_DELEGATE_FACT_PREFIXES = frozenset(( + 'discovered_interpreter_', +)) + class StrategySentinel: pass @@ -389,6 +395,34 @@ class StrategyBase: host_name = result.get('_ansible_delegated_vars', {}).get('ansible_delegated_host', None) return [host_name or task.delegate_to] + def _set_always_delegated_facts(self, result, task): + """Sets host facts for ``delegate_to`` hosts for facts that should + always be delegated + + This operation mutates ``result`` to remove the always delegated facts + + See ``ALWAYS_DELEGATE_FACT_PREFIXES`` + """ + if task.delegate_to is None: + return + + facts = result['ansible_facts'] + always_keys = set() + _add = always_keys.add + for fact_key in facts: + for always_key in ALWAYS_DELEGATE_FACT_PREFIXES: + if fact_key.startswith(always_key): + _add(fact_key) + if always_keys: + _pop = facts.pop + always_facts = { + 'ansible_facts': dict((k, _pop(k)) for k in list(facts) if k in always_keys) + } + host_list = self.get_delegated_hosts(result, task) + _set_host_facts = self._variable_manager.set_host_facts + for target_host in host_list: + _set_host_facts(target_host, always_facts) + @debug_closure def _process_pending_results(self, iterator, one_pass=False, max_passes=None): ''' @@ -606,11 +640,13 @@ class StrategyBase: self._add_group(original_host, result_item) if 'ansible_facts' in result_item: - # if delegated fact and we are delegating facts, we need to change target host for them if original_task.delegate_to is not None and original_task.delegate_facts: host_list = self.get_delegated_hosts(result_item, original_task) else: + # Set facts that should always be on the delegated hosts + self._set_always_delegated_facts(result_item, original_task) + host_list = self.get_task_hosts(iterator, original_host, original_task) if original_task.action == 'include_vars':