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:
parent
e0cb54a2aa
commit
db749de5b8
15 changed files with 236 additions and 175 deletions
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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')
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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!
|
||||
|
||||
|
|
|
@ -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!
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
128
lib/ansible/vars/clean.py
Normal 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}
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue