1
0
Fork 0
mirror of https://github.com/ansible-collections/community.general.git synced 2024-09-14 20:13:21 +02:00

namespace facts

updated action plugins to use new guranteed facts
updated tests to new data clean
added cases for ansible_local and some docstrings
This commit is contained in:
Brian Coca 2017-10-29 00:33:02 -04:00 committed by Brian Coca
parent e0cb54a2aa
commit db749de5b8
15 changed files with 236 additions and 175 deletions

View file

@ -1300,19 +1300,16 @@ NETWORK_GROUP_MODULES:
- {key: network_group_modules, section: defaults}
type: list
yaml: {key: defaults.network_group_modules}
#ONLY_NAMESPACE_FACTS:
# Deffered to 2.5
# FIXME: reenable when we can remove ansible_ prefix from namespaced facts
# default: False
# description:
# - Facts normally get injected as top level variables, this setting prevents that.
# - Facts are still available in the `ansible_facts` variable w/o the `ansible_` prefix.
# env: [{name: ANSIBLE_RESTRICT_FACTS}]
# ini:
# - {key: restrict_facts_namespace, section: defaults}
# type: boolean
# yaml: {key: defaults.restrict_facts_namespace}
# version_added: "2.4"
INJECT_FACTS_AS_VARS:
default: True
description:
- Facts are available inside the `ansible_facts` variable, this setting also pushes them as their own vars in the main namespace.
- Unlike inside the `ansible_facts` dictionary, these will have an `ansible_` prefix.
env: [{name: ANSIBLE_INJECT_FACT_VARS}]
ini:
- {key: inject_facts_as_vars, section: defaults}
type: boolean
version_added: "2.5"
PARAMIKO_HOST_KEY_AUTO_ADD:
# TODO: move to plugin
default: False

View file

@ -99,6 +99,61 @@ TREE_DIR = None
VAULT_VERSION_MIN = 1.0
VAULT_VERSION_MAX = 1.0
# FIXME: remove once play_context mangling is removed
MAGIC_VARIABLE_MAPPING = dict(
# base
connection=('ansible_connection', ),
module_compression=('ansible_module_compression', ),
shell=('ansible_shell_type', ),
executable=('ansible_shell_executable', ),
remote_tmp_dir=('ansible_remote_tmp', ),
# connection common
remote_addr=('ansible_ssh_host', 'ansible_host'),
remote_user=('ansible_ssh_user', 'ansible_user'),
password=('ansible_ssh_pass', 'ansible_password'),
port=('ansible_ssh_port', 'ansible_port'),
pipelining=('ansible_ssh_pipelining', 'ansible_pipelining'),
timeout=('ansible_ssh_timeout', 'ansible_timeout'),
private_key_file=('ansible_ssh_private_key_file', 'ansible_private_key_file'),
# networking modules
network_os=('ansible_network_os', ),
connection_user=('ansible_connection_user',),
# ssh TODO: remove
ssh_executable=('ansible_ssh_executable', ),
ssh_common_args=('ansible_ssh_common_args', ),
sftp_extra_args=('ansible_sftp_extra_args', ),
scp_extra_args=('ansible_scp_extra_args', ),
ssh_extra_args=('ansible_ssh_extra_args', ),
ssh_transfer_method=('ansible_ssh_transfer_method', ),
# docker TODO: remove
docker_extra_args=('ansible_docker_extra_args', ),
# become
become=('ansible_become', ),
become_method=('ansible_become_method', ),
become_user=('ansible_become_user', ),
become_pass=('ansible_become_password', 'ansible_become_pass'),
become_exe=('ansible_become_exe', ),
become_flags=('ansible_become_flags', ),
# deprecated
sudo=('ansible_sudo', ),
sudo_user=('ansible_sudo_user', ),
sudo_pass=('ansible_sudo_password', 'ansible_sudo_pass'),
sudo_exe=('ansible_sudo_exe', ),
sudo_flags=('ansible_sudo_flags', ),
su=('ansible_su', ),
su_user=('ansible_su_user', ),
su_pass=('ansible_su_password', 'ansible_su_pass'),
su_exe=('ansible_su_exe', ),
su_flags=('ansible_su_flags', ),
)
# POPULATE SETTINGS FROM CONFIG ###
config = ConfigManager()

