Track source of vars
This commit is contained in:
parent
009225dfc6
commit
45713aa38f
1 changed files with 92 additions and 23 deletions
|
@ -179,6 +179,20 @@ class VariableManager:
|
||||||
_hosts_all=_hosts_all,
|
_hosts_all=_hosts_all,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
_vars_sources = {}
|
||||||
|
|
||||||
|
def _combine_and_track(data, new_data, source):
|
||||||
|
'''
|
||||||
|
Wrapper function to update var sources dict and call combine_vars()
|
||||||
|
|
||||||
|
See notes in the VarsWithSources docstring for caveats and limitations of the source tracking
|
||||||
|
'''
|
||||||
|
if C.DEFAULT_DEBUG:
|
||||||
|
# Populate var sources dict
|
||||||
|
for key in new_data:
|
||||||
|
_vars_sources[key] = source
|
||||||
|
return combine_vars(data, new_data)
|
||||||
|
|
||||||
# default for all cases
|
# default for all cases
|
||||||
basedirs = []
|
basedirs = []
|
||||||
if self.safe_basedir: # avoid adhoc/console loading cwd
|
if self.safe_basedir: # avoid adhoc/console loading cwd
|
||||||
|
@ -188,7 +202,7 @@ class VariableManager:
|
||||||
# first we compile any vars specified in defaults/main.yml
|
# first we compile any vars specified in defaults/main.yml
|
||||||
# for all roles within the specified play
|
# for all roles within the specified play
|
||||||
for role in play.get_roles():
|
for role in play.get_roles():
|
||||||
all_vars = combine_vars(all_vars, role.get_default_vars())
|
all_vars = _combine_and_track(all_vars, role.get_default_vars(), "role '%s' defaults" % role.name)
|
||||||
|
|
||||||
if task:
|
if task:
|
||||||
# set basedirs
|
# set basedirs
|
||||||
|
@ -204,7 +218,8 @@ class VariableManager:
|
||||||
# sure it sees its defaults above any other roles, as we previously
|
# sure it sees its defaults above any other roles, as we previously
|
||||||
# (v1) made sure each task had a copy of its roles default vars
|
# (v1) made sure each task had a copy of its roles default vars
|
||||||
if task._role is not None and (play or task.action == 'include_role'):
|
if task._role is not None and (play or task.action == 'include_role'):
|
||||||
all_vars = combine_vars(all_vars, task._role.get_default_vars(dep_chain=task.get_dep_chain()))
|
all_vars = _combine_and_track(all_vars, task._role.get_default_vars(dep_chain=task.get_dep_chain()),
|
||||||
|
"role '%s' defaults" % task._role.name)
|
||||||
|
|
||||||
if host:
|
if host:
|
||||||
# THE 'all' group and the rest of groups for a host, used below
|
# THE 'all' group and the rest of groups for a host, used below
|
||||||
|
@ -238,7 +253,7 @@ class VariableManager:
|
||||||
''' merges all entities adjacent to play '''
|
''' merges all entities adjacent to play '''
|
||||||
data = {}
|
data = {}
|
||||||
for path in basedirs:
|
for path in basedirs:
|
||||||
data = combine_vars(data, get_vars_from_path(self._loader, path, entities, stage))
|
data = _combine_and_track(data, get_vars_from_path(self._loader, path, entities, stage), "path '%s'" % path)
|
||||||
return data
|
return data
|
||||||
|
|
||||||
# configurable functions that are sortable via config, remember to add to _ALLOWED if expanding this list
|
# configurable functions that are sortable via config, remember to add to _ALLOWED if expanding this list
|
||||||
|
@ -270,8 +285,8 @@ class VariableManager:
|
||||||
'''
|
'''
|
||||||
data = {}
|
data = {}
|
||||||
for group in host_groups:
|
for group in host_groups:
|
||||||
data[group] = combine_vars(data[group], _plugins_inventory(group))
|
data[group] = _combine_and_track(data[group], _plugins_inventory(group), "inventory group_vars for '%s'" % group)
|
||||||
data[group] = combine_vars(data[group], _plugins_play(group))
|
data[group] = _combine_and_track(data[group], _plugins_play(group), "playbook group_vars for '%s'" % group)
|
||||||
return data
|
return data
|
||||||
|
|
||||||
# Merge groups as per precedence config
|
# Merge groups as per precedence config
|
||||||
|
@ -279,14 +294,14 @@ class VariableManager:
|
||||||
for entry in C.VARIABLE_PRECEDENCE:
|
for entry in C.VARIABLE_PRECEDENCE:
|
||||||
if entry in self._ALLOWED:
|
if entry in self._ALLOWED:
|
||||||
display.debug('Calling %s to load vars for %s' % (entry, host.name))
|
display.debug('Calling %s to load vars for %s' % (entry, host.name))
|
||||||
all_vars = combine_vars(all_vars, locals()[entry]())
|
all_vars = _combine_and_track(all_vars, locals()[entry](), "group vars, precedence entry '%s'" % entry)
|
||||||
else:
|
else:
|
||||||
display.warning('Ignoring unknown variable precedence entry: %s' % (entry))
|
display.warning('Ignoring unknown variable precedence entry: %s' % (entry))
|
||||||
|
|
||||||
# host vars, from inventory, inventory adjacent and play adjacent via plugins
|
# host vars, from inventory, inventory adjacent and play adjacent via plugins
|
||||||
all_vars = combine_vars(all_vars, host.get_vars())
|
all_vars = _combine_and_track(all_vars, host.get_vars(), "host vars for '%s'" % host)
|
||||||
all_vars = combine_vars(all_vars, _plugins_inventory([host]))
|
all_vars = _combine_and_track(all_vars, _plugins_inventory([host]), "inventory host_vars for '%s'" % host)
|
||||||
all_vars = combine_vars(all_vars, _plugins_play([host]))
|
all_vars = _combine_and_track(all_vars, _plugins_play([host]), "playbook host_vars for '%s'" % host)
|
||||||
|
|
||||||
# finally, the facts caches for this host, if it exists
|
# finally, the facts caches for this host, if it exists
|
||||||
# TODO: cleaning of facts should eventually become part of taskresults instead of vars
|
# TODO: cleaning of facts should eventually become part of taskresults instead of vars
|
||||||
|
@ -296,15 +311,15 @@ class VariableManager:
|
||||||
|
|
||||||
# push facts to main namespace
|
# push facts to main namespace
|
||||||
if C.INJECT_FACTS_AS_VARS:
|
if C.INJECT_FACTS_AS_VARS:
|
||||||
all_vars = combine_vars(all_vars, wrap_var(clean_facts(facts)))
|
all_vars = _combine_and_track(all_vars, wrap_var(clean_facts(facts)), "facts")
|
||||||
else:
|
else:
|
||||||
# always 'promote' ansible_local
|
# always 'promote' ansible_local
|
||||||
all_vars = combine_vars(all_vars, wrap_var({'ansible_local': facts.get('ansible_local', {})}))
|
all_vars = _combine_and_track(all_vars, wrap_var({'ansible_local': facts.get('ansible_local', {})}), "facts")
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if play:
|
if play:
|
||||||
all_vars = combine_vars(all_vars, play.get_vars())
|
all_vars = _combine_and_track(all_vars, play.get_vars(), "play vars")
|
||||||
|
|
||||||
vars_files = play.get_vars_files()
|
vars_files = play.get_vars_files()
|
||||||
try:
|
try:
|
||||||
|
@ -338,7 +353,7 @@ class VariableManager:
|
||||||
data = preprocess_vars(self._loader.load_from_file(vars_file, unsafe=True))
|
data = preprocess_vars(self._loader.load_from_file(vars_file, unsafe=True))
|
||||||
if data is not None:
|
if data is not None:
|
||||||
for item in data:
|
for item in data:
|
||||||
all_vars = combine_vars(all_vars, item)
|
all_vars = _combine_and_track(all_vars, item, "play vars_files from '%s'" % vars_file)
|
||||||
break
|
break
|
||||||
except AnsibleFileNotFound:
|
except AnsibleFileNotFound:
|
||||||
# we continue on loader failures
|
# we continue on loader failures
|
||||||
|
@ -369,39 +384,40 @@ class VariableManager:
|
||||||
# unless the user has disabled this via a config option
|
# unless the user has disabled this via a config option
|
||||||
if not C.DEFAULT_PRIVATE_ROLE_VARS:
|
if not C.DEFAULT_PRIVATE_ROLE_VARS:
|
||||||
for role in play.get_roles():
|
for role in play.get_roles():
|
||||||
all_vars = combine_vars(all_vars, role.get_vars(include_params=False))
|
all_vars = _combine_and_track(all_vars, role.get_vars(include_params=False), "role '%s' vars" % role.name)
|
||||||
|
|
||||||
# next, we merge in the vars from the role, which will specifically
|
# next, we merge in the vars from the role, which will specifically
|
||||||
# follow the role dependency chain, and then we merge in the tasks
|
# follow the role dependency chain, and then we merge in the tasks
|
||||||
# vars (which will look at parent blocks/task includes)
|
# vars (which will look at parent blocks/task includes)
|
||||||
if task:
|
if task:
|
||||||
if task._role:
|
if task._role:
|
||||||
all_vars = combine_vars(all_vars, task._role.get_vars(task.get_dep_chain(), include_params=False))
|
all_vars = _combine_and_track(all_vars, task._role.get_vars(task.get_dep_chain(), include_params=False),
|
||||||
all_vars = combine_vars(all_vars, task.get_vars())
|
"role '%s' vars" % task._role.name)
|
||||||
|
all_vars = _combine_and_track(all_vars, task.get_vars(), "task vars")
|
||||||
|
|
||||||
# next, we merge in the vars cache (include vars) and nonpersistent
|
# next, we merge in the vars cache (include vars) and nonpersistent
|
||||||
# facts cache (set_fact/register), in that order
|
# facts cache (set_fact/register), in that order
|
||||||
if host:
|
if host:
|
||||||
# include_vars non-persistent cache
|
# include_vars non-persistent cache
|
||||||
all_vars = combine_vars(all_vars, self._vars_cache.get(host.get_name(), dict()))
|
all_vars = _combine_and_track(all_vars, self._vars_cache.get(host.get_name(), dict()), "include_vars")
|
||||||
# fact non-persistent cache
|
# fact non-persistent cache
|
||||||
all_vars = combine_vars(all_vars, self._nonpersistent_fact_cache.get(host.name, dict()))
|
all_vars = _combine_and_track(all_vars, self._nonpersistent_fact_cache.get(host.name, dict()), "set_fact")
|
||||||
|
|
||||||
# next, we merge in role params and task include params
|
# next, we merge in role params and task include params
|
||||||
if task:
|
if task:
|
||||||
if task._role:
|
if task._role:
|
||||||
all_vars = combine_vars(all_vars, task._role.get_role_params(task.get_dep_chain()))
|
all_vars = _combine_and_track(all_vars, task._role.get_role_params(task.get_dep_chain()), "role '%s' params" % task._role.name)
|
||||||
|
|
||||||
# special case for include tasks, where the include params
|
# special case for include tasks, where the include params
|
||||||
# may be specified in the vars field for the task, which should
|
# may be specified in the vars field for the task, which should
|
||||||
# have higher precedence than the vars/np facts above
|
# have higher precedence than the vars/np facts above
|
||||||
all_vars = combine_vars(all_vars, task.get_include_params())
|
all_vars = _combine_and_track(all_vars, task.get_include_params(), "include params")
|
||||||
|
|
||||||
# extra vars
|
# extra vars
|
||||||
all_vars = combine_vars(all_vars, self._extra_vars)
|
all_vars = _combine_and_track(all_vars, self._extra_vars, "extra vars")
|
||||||
|
|
||||||
# magic variables
|
# magic variables
|
||||||
all_vars = combine_vars(all_vars, magic_variables)
|
all_vars = _combine_and_track(all_vars, magic_variables, "magic vars")
|
||||||
|
|
||||||
# special case for the 'environment' magic variable, as someone
|
# special case for the 'environment' magic variable, as someone
|
||||||
# may have set it as a variable and we don't want to stomp on it
|
# may have set it as a variable and we don't want to stomp on it
|
||||||
|
@ -419,7 +435,11 @@ class VariableManager:
|
||||||
all_vars['vars'] = all_vars.copy()
|
all_vars['vars'] = all_vars.copy()
|
||||||
|
|
||||||
display.debug("done with get_vars()")
|
display.debug("done with get_vars()")
|
||||||
return all_vars
|
if C.DEFAULT_DEBUG:
|
||||||
|
# Use VarsWithSources wrapper class to display var sources
|
||||||
|
return VarsWithSources.new_vars_with_sources(all_vars, _vars_sources)
|
||||||
|
else:
|
||||||
|
return all_vars
|
||||||
|
|
||||||
def _get_magic_variables(self, play, host, task, include_hostvars, include_delegate_to,
|
def _get_magic_variables(self, play, host, task, include_hostvars, include_delegate_to,
|
||||||
_hosts=None, _hosts_all=None):
|
_hosts=None, _hosts_all=None):
|
||||||
|
@ -661,3 +681,52 @@ class VariableManager:
|
||||||
self._vars_cache[host] = combine_vars(self._vars_cache[host], {varname: value})
|
self._vars_cache[host] = combine_vars(self._vars_cache[host], {varname: value})
|
||||||
else:
|
else:
|
||||||
self._vars_cache[host][varname] = value
|
self._vars_cache[host][varname] = value
|
||||||
|
|
||||||
|
|
||||||
|
class VarsWithSources(MutableMapping):
|
||||||
|
'''
|
||||||
|
Dict-like class for vars that also provides source information for each var
|
||||||
|
|
||||||
|
This class can only store the source for top-level vars. It does no tracking
|
||||||
|
on its own, just shows a debug message with the information that it is provided
|
||||||
|
when a particular var is accessed.
|
||||||
|
'''
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
''' Dict-compatible constructor '''
|
||||||
|
self.data = dict(*args, **kwargs)
|
||||||
|
self.sources = {}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def new_vars_with_sources(cls, data, sources):
|
||||||
|
''' Alternate constructor method to instantiate class with sources '''
|
||||||
|
v = cls(data)
|
||||||
|
v.sources = sources
|
||||||
|
return v
|
||||||
|
|
||||||
|
def get_source(self, key):
|
||||||
|
return self.sources.get(key, None)
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
val = self.data[key]
|
||||||
|
# See notes in the VarsWithSources docstring for caveats and limitations of the source tracking
|
||||||
|
display.debug("variable '%s' from source: %s" % (key, self.sources.get(key, "unknown")))
|
||||||
|
return val
|
||||||
|
|
||||||
|
def __setitem__(self, key, value):
|
||||||
|
self.data[key] = value
|
||||||
|
|
||||||
|
def __delitem__(self, key):
|
||||||
|
del self.data[key]
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return iter(self.data)
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return len(self.data)
|
||||||
|
|
||||||
|
# Prevent duplicate debug messages by defining our own __contains__ pointing at the underlying dict
|
||||||
|
def __contains__(self, key):
|
||||||
|
return self.data.__contains__(key)
|
||||||
|
|
||||||
|
def copy(self):
|
||||||
|
return VarsWithSources.new_vars_with_sources(self.data.copy(), self.sources.copy())
|
||||||
|
|
Loading…
Add table
Reference in a new issue