mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
Several var fixes
* Fixes hostvar serialization issue (#12005) * Fixes regression in include_vars from within a role (#9498), where we had the precedence order for vars_cache (include_vars, set_fact) incorrectly before role vars. * Fixes another bug in which vars loaded from files in the format of a list instead of dictionary would cause a failure. Fixes #9498 Fixes #12005
This commit is contained in:
parent
144da7e7d1
commit
635fa0757b
2 changed files with 83 additions and 25 deletions
|
@ -84,6 +84,26 @@ class VariableManager:
|
|||
def set_inventory(self, inventory):
|
||||
self._inventory = inventory
|
||||
|
||||
def _preprocess_vars(self, a):
|
||||
'''
|
||||
Ensures that vars contained in the parameter passed in are
|
||||
returned as a list of dictionaries, to ensure for instance
|
||||
that vars loaded from a file conform to an expected state.
|
||||
'''
|
||||
|
||||
if a is None:
|
||||
return None
|
||||
elif not isinstance(a, list):
|
||||
data = [ a ]
|
||||
else:
|
||||
data = a
|
||||
|
||||
for item in data:
|
||||
if not isinstance(item, MutableMapping):
|
||||
raise AnsibleError("variable files must contain either a dictionary of variables, or a list of dictionaries. Got: %s (%s)" % (a, type(a)))
|
||||
|
||||
return data
|
||||
|
||||
def _validate_both_dicts(self, a, b):
|
||||
'''
|
||||
Validates that both arguments are dictionaries, or an error is raised.
|
||||
|
@ -127,7 +147,7 @@ class VariableManager:
|
|||
|
||||
return result
|
||||
|
||||
def get_vars(self, loader, play=None, host=None, task=None, use_cache=True):
|
||||
def get_vars(self, loader, play=None, host=None, task=None, include_hostvars=True, use_cache=True):
|
||||
'''
|
||||
Returns the variables, with optional "context" given via the parameters
|
||||
for the play, host, and task (which could possibly result in different
|
||||
|
@ -168,16 +188,22 @@ class VariableManager:
|
|||
|
||||
# we merge in the special 'all' group_vars first, if they exist
|
||||
if 'all' in self._group_vars_files:
|
||||
all_vars = self._combine_vars(all_vars, self._group_vars_files['all'])
|
||||
data = self._preprocess_vars(self._group_vars_files['all'])
|
||||
for item in data:
|
||||
all_vars = self._combine_vars(all_vars, item)
|
||||
|
||||
for group in host.get_groups():
|
||||
all_vars = self._combine_vars(all_vars, group.get_vars())
|
||||
if group.name in self._group_vars_files and group.name != 'all':
|
||||
all_vars = self._combine_vars(all_vars, self._group_vars_files[group.name])
|
||||
data = self._preprocess_vars(self._group_vars_files[group.name])
|
||||
for item in data:
|
||||
all_vars = self._combine_vars(all_vars, item)
|
||||
|
||||
host_name = host.get_name()
|
||||
if host_name in self._host_vars_files:
|
||||
all_vars = self._combine_vars(all_vars, self._host_vars_files[host_name])
|
||||
data = self._preprocess_vars(self._host_vars_files[host_name])
|
||||
for item in data:
|
||||
all_vars = self._combine_vars(all_vars, self._host_vars_files[host_name])
|
||||
|
||||
# then we merge in vars specified for this host
|
||||
all_vars = self._combine_vars(all_vars, host.get_vars())
|
||||
|
@ -209,9 +235,10 @@ class VariableManager:
|
|||
# as soon as we read one from the list. If none are found, we
|
||||
# raise an error, which is silently ignored at this point.
|
||||
for vars_file in vars_file_list:
|
||||
data = loader.load_from_file(vars_file)
|
||||
data = self._preprocess_vars(loader.load_from_file(vars_file))
|
||||
if data is not None:
|
||||
all_vars = self._combine_vars(all_vars, data)
|
||||
for item in data:
|
||||
all_vars = self._combine_vars(all_vars, item)
|
||||
break
|
||||
else:
|
||||
raise AnsibleError("vars file %s was not found" % vars_file_item)
|
||||
|
@ -222,14 +249,14 @@ class VariableManager:
|
|||
for role in play.get_roles():
|
||||
all_vars = self._combine_vars(all_vars, role.get_vars())
|
||||
|
||||
if host:
|
||||
all_vars = self._combine_vars(all_vars, self._vars_cache.get(host.get_name(), dict()))
|
||||
|
||||
if task:
|
||||
if task._role:
|
||||
all_vars = self._combine_vars(all_vars, task._role.get_vars())
|
||||
all_vars = self._combine_vars(all_vars, task.get_vars())
|
||||
|
||||
if host:
|
||||
all_vars = self._combine_vars(all_vars, self._vars_cache.get(host.get_name(), dict()))
|
||||
|
||||
all_vars = self._combine_vars(all_vars, self._extra_vars)
|
||||
|
||||
# FIXME: make sure all special vars are here
|
||||
|
@ -241,9 +268,10 @@ class VariableManager:
|
|||
all_vars['groups'] = [group.name for group in host.get_groups()]
|
||||
|
||||
if self._inventory is not None:
|
||||
hostvars = HostVars(vars_manager=self, play=play, inventory=self._inventory, loader=loader)
|
||||
all_vars['hostvars'] = hostvars
|
||||
all_vars['groups'] = self._inventory.groups_list()
|
||||
if include_hostvars:
|
||||
hostvars = HostVars(vars_manager=self, play=play, inventory=self._inventory, loader=loader)
|
||||
all_vars['hostvars'] = hostvars
|
||||
|
||||
if task:
|
||||
if task._role:
|
||||
|
|
|
@ -20,9 +20,12 @@ from __future__ import (absolute_import, division, print_function)
|
|||
__metaclass__ = type
|
||||
|
||||
import collections
|
||||
import sys
|
||||
|
||||
from jinja2 import Undefined as j2undefined
|
||||
|
||||
from ansible import constants as C
|
||||
from ansible.inventory.host import Host
|
||||
from ansible.template import Templar
|
||||
|
||||
__all__ = ['HostVars']
|
||||
|
@ -32,22 +35,47 @@ class HostVars(collections.Mapping):
|
|||
''' A special view of vars_cache that adds values from the inventory when needed. '''
|
||||
|
||||
def __init__(self, vars_manager, play, inventory, loader):
|
||||
self._vars_manager = vars_manager
|
||||
self._play = play
|
||||
self._inventory = inventory
|
||||
self._loader = loader
|
||||
self._lookup = {}
|
||||
self._lookup = {}
|
||||
self._loader = loader
|
||||
|
||||
# temporarily remove the inventory filter restriction
|
||||
# so we can compile the variables for all of the hosts
|
||||
# in inventory
|
||||
restriction = inventory._restriction
|
||||
inventory.remove_restriction()
|
||||
hosts = inventory.get_hosts()
|
||||
inventory.restrict_to_hosts(restriction)
|
||||
|
||||
# check to see if localhost is in the hosts list, as we
|
||||
# may have it referenced via hostvars but if created implicitly
|
||||
# it doesn't sow up in the hosts list
|
||||
has_localhost = False
|
||||
for host in hosts:
|
||||
if host.name in C.LOCALHOST:
|
||||
has_localhost = True
|
||||
break
|
||||
|
||||
# we don't use the method in inventory to create the implicit host,
|
||||
# because it also adds it to the 'ungrouped' group, and we want to
|
||||
# avoid any side-effects
|
||||
if not has_localhost:
|
||||
new_host = Host(name='localhost')
|
||||
new_host.set_variable("ansible_python_interpreter", sys.executable)
|
||||
new_host.set_variable("ansible_connection", "local")
|
||||
new_host.ipv4_address = '127.0.0.1'
|
||||
hosts.append(new_host)
|
||||
|
||||
for host in hosts:
|
||||
self._lookup[host.name] = vars_manager.get_vars(loader=loader, play=play, host=host, include_hostvars=False)
|
||||
|
||||
def __getitem__(self, host_name):
|
||||
|
||||
if host_name not in self._lookup:
|
||||
host = self._inventory.get_host(host_name)
|
||||
if not host:
|
||||
return j2undefined
|
||||
result = self._vars_manager.get_vars(loader=self._loader, play=self._play, host=host)
|
||||
templar = Templar(variables=result, loader=self._loader)
|
||||
self._lookup[host_name] = templar.template(result, fail_on_undefined=False)
|
||||
return self._lookup[host_name]
|
||||
return j2undefined
|
||||
|
||||
data = self._lookup.get(host_name)
|
||||
templar = Templar(variables=data, loader=self._loader)
|
||||
return templar.template(data, fail_on_undefined=False)
|
||||
|
||||
def __contains__(self, host_name):
|
||||
item = self.get(host_name)
|
||||
|
@ -62,7 +90,9 @@ class HostVars(collections.Mapping):
|
|||
raise NotImplementedError('HostVars does not support len. hosts entries are discovered dynamically as needed')
|
||||
|
||||
def __getstate__(self):
|
||||
return self._lookup
|
||||
data = self._lookup.copy()
|
||||
return dict(loader=self._loader, data=data)
|
||||
|
||||
def __setstate__(self, data):
|
||||
self._lookup = data
|
||||
self._lookup = data.get('data')
|
||||
self._loader = data.get('loader')
|
||||
|
|
Loading…
Reference in a new issue