mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
Correct variable blending from vars_files with hostvars in them
Fixes #8638
This commit is contained in:
parent
8956c636a5
commit
8a1fbed5d6
14 changed files with 69 additions and 64 deletions
|
@ -396,6 +396,7 @@ class PlayBook(object):
|
||||||
remote_port=task.play.remote_port,
|
remote_port=task.play.remote_port,
|
||||||
module_vars=task.module_vars,
|
module_vars=task.module_vars,
|
||||||
default_vars=task.default_vars,
|
default_vars=task.default_vars,
|
||||||
|
extra_vars=self.extra_vars,
|
||||||
private_key_file=self.private_key_file,
|
private_key_file=self.private_key_file,
|
||||||
setup_cache=self.SETUP_CACHE,
|
setup_cache=self.SETUP_CACHE,
|
||||||
vars_cache=self.VARS_CACHE,
|
vars_cache=self.VARS_CACHE,
|
||||||
|
|
|
@ -89,15 +89,15 @@ class Play(object):
|
||||||
self.vars_files = ds.get('vars_files', [])
|
self.vars_files = ds.get('vars_files', [])
|
||||||
if not isinstance(self.vars_files, list):
|
if not isinstance(self.vars_files, list):
|
||||||
raise errors.AnsibleError('vars_files must be a list')
|
raise errors.AnsibleError('vars_files must be a list')
|
||||||
self._update_vars_files_for_host(None)
|
processed_vars_files = self._update_vars_files_for_host(None)
|
||||||
|
|
||||||
# now we load the roles into the datastructure
|
# now we load the roles into the datastructure
|
||||||
self.included_roles = []
|
self.included_roles = []
|
||||||
ds = self._load_roles(self.roles, ds)
|
ds = self._load_roles(self.roles, ds)
|
||||||
|
|
||||||
# and finally re-process the vars files as they may have
|
# and finally re-process the vars files as they may have been updated
|
||||||
# been updated by the included roles
|
# by the included roles, but exclude any which have been processed
|
||||||
self.vars_files = ds.get('vars_files', [])
|
self.vars_files = utils.list_difference(ds.get('vars_files', []), processed_vars_files)
|
||||||
if not isinstance(self.vars_files, list):
|
if not isinstance(self.vars_files, list):
|
||||||
raise errors.AnsibleError('vars_files must be a list')
|
raise errors.AnsibleError('vars_files must be a list')
|
||||||
|
|
||||||
|
@ -765,27 +765,37 @@ class Play(object):
|
||||||
|
|
||||||
""" Render the raw filename into 3 forms """
|
""" Render the raw filename into 3 forms """
|
||||||
|
|
||||||
|
# filename2 is the templated version of the filename, which will
|
||||||
|
# be fully rendered if any variables contained within it are
|
||||||
|
# non-inventory related
|
||||||
filename2 = template(self.basedir, filename, self.vars)
|
filename2 = template(self.basedir, filename, self.vars)
|
||||||
|
|
||||||
|
# filename3 is the same as filename2, but when the host object is
|
||||||
|
# available, inventory variables will be expanded as well since the
|
||||||
|
# name is templated with the injected variables
|
||||||
filename3 = filename2
|
filename3 = filename2
|
||||||
if host is not None:
|
if host is not None:
|
||||||
filename3 = template(self.basedir, filename2, inject)
|
filename3 = template(self.basedir, filename2, inject)
|
||||||
|
|
||||||
|
# filename4 is the dwim'd path, but may also be mixed-scope, so we use
|
||||||
|
# both play scoped vars and host scoped vars to template the filepath
|
||||||
if self._has_vars_in(filename3) and host is not None:
|
if self._has_vars_in(filename3) and host is not None:
|
||||||
# allow play scoped vars and host scoped vars to template the filepath
|
|
||||||
inject.update(self.vars)
|
inject.update(self.vars)
|
||||||
filename4 = template(self.basedir, filename3, inject)
|
filename4 = template(self.basedir, filename3, inject)
|
||||||
filename4 = utils.path_dwim(self.basedir, filename4)
|
filename4 = utils.path_dwim(self.basedir, filename4)
|
||||||
else:
|
else:
|
||||||
filename4 = utils.path_dwim(self.basedir, filename3)
|
filename4 = utils.path_dwim(self.basedir, filename3)
|
||||||
|
|
||||||
return filename2, filename3, filename4
|
return filename2, filename3, filename4
|
||||||
|
|
||||||
|
|
||||||
def update_vars_cache(host, inject, data, filename):
|
def update_vars_cache(host, data, target_filename=None):
|
||||||
|
|
||||||
""" update a host's varscache with new var data """
|
""" update a host's varscache with new var data """
|
||||||
|
|
||||||
data = utils.combine_vars(inject, data)
|
|
||||||
self.playbook.VARS_CACHE[host] = utils.combine_vars(self.playbook.VARS_CACHE.get(host, {}), data)
|
self.playbook.VARS_CACHE[host] = utils.combine_vars(self.playbook.VARS_CACHE.get(host, {}), data)
|
||||||
self.playbook.callbacks.on_import_for_host(host, filename4)
|
if target_filename:
|
||||||
|
self.playbook.callbacks.on_import_for_host(host, target_filename)
|
||||||
|
|
||||||
def process_files(filename, filename2, filename3, filename4, host=None):
|
def process_files(filename, filename2, filename3, filename4, host=None):
|
||||||
|
|
||||||
|
@ -796,21 +806,19 @@ class Play(object):
|
||||||
if type(data) != dict:
|
if type(data) != dict:
|
||||||
raise errors.AnsibleError("%s must be stored as a dictionary/hash" % filename4)
|
raise errors.AnsibleError("%s must be stored as a dictionary/hash" % filename4)
|
||||||
if host is not None:
|
if host is not None:
|
||||||
if self._has_vars_in(filename2) and not self._has_vars_in(filename3):
|
target_filename = None
|
||||||
# running a host specific pass and has host specific variables
|
if self._has_vars_in(filename2):
|
||||||
# load into setup cache
|
if not self._has_vars_in(filename3):
|
||||||
update_vars_cache(host, inject, data, filename4)
|
target_filename = filename3
|
||||||
elif self._has_vars_in(filename3) and not self._has_vars_in(filename4):
|
else:
|
||||||
# handle mixed scope variables in filepath
|
target_filename = filename4
|
||||||
update_vars_cache(host, inject, data, filename4)
|
update_vars_cache(host, data, target_filename=target_filename)
|
||||||
|
else:
|
||||||
elif not self._has_vars_in(filename4):
|
self.vars = utils.combine_vars(self.vars, data)
|
||||||
# found a non-host specific variable, load into vars and NOT
|
# we did process this file
|
||||||
# the setup cache
|
return True
|
||||||
if host is not None:
|
# we did not process this file
|
||||||
self.vars.update(data)
|
return False
|
||||||
else:
|
|
||||||
self.vars = utils.combine_vars(self.vars, data)
|
|
||||||
|
|
||||||
# Enforce that vars_files is always a list
|
# Enforce that vars_files is always a list
|
||||||
if type(self.vars_files) != list:
|
if type(self.vars_files) != list:
|
||||||
|
@ -825,6 +833,7 @@ class Play(object):
|
||||||
else:
|
else:
|
||||||
inject = None
|
inject = None
|
||||||
|
|
||||||
|
processed = []
|
||||||
for filename in self.vars_files:
|
for filename in self.vars_files:
|
||||||
if type(filename) == list:
|
if type(filename) == list:
|
||||||
# loop over all filenames, loading the first one, and failing if none found
|
# loop over all filenames, loading the first one, and failing if none found
|
||||||
|
@ -835,7 +844,8 @@ class Play(object):
|
||||||
sequence.append(filename4)
|
sequence.append(filename4)
|
||||||
if os.path.exists(filename4):
|
if os.path.exists(filename4):
|
||||||
found = True
|
found = True
|
||||||
process_files(filename, filename2, filename3, filename4, host=host)
|
if process_files(filename, filename2, filename3, filename4, host=host):
|
||||||
|
processed.append(filename)
|
||||||
elif host is not None:
|
elif host is not None:
|
||||||
self.playbook.callbacks.on_not_import_for_host(host, filename4)
|
self.playbook.callbacks.on_not_import_for_host(host, filename4)
|
||||||
if found:
|
if found:
|
||||||
|
@ -844,14 +854,12 @@ class Play(object):
|
||||||
raise errors.AnsibleError(
|
raise errors.AnsibleError(
|
||||||
"%s: FATAL, no files matched for vars_files import sequence: %s" % (host, sequence)
|
"%s: FATAL, no files matched for vars_files import sequence: %s" % (host, sequence)
|
||||||
)
|
)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# just one filename supplied, load it!
|
# just one filename supplied, load it!
|
||||||
filename2, filename3, filename4 = generate_filenames(host, inject, filename)
|
filename2, filename3, filename4 = generate_filenames(host, inject, filename)
|
||||||
if self._has_vars_in(filename4):
|
if self._has_vars_in(filename4):
|
||||||
continue
|
continue
|
||||||
process_files(filename, filename2, filename3, filename4, host=host)
|
if process_files(filename, filename2, filename3, filename4, host=host):
|
||||||
|
processed.append(filename)
|
||||||
|
|
||||||
# finally, update the VARS_CACHE for the host, if it is set
|
return processed
|
||||||
if host is not None:
|
|
||||||
self.playbook.VARS_CACHE.setdefault(host, {}).update(self.playbook.extra_vars)
|
|
||||||
|
|
|
@ -130,6 +130,7 @@ class Runner(object):
|
||||||
sudo_user=C.DEFAULT_SUDO_USER, # ex: 'root'
|
sudo_user=C.DEFAULT_SUDO_USER, # ex: 'root'
|
||||||
module_vars=None, # a playbooks internals thing
|
module_vars=None, # a playbooks internals thing
|
||||||
default_vars=None, # ditto
|
default_vars=None, # ditto
|
||||||
|
extra_vars=None, # extra vars specified with he playbook(s)
|
||||||
is_playbook=False, # running from playbook or not?
|
is_playbook=False, # running from playbook or not?
|
||||||
inventory=None, # reference to Inventory object
|
inventory=None, # reference to Inventory object
|
||||||
subset=None, # subset pattern
|
subset=None, # subset pattern
|
||||||
|
@ -170,6 +171,8 @@ class Runner(object):
|
||||||
|
|
||||||
self.module_vars = utils.default(module_vars, lambda: {})
|
self.module_vars = utils.default(module_vars, lambda: {})
|
||||||
self.default_vars = utils.default(default_vars, lambda: {})
|
self.default_vars = utils.default(default_vars, lambda: {})
|
||||||
|
self.extra_vars = utils.default(extra_vars, lambda: {})
|
||||||
|
|
||||||
self.always_run = None
|
self.always_run = None
|
||||||
self.connector = connection.Connector(self)
|
self.connector = connection.Connector(self)
|
||||||
self.conditional = conditional
|
self.conditional = conditional
|
||||||
|
@ -605,10 +608,13 @@ class Runner(object):
|
||||||
module_vars = template.template(self.basedir, self.module_vars, module_vars_inject)
|
module_vars = template.template(self.basedir, self.module_vars, module_vars_inject)
|
||||||
|
|
||||||
inject = {}
|
inject = {}
|
||||||
|
|
||||||
inject = utils.combine_vars(inject, self.default_vars)
|
inject = utils.combine_vars(inject, self.default_vars)
|
||||||
inject = utils.combine_vars(inject, host_variables)
|
inject = utils.combine_vars(inject, host_variables)
|
||||||
|
inject = utils.combine_vars(inject, self.setup_cache.get(host, {}))
|
||||||
inject = utils.combine_vars(inject, module_vars)
|
inject = utils.combine_vars(inject, module_vars)
|
||||||
inject = utils.combine_vars(inject, combined_cache.get(host, {}))
|
inject = utils.combine_vars(inject, self.vars_cache.get(host, {}))
|
||||||
|
inject = utils.combine_vars(inject, self.extra_vars)
|
||||||
inject.setdefault('ansible_ssh_user', self.remote_user)
|
inject.setdefault('ansible_ssh_user', self.remote_user)
|
||||||
inject['hostvars'] = hostvars
|
inject['hostvars'] = hostvars
|
||||||
inject['group_names'] = host_variables.get('group_names', [])
|
inject['group_names'] = host_variables.get('group_names', [])
|
||||||
|
|
|
@ -1163,6 +1163,16 @@ def list_intersection(a, b):
|
||||||
result.append(x)
|
result.append(x)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
def list_difference(a, b):
|
||||||
|
result = []
|
||||||
|
for x in a:
|
||||||
|
if x not in b and x not in result:
|
||||||
|
result.append(x)
|
||||||
|
for x in b:
|
||||||
|
if x not in a and x not in result:
|
||||||
|
result.append(x)
|
||||||
|
return result
|
||||||
|
|
||||||
def safe_eval(expr, locals={}, include_exceptions=False):
|
def safe_eval(expr, locals={}, include_exceptions=False):
|
||||||
'''
|
'''
|
||||||
This is intended for allowing things like:
|
This is intended for allowing things like:
|
||||||
|
|
|
@ -3,3 +3,4 @@
|
||||||
- 'extra_var == "extra_var"'
|
- 'extra_var == "extra_var"'
|
||||||
- 'vars_var == "vars_var"'
|
- 'vars_var == "vars_var"'
|
||||||
- 'vars_files_var == "vars_files_var"'
|
- 'vars_files_var == "vars_files_var"'
|
||||||
|
- 'vars_files_var_role == "vars_files_var_role3"'
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
- debug: var=param_var
|
- debug: var=param_var
|
||||||
- debug: var=vars_var
|
- debug: var=vars_var
|
||||||
- debug: var=vars_files_var
|
- debug: var=vars_files_var
|
||||||
|
- debug: var=vars_files_var_role
|
||||||
- debug: var=defaults_file_var_role1
|
- debug: var=defaults_file_var_role1
|
||||||
- assert:
|
- assert:
|
||||||
that:
|
that:
|
||||||
|
@ -9,4 +10,5 @@
|
||||||
- 'param_var == "param_var_role1"'
|
- 'param_var == "param_var_role1"'
|
||||||
- 'vars_var == "vars_var"'
|
- 'vars_var == "vars_var"'
|
||||||
- 'vars_files_var == "vars_files_var"'
|
- 'vars_files_var == "vars_files_var"'
|
||||||
|
- 'vars_files_var_role == "vars_files_var_role3"'
|
||||||
- 'defaults_file_var_role1 == "defaults_file_var_role1"'
|
- 'defaults_file_var_role1 == "defaults_file_var_role1"'
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
---
|
---
|
||||||
# should override the global vars_files_var since it's local to the role
|
# should override the global vars_files_var since it's local to the role
|
||||||
vars_files_var: "vars_files_var_role1"
|
# but will be set to the value in the last role included which defines it
|
||||||
|
vars_files_var_role: "vars_files_var_role1"
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
- debug: var=param_var
|
- debug: var=param_var
|
||||||
- debug: var=vars_var
|
- debug: var=vars_var
|
||||||
- debug: var=vars_files_var
|
- debug: var=vars_files_var
|
||||||
|
- debug: var=vars_files_var_role
|
||||||
- debug: var=defaults_file_var_role1
|
- debug: var=defaults_file_var_role1
|
||||||
- assert:
|
- assert:
|
||||||
that:
|
that:
|
||||||
|
@ -9,4 +10,5 @@
|
||||||
- 'param_var == "param_var_role2"'
|
- 'param_var == "param_var_role2"'
|
||||||
- 'vars_var == "vars_var"'
|
- 'vars_var == "vars_var"'
|
||||||
- 'vars_files_var == "vars_files_var"'
|
- 'vars_files_var == "vars_files_var"'
|
||||||
|
- 'vars_files_var_role == "vars_files_var_role3"'
|
||||||
- 'defaults_file_var_role2 == "overridden by role vars"'
|
- 'defaults_file_var_role2 == "overridden by role vars"'
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
# should override the global vars_files_var since it's local to the role
|
# should override the global vars_files_var since it's local to the role
|
||||||
vars_files_var: "vars_files_var_role1"
|
vars_files_var_role: "vars_files_var_role2"
|
||||||
# should override the value in defaults/main.yml for role 2
|
# should override the value in defaults/main.yml for role 2
|
||||||
defaults_file_var_role2: "overridden by role vars"
|
defaults_file_var_role2: "overridden by role vars"
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
- debug: var=param_var
|
- debug: var=param_var
|
||||||
- debug: var=vars_var
|
- debug: var=vars_var
|
||||||
- debug: var=vars_files_var
|
- debug: var=vars_files_var
|
||||||
|
- debug: var=vars_files_var_role
|
||||||
- debug: var=defaults_file_var_role1
|
- debug: var=defaults_file_var_role1
|
||||||
- assert:
|
- assert:
|
||||||
that:
|
that:
|
||||||
|
@ -9,4 +10,5 @@
|
||||||
- 'param_var == "param_var_role3"'
|
- 'param_var == "param_var_role3"'
|
||||||
- 'vars_var == "vars_var"'
|
- 'vars_var == "vars_var"'
|
||||||
- 'vars_files_var == "vars_files_var"'
|
- 'vars_files_var == "vars_files_var"'
|
||||||
|
- 'vars_files_var_role == "vars_files_var_role3"'
|
||||||
- 'defaults_file_var_role3 == "overridden from inventory"'
|
- 'defaults_file_var_role3 == "overridden from inventory"'
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
---
|
---
|
||||||
# should override the global vars_files_var since it's local to the role
|
# should override the global vars_files_var since it's local to the role
|
||||||
vars_files_var: "vars_files_var_role1"
|
vars_files_var_role: "vars_files_var_role3"
|
||||||
|
|
|
@ -12,8 +12,10 @@
|
||||||
- debug: var=extra_var
|
- debug: var=extra_var
|
||||||
- debug: var=vars_var
|
- debug: var=vars_var
|
||||||
- debug: var=vars_files_var
|
- debug: var=vars_files_var
|
||||||
|
- debug: var=vars_files_var_role
|
||||||
- assert:
|
- assert:
|
||||||
that:
|
that:
|
||||||
- 'extra_var == "extra_var"'
|
- 'extra_var == "extra_var"'
|
||||||
- 'vars_var == "vars_var"'
|
- 'vars_var == "vars_var"'
|
||||||
- 'vars_files_var == "vars_files_var"'
|
- 'vars_files_var == "vars_files_var"'
|
||||||
|
- 'vars_files_var_role == "vars_files_var_role3"'
|
||||||
|
|
|
@ -2,3 +2,4 @@
|
||||||
extra_var: "BAD!"
|
extra_var: "BAD!"
|
||||||
role_var: "BAD!"
|
role_var: "BAD!"
|
||||||
vars_files_var: "vars_files_var"
|
vars_files_var: "vars_files_var"
|
||||||
|
vars_files_var_role: "should be overridden by roles"
|
||||||
|
|
|
@ -266,37 +266,6 @@ class TestMe(unittest.TestCase):
|
||||||
assert 'foo' in play.playbook.VARS_CACHE['localhost'], "vars_file vars were not loaded into vars_cache"
|
assert 'foo' in play.playbook.VARS_CACHE['localhost'], "vars_file vars were not loaded into vars_cache"
|
||||||
assert play.playbook.VARS_CACHE['localhost']['foo'] == 'bar', "foo does not equal bar"
|
assert play.playbook.VARS_CACHE['localhost']['foo'] == 'bar', "foo does not equal bar"
|
||||||
|
|
||||||
def test_vars_files_for_host_with_extra_vars(self):
|
|
||||||
|
|
||||||
# host != None
|
|
||||||
# vars in filename2
|
|
||||||
# no vars in filename3
|
|
||||||
|
|
||||||
# make a vars file
|
|
||||||
fd, temp_path = mkstemp()
|
|
||||||
f = open(temp_path, "wb")
|
|
||||||
f.write("foo: bar\n")
|
|
||||||
f.close()
|
|
||||||
|
|
||||||
# build play attributes
|
|
||||||
playbook = FakePlayBook()
|
|
||||||
ds = { "hosts": "localhost",
|
|
||||||
"vars_files": ["{{ temp_path }}"]}
|
|
||||||
basedir = "."
|
|
||||||
playbook.VARS_CACHE['localhost']['temp_path'] = temp_path
|
|
||||||
playbook.extra_vars = {"foo": "extra"}
|
|
||||||
|
|
||||||
# create play and do first run
|
|
||||||
play = Play(playbook, ds, basedir)
|
|
||||||
|
|
||||||
# the second run is started by calling update_vars_files
|
|
||||||
play.update_vars_files(['localhost'])
|
|
||||||
os.remove(temp_path)
|
|
||||||
|
|
||||||
assert 'foo' in play.vars, "extra vars were not set in play.vars"
|
|
||||||
assert 'foo' in play.playbook.VARS_CACHE['localhost'], "vars_file vars were not loaded into vars_cache"
|
|
||||||
assert play.playbook.VARS_CACHE['localhost']['foo'] == 'extra', "extra vars did not overwrite vars_files vars"
|
|
||||||
|
|
||||||
|
|
||||||
########################################
|
########################################
|
||||||
# COMPLEX FILENAME TEMPLATING TESTS
|
# COMPLEX FILENAME TEMPLATING TESTS
|
||||||
|
|
Loading…
Reference in a new issue