View file

@ -33,6 +33,7 @@ from ansible.plugins.connection import ConnectionBase
from ansible.template import Templar
from ansible.utils.listify import listify_lookup_plugin_terms
from ansible.utils.unsafe_proxy import UnsafeProxy, wrap_var
from ansible.vars.clean import namespace_facts, clean_facts
try:
from __main__ import display
@ -574,7 +575,9 @@ class TaskExecutor:
return failed_when_result
if 'ansible_facts' in result:
vars_copy.update(result['ansible_facts'])
vars_copy.update(namespace_facts(result['ansible_facts']))
if C.INJECT_FACTS_AS_VARS:
vars_copy.update(clean_facts(result['ansible_facts']))
# set the failed property if it was missing.
if 'failed' not in result:
@ -620,7 +623,9 @@ class TaskExecutor:
variables[self._task.register] = wrap_var(result)
if 'ansible_facts' in result:
variables.update(result['ansible_facts'])
variables.update(namespace_facts(result['ansible_facts']))
if C.INJECT_FACTS_AS_VARS:
variables.update(clean_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

View file

@ -8,7 +8,7 @@ __metaclass__ = type
from copy import deepcopy
from ansible.parsing.dataloader import DataLoader
from ansible.vars.manager import strip_internal_keys
from ansible.vars.clean import strip_internal_keys
_IGNORE = ('failed', 'skipped')

View file

@ -72,8 +72,7 @@ def ansible_facts(module, gather_subset=None):
all_collector_classes = default_collectors.collectors
# don't add a prefix
namespace = PrefixFactNamespace(namespace_name='ansible',
prefix='')
namespace = PrefixFactNamespace(namespace_name='ansible', prefix='')
fact_collector = \
ansible_collector.get_ansible_collector(all_collector_classes=all_collector_classes,

View file

@ -54,59 +54,6 @@ __all__ = ['PlayContext']
# object. The dictionary values are tuples, to account for aliases
# in variable names.
MAGIC_VARIABLE_MAPPING = dict(
# base
connection=('ansible_connection', ),
module_compression=('ansible_module_compression', ),
shell=('ansible_shell_type', ),
executable=('ansible_shell_executable', ),
remote_tmp_dir=('ansible_remote_tmp', ),
# connection common
remote_addr=('ansible_ssh_host', 'ansible_host'),
remote_user=('ansible_ssh_user', 'ansible_user'),
password=('ansible_ssh_pass', 'ansible_password'),
port=('ansible_ssh_port', 'ansible_port'),
pipelining=('ansible_ssh_pipelining', 'ansible_pipelining'),
timeout=('ansible_ssh_timeout', 'ansible_timeout'),
private_key_file=('ansible_ssh_private_key_file', 'ansible_private_key_file'),
# networking modules
network_os=('ansible_network_os', ),
connection_user=('ansible_connection_user',),
# ssh TODO: remove
ssh_executable=('ansible_ssh_executable', ),
ssh_common_args=('ansible_ssh_common_args', ),
sftp_extra_args=('ansible_sftp_extra_args', ),
scp_extra_args=('ansible_scp_extra_args', ),
ssh_extra_args=('ansible_ssh_extra_args', ),
ssh_transfer_method=('ansible_ssh_transfer_method', ),
# docker TODO: remove
docker_extra_args=('ansible_docker_extra_args', ),
# become
become=('ansible_become', ),
become_method=('ansible_become_method', ),
become_user=('ansible_become_user', ),
become_pass=('ansible_become_password', 'ansible_become_pass'),
become_exe=('ansible_become_exe', ),
become_flags=('ansible_become_flags', ),
# deprecated
sudo=('ansible_sudo', ),
sudo_user=('ansible_sudo_user', ),
sudo_pass=('ansible_sudo_password', 'ansible_sudo_pass'),
sudo_exe=('ansible_sudo_exe', ),
sudo_flags=('ansible_sudo_flags', ),
su=('ansible_su', ),
su_user=('ansible_su_user', ),
su_pass=('ansible_su_password', 'ansible_su_pass'),
su_exe=('ansible_su_exe', ),
su_flags=('ansible_su_flags', ),
)
# TODO: needs to be configurable
b_SU_PROMPT_LOCALIZATIONS = [
@ -382,7 +329,7 @@ class PlayContext(Base):
delegated_vars = variables.get('ansible_delegated_vars', dict()).get(delegated_host_name, dict())
delegated_transport = C.DEFAULT_TRANSPORT
for transport_var in MAGIC_VARIABLE_MAPPING.get('connection'):
for transport_var in C.MAGIC_VARIABLE_MAPPING.get('connection'):
if transport_var in delegated_vars:
delegated_transport = delegated_vars[transport_var]
break
@ -391,7 +338,7 @@ class PlayContext(Base):
# address, otherwise we default to connecting to it by name. This
# may happen when users put an IP entry into their inventory, or if
# they rely on DNS for a non-inventory hostname
for address_var in ('ansible_%s_host' % transport_var,) + MAGIC_VARIABLE_MAPPING.get('remote_addr'):
for address_var in ('ansible_%s_host' % transport_var,) + C.MAGIC_VARIABLE_MAPPING.get('remote_addr'):
if address_var in delegated_vars:
break
else:
@ -400,7 +347,7 @@ class PlayContext(Base):
# reset the port back to the default if none was specified, to prevent
# the delegated host from inheriting the original host's setting
for port_var in ('ansible_%s_port' % transport_var,) + MAGIC_VARIABLE_MAPPING.get('port'):
for port_var in ('ansible_%s_port' % transport_var,) + C.MAGIC_VARIABLE_MAPPING.get('port'):
if port_var in delegated_vars:
break
else:
@ -410,7 +357,7 @@ class PlayContext(Base):
delegated_vars['ansible_port'] = C.DEFAULT_REMOTE_PORT
# and likewise for the remote user
for user_var in ('ansible_%s_user' % transport_var,) + MAGIC_VARIABLE_MAPPING.get('remote_user'):
for user_var in ('ansible_%s_user' % transport_var,) + C.MAGIC_VARIABLE_MAPPING.get('remote_user'):
if user_var in delegated_vars and delegated_vars[user_var]:
break
else:
@ -419,12 +366,12 @@ class PlayContext(Base):
delegated_vars = dict()
# setup shell
for exe_var in MAGIC_VARIABLE_MAPPING.get('executable'):
for exe_var in C.MAGIC_VARIABLE_MAPPING.get('executable'):
if exe_var in variables:
setattr(new_info, 'executable', variables.get(exe_var))
attrs_considered = []
for (attr, variable_names) in iteritems(MAGIC_VARIABLE_MAPPING):
for (attr, variable_names) in iteritems(C.MAGIC_VARIABLE_MAPPING):
for variable_name in variable_names:
if attr in attrs_considered:
continue
@ -447,17 +394,17 @@ class PlayContext(Base):
# become legacy updates -- from inventory file (inventory overrides
# commandline)
for become_pass_name in MAGIC_VARIABLE_MAPPING.get('become_pass'):
for become_pass_name in C.MAGIC_VARIABLE_MAPPING.get('become_pass'):
if become_pass_name in variables:
break
else: # This is a for-else
if new_info.become_method == 'sudo':
for sudo_pass_name in MAGIC_VARIABLE_MAPPING.get('sudo_pass'):
for sudo_pass_name in C.MAGIC_VARIABLE_MAPPING.get('sudo_pass'):
if sudo_pass_name in variables:
setattr(new_info, 'become_pass', variables[sudo_pass_name])
break
elif new_info.become_method == 'su':
for su_pass_name in MAGIC_VARIABLE_MAPPING.get('su_pass'):
for su_pass_name in C.MAGIC_VARIABLE_MAPPING.get('su_pass'):
if su_pass_name in variables:
setattr(new_info, 'become_pass', variables[su_pass_name])
break
@ -471,7 +418,7 @@ class PlayContext(Base):
# in the event that we were using local before make sure to reset the
# connection type to the default transport for the delegated-to host,
# if not otherwise specified
for connection_type in MAGIC_VARIABLE_MAPPING.get('connection'):
for connection_type in C.MAGIC_VARIABLE_MAPPING.get('connection'):
if connection_type in delegated_vars:
break
else:
@ -636,7 +583,7 @@ class PlayContext(Base):
In case users need to access from the play, this is a legacy from runner.
'''
for prop, var_list in MAGIC_VARIABLE_MAPPING.items():
for prop, var_list in C.MAGIC_VARIABLE_MAPPING.items():
try:
if 'become' in prop:
continue

View file

@ -37,10 +37,9 @@ from ansible.module_utils.six import binary_type, string_types, text_type, iteri
from ansible.module_utils.six.moves import shlex_quote
from ansible.module_utils._text import to_bytes, to_native, to_text
from ansible.parsing.utils.jsonify import jsonify
from ansible.playbook.play_context import MAGIC_VARIABLE_MAPPING
from ansible.release import __version__
from ansible.utils.unsafe_proxy import wrap_var
from ansible.vars.manager import remove_internal_keys
from ansible.vars.clean import remove_internal_keys
try:
@ -765,49 +764,6 @@ class ActionBase(with_metaclass(ABCMeta, object)):
display.debug("done with _execute_module (%s, %s)" % (module_name, module_args))
return data
def _clean_returned_data(self, data):
remove_keys = set()
fact_keys = set(data.keys())
# first we add all of our magic variable names to the set of
# keys we want to remove from facts
for magic_var in MAGIC_VARIABLE_MAPPING:
remove_keys.update(fact_keys.intersection(MAGIC_VARIABLE_MAPPING[magic_var]))
# next we remove any connection plugin specific vars
for conn_path in self._shared_loader_obj.connection_loader.all(path_only=True):
try:
conn_name = os.path.splitext(os.path.basename(conn_path))[0]
re_key = re.compile('^ansible_%s_' % conn_name)
for fact_key in fact_keys:
# exception for lvm tech, whic normally returns asnible_x_bridge facts that get filterd out (docker,lxc, etc)
if re_key.match(fact_key) and not fact_key.endswith(('_bridge', '_gwbridge')):
remove_keys.add(fact_key)
except AttributeError:
pass
# remove some KNOWN keys
for hard in C.RESTRICTED_RESULT_KEYS + C.INTERNAL_RESULT_KEYS:
if hard in fact_keys:
remove_keys.add(hard)
# finally, we search for interpreter keys to remove
re_interp = re.compile('^ansible_.*_interpreter$')
for fact_key in fact_keys:
if re_interp.match(fact_key):
remove_keys.add(fact_key)
# then we remove them (except for ssh host keys)
for r_key in remove_keys:
if not r_key.startswith('ansible_ssh_host_key_'):
try:
r_val = to_text(data[r_key])
if len(r_val) > 24:
r_val = '%s ... %s' % (r_val[:13], r_val[-6:])
except:
r_val = ' <failed to convert value to a string> '
display.warning("Removed restricted key from module data: %s = %s" % (r_key, r_val))
del data[r_key]
remove_internal_keys(data)
def _parse_returned_data(self, res):
try:
filtered_output, warnings = _filter_non_json_lines(res.get('stdout', u''))
@ -817,7 +773,6 @@ class ActionBase(with_metaclass(ABCMeta, object)):
data = json.loads(filtered_output)
if 'ansible_facts' in data and isinstance(data['ansible_facts'], dict):
self._clean_returned_data(data['ansible_facts'])
data['ansible_facts'] = wrap_var(data['ansible_facts'])
data['_ansible_parsed'] = True
except ValueError:

View file

@ -43,9 +43,9 @@ class ActionModule(ActionBase):
if module == 'auto':
try:
if self._task.delegate_to: # if we delegate, we should use delegated host's facts
module = self._templar.template("{{hostvars['%s']['ansible_pkg_mgr']}}" % self._task.delegate_to)
module = self._templar.template("{{hostvars['%s']['ansible_facts']['pkg_mgr']}}" % self._task.delegate_to)
else:
module = self._templar.template('{{ansible_pkg_mgr}}')
module = self._templar.template('{{ansible_facts.pkg_mgr}}')
except:
pass # could not get it from template!

View file

@ -42,9 +42,9 @@ class ActionModule(ActionBase):
if module == 'auto':
try:
if self._task.delegate_to: # if we delegate, we should use delegated host's facts
module = self._templar.template("{{hostvars['%s']['ansible_service_mgr']}}" % self._task.delegate_to)
module = self._templar.template("{{hostvars['%s']['ansible_facts']['service_mgr']}}" % self._task.delegate_to)
else:
module = self._templar.template('{{ansible_service_mgr}}')
module = self._templar.template('{{ansible_facts.service_mgr}}')
except:
pass # could not get it from template!

View file

@ -24,7 +24,6 @@ from ansible import constants as C
from ansible.module_utils.six import string_types
from ansible.module_utils._text import to_text
from ansible.module_utils.parsing.convert_bool import boolean
from ansible.playbook.play_context import MAGIC_VARIABLE_MAPPING
from ansible.plugins.action import ActionBase
from ansible.plugins.loader import connection_loader
@ -223,7 +222,7 @@ class ActionModule(ActionBase):
localhost_ports = set()
for host in C.LOCALHOST:
localhost_vars = task_vars['hostvars'].get(host, {})
for port_var in MAGIC_VARIABLE_MAPPING['port']:
for port_var in C.MAGIC_VARIABLE_MAPPING['port']:
port = localhost_vars.get(port_var, None)
if port:
break
@ -271,7 +270,7 @@ class ActionModule(ActionBase):
localhost_shell = None
for host in C.LOCALHOST:
localhost_vars = task_vars['hostvars'].get(host, {})
for shell_var in MAGIC_VARIABLE_MAPPING['shell']:
for shell_var in C.MAGIC_VARIABLE_MAPPING['shell']:
localhost_shell = localhost_vars.get(shell_var, None)
if localhost_shell:
break
@ -285,7 +284,7 @@ class ActionModule(ActionBase):
localhost_executable = None
for host in C.LOCALHOST:
localhost_vars = task_vars['hostvars'].get(host, {})
for executable_var in MAGIC_VARIABLE_MAPPING['executable']:
for executable_var in C.MAGIC_VARIABLE_MAPPING['executable']:
localhost_executable = localhost_vars.get(executable_var, None)
if localhost_executable:
break

View file

@ -29,7 +29,7 @@ from ansible import constants as C
from ansible.plugins import AnsiblePlugin
from ansible.module_utils._text import to_text
from ansible.utils.color import stringc
from ansible.vars.manager import strip_internal_keys
from ansible.vars.clean import strip_internal_keys
try:
from __main__ import display as global_display

View file

@ -43,7 +43,7 @@ from ansible.playbook.role_include import IncludeRole
from ansible.plugins.loader import action_loader, connection_loader, filter_loader, lookup_loader, module_loader, test_loader
from ansible.template import Templar
from ansible.utils.vars import combine_vars
from ansible.vars.manager import strip_internal_keys
from ansible.vars.clean import strip_internal_keys
try:

128
lib/ansible/vars/clean.py Normal file
View file

@ -0,0 +1,128 @@
# Copyright (c) 2017 Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
# Make coding more python3-ish
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import os
import re
from copy import deepcopy
from ansible import constants as C
from ansible.module_utils._text import to_text
from ansible.module_utils.six import string_types
from ansible.plugins.loader import connection_loader
try:
from __main__ import display
except ImportError:
from ansible.utils.display import Display
display = Display()
def strip_internal_keys(dirty, exceptions=None):
'''
All keys stating with _ansible_ are internal, so create a copy of the 'dirty' dict
and remove them from the clean one before returning it
'''
if exceptions is None:
exceptions = ()
clean = dirty.copy()
for k in dirty.keys():
if isinstance(k, string_types) and k.startswith('_ansible_'):
if k not in exceptions:
del clean[k]
elif isinstance(dirty[k], dict):
clean[k] = strip_internal_keys(dirty[k])
return clean
def remove_internal_keys(data):
'''
More nuanced version of strip_internal_keys
'''
for key in list(data.keys()):
if (key.startswith('_ansible_') and key != '_ansible_parsed') or key in C.INTERNAL_RESULT_KEYS:
display.warning("Removed unexpected internal key in module return: %s = %s" % (key, data[key]))
del data[key]
# remove bad/empty internal keys
for key in ['warnings', 'deprecations']:
if key in data and not data[key]:
del data[key]
def clean_facts(facts):
''' remove facts that can override internal keys or othewise deemed unsafe '''
data = deepcopy(facts)
remove_keys = set()
fact_keys = set(data.keys())
# first we add all of our magic variable names to the set of
# keys we want to remove from facts
for magic_var in C.MAGIC_VARIABLE_MAPPING:
remove_keys.update(fact_keys.intersection(C.MAGIC_VARIABLE_MAPPING[magic_var]))
# next we remove any connection plugin specific vars
for conn_path in connection_loader.all(path_only=True):
try:
conn_name = os.path.splitext(os.path.basename(conn_path))[0]
re_key = re.compile('^ansible_%s_' % conn_name)
for fact_key in fact_keys:
# exception for lvm tech, whic normally returns asnible_x_bridge facts that get filterd out (docker,lxc, etc)
if re_key.match(fact_key) and not fact_key.endswith(('_bridge', '_gwbridge')):
remove_keys.add(fact_key)
except AttributeError:
pass
# remove some KNOWN keys
for hard in C.RESTRICTED_RESULT_KEYS + C.INTERNAL_RESULT_KEYS:
if hard in fact_keys:
remove_keys.add(hard)
# finally, we search for interpreter keys to remove
re_interp = re.compile('^ansible_.*_interpreter$')
for fact_key in fact_keys:
if re_interp.match(fact_key):
remove_keys.add(fact_key)
# then we remove them (except for ssh host keys)
for r_key in remove_keys:
if not r_key.startswith('ansible_ssh_host_key_'):
try:
r_val = to_text(data[r_key])
if len(r_val) > 24:
r_val = '%s ... %s' % (r_val[:13], r_val[-6:])
except Exception:
r_val = ' <failed to convert value to a string> '
display.warning("Removed restricted key from module data: %s = %s" % (r_key, r_val))
del data[r_key]
return strip_internal_keys(data)
def inject_facts(facts):
''' return clean facts inside with an ansible_ prefix '''
injected = {}
for k in facts:
if k.startswith('ansible_') or k == 'module_setup':
new = k
else:
new = 'ansilbe_%s' % k
injected[new] = deepcopy(facts[k])
return clean_facts(injected)
def namespace_facts(facts):
''' return all facts inside 'ansible_facts' w/o an ansible_ prefix '''
deprefixed = {}
for k in facts:
if k in ('ansible_local'):
# exceptions to 'deprefixing'
deprefixed[k] = deepcopy(facts[k])
else:
deprefixed[k.replace('ansible_', '', 1)] = deepcopy(facts[k])
return {'ansible_facts': deprefixed}

View file

@ -36,13 +36,14 @@ from ansible.errors import AnsibleError, AnsibleParserError, AnsibleUndefinedVar
from ansible.inventory.host import Host
from ansible.inventory.helpers import sort_groups, get_group_vars
from ansible.module_utils._text import to_native
from ansible.module_utils.six import iteritems, string_types, text_type
from ansible.module_utils.six import iteritems, text_type
from ansible.plugins.loader import lookup_loader, vars_loader
from ansible.plugins.cache import FactCache
from ansible.template import Templar
from ansible.utils.listify import listify_lookup_plugin_terms
from ansible.utils.vars import combine_vars
from ansible.utils.unsafe_proxy import wrap_var
from ansible.vars.clean import namespace_facts, clean_facts
try:
from __main__ import display
@ -72,39 +73,6 @@ def preprocess_vars(a):
return data
def strip_internal_keys(dirty, exceptions=None):
'''
All keys stating with _ansible_ are internal, so create a copy of the 'dirty' dict
and remove them from the clean one before returning it
'''
if exceptions is None:
exceptions = ()
clean = dirty.copy()
for k in dirty.keys():
if isinstance(k, string_types) and k.startswith('_ansible_'):
if k not in exceptions:
del clean[k]
elif isinstance(dirty[k], dict):
clean[k] = strip_internal_keys(dirty[k])
return clean
def remove_internal_keys(data):
'''
More nuanced version of strip_internal_keys
'''
for key in list(data.keys()):
if (key.startswith('_ansible_') and key != '_ansible_parsed') or key in C.INTERNAL_RESULT_KEYS:
display.warning("Removed unexpected internal key in module return: %s = %s" % (key, data[key]))
del data[key]
# remove bad/empty internal keys
for key in ['warnings', 'deprecations']:
if key in data and not data[key]:
del data[key]
class VariableManager:
_ALLOWED = frozenset(['plugins_by_group', 'groups_plugins_play', 'groups_plugins_inventory', 'groups_inventory',
@ -351,10 +319,15 @@ class VariableManager:
# finally, the facts caches for this host, if it exists
try:
host_facts = wrap_var(self._fact_cache.get(host.name, {}))
facts = self._fact_cache.get(host.name, {})
all_vars.update(namespace_facts(facts))
# push facts to main namespace
all_vars = combine_vars(all_vars, host_facts)
if C.INJECT_FACTS_AS_VARS:
all_vars = combine_vars(all_vars, wrap_var(facts))
else:
# always 'promote' ansible_local
all_vars = combine_vars(all_vars, wrap_var({'ansible_local': facts.get('ansible_local', {})}))
except KeyError:
pass
@ -431,7 +404,9 @@ class VariableManager:
# next, we merge in the vars cache (include vars) and nonpersistent
# facts cache (set_fact/register), in that order
if host:
# include_vars non-persistent cache
all_vars = combine_vars(all_vars, self._vars_cache.get(host.get_name(), dict()))
# fact non-persistent cache
all_vars = combine_vars(all_vars, self._nonpersistent_fact_cache.get(host.name, dict()))
# next, we merge in role params and task include params

View file

@ -33,6 +33,7 @@ from ansible.module_utils._text import to_bytes
from ansible.playbook.play_context import PlayContext
from ansible.plugins.action import ActionBase
from ansible.template import Templar
from ansible.vars.clean import clean_facts
from units.mock.loader import DictDataLoader
@ -550,7 +551,7 @@ class TestActionBaseCleanReturnedData(unittest.TestCase):
'ansible_ssh_some_var': 'whatever',
'ansible_ssh_host_key_somehost': 'some key here',
'some_other_var': 'foo bar'}
action_base._clean_returned_data(data)
data = clean_facts(data)
self.assertNotIn('ansible_playbook_python', data)
self.assertNotIn('ansible_python_interpreter', data)
self.assertIn('ansible_ssh_host_key_somehost', data)