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

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