diff --git a/docs/docsite/rst/intro_configuration.rst b/docs/docsite/rst/intro_configuration.rst index d44a5881e80..c0df1bd0eaf 100644 --- a/docs/docsite/rst/intro_configuration.rst +++ b/docs/docsite/rst/intro_configuration.rst @@ -762,6 +762,27 @@ always default to the current user if this is not defined:: remote_user = root + +.. _restrict_facts_namespace: + +restrict_facts_namespace +======================== + +.. versionadded:: 2.4 + +This allows restricting facts in their own namespace (under ansible_facts) instead of pushing them into the main. +False by default. Can also be set via the environment variable `ANSIBLE_RESTRICT_FACTS`. Using `ansible_system` as an example: + +When False:: + + - debug: var=ansible_system + + +When True:: + + - debug: var=ansible_facts.ansible_system + + .. _retry_files_enabled: retry_files_enabled diff --git a/examples/ansible.cfg b/examples/ansible.cfg index 109f508002e..459fc9719c4 100644 --- a/examples/ansible.cfg +++ b/examples/ansible.cfg @@ -288,6 +288,10 @@ # only update this setting if you know how this works, otherwise it can break module execution #network_group_modules=['eos', 'nxos', 'ios', 'iosxr', 'junos', 'vyos'] +# This keeps facts from polluting the main namespace as variables. +# Setting to True keeps them under the ansible_facts namespace, the default is False +#restrict_facts_namespace: True + [privilege_escalation] #become=True #become_method=sudo diff --git a/lib/ansible/constants.py b/lib/ansible/constants.py index 1f00afcdcb2..f79f9edcbba 100644 --- a/lib/ansible/constants.py +++ b/lib/ansible/constants.py @@ -236,6 +236,7 @@ DEFAULT_VAR_COMPRESSION_LEVEL = get_config(p, DEFAULTS, 'var_compression_level', DEFAULT_INTERNAL_POLL_INTERVAL = get_config(p, DEFAULTS, 'internal_poll_interval', None, 0.001, value_type='float') ERROR_ON_MISSING_HANDLER = get_config(p, DEFAULTS, 'error_on_missing_handler', 'ANSIBLE_ERROR_ON_MISSING_HANDLER', True, value_type='boolean') SHOW_CUSTOM_STATS = get_config(p, DEFAULTS, 'show_custom_stats', 'ANSIBLE_SHOW_CUSTOM_STATS', False, value_type='boolean') +NAMESPACE_FACTS = get_config(p, DEFAULTS, 'restrict_facts_namespace', 'ANSIBLE_RESTRICT_FACTS', False, value_type='boolean') # static includes DEFAULT_TASK_INCLUDES_STATIC = get_config(p, DEFAULTS, 'task_includes_static', 'ANSIBLE_TASK_INCLUDES_STATIC', False, value_type='boolean') diff --git a/lib/ansible/executor/task_executor.py b/lib/ansible/executor/task_executor.py index c2c87ef5a81..64f8a6c3e10 100644 --- a/lib/ansible/executor/task_executor.py +++ b/lib/ansible/executor/task_executor.py @@ -557,7 +557,9 @@ class TaskExecutor: return failed_when_result if 'ansible_facts' in result: - vars_copy.update(result['ansible_facts']) + if not C.NAMESPACE_FACTS: + vars_copy.update(result['ansible_facts']) + vars_copy.update({'ansible_facts': result['ansible_facts']}) # set the failed property if the result has a non-zero rc. This will be # overridden below if the failed_when property is set @@ -596,7 +598,9 @@ class TaskExecutor: variables[self._task.register] = wrap_var(result) if 'ansible_facts' in result: - variables.update(result['ansible_facts']) + if not C.NAMESPACE_FACTS: + variables.update(result['ansible_facts']) + variables.update({'ansible_facts': result['ansible_facts']}) # save the notification target in the result, if it was specified, as # this task may be running in a loop in which case the notification diff --git a/lib/ansible/plugins/action/__init__.py b/lib/ansible/plugins/action/__init__.py index 736a9cab026..95343c2e696 100644 --- a/lib/ansible/plugins/action/__init__.py +++ b/lib/ansible/plugins/action/__init__.py @@ -733,7 +733,7 @@ class ActionBase(with_metaclass(ABCMeta, object)): # actually execute res = self._low_level_execute_command(cmd, sudoable=sudoable, in_data=in_data) - # parse the main result, also cleans up internal keys + # parse the main result data = self._parse_returned_data(res) #NOTE: INTERNAL KEYS ONLY ACCESSIBLE HERE diff --git a/lib/ansible/vars/__init__.py b/lib/ansible/vars/__init__.py index 5afdfd14ee6..03f435d64ab 100644 --- a/lib/ansible/vars/__init__.py +++ b/lib/ansible/vars/__init__.py @@ -281,7 +281,11 @@ class VariableManager: # finally, the facts caches for this host, if it exists try: host_facts = wrap_var(self._fact_cache.get(host.name, dict())) - all_vars = combine_vars(all_vars, host_facts) + if not C.NAMESPACE_FACTS: + # allow facts to polute main namespace + all_vars = combine_vars(all_vars, host_facts) + # always return namespaced facts + all_vars = combine_vars(all_vars, {'ansible_facts': host_facts}) except KeyError: pass