mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
Added basic support for hash_behaviour=merge in roles
Dict vars passed to roles are now properly merged instead of simply overriding dict vars that are coming from vars_files.
This commit is contained in:
parent
fc2d25eb82
commit
c642ba77ae
6 changed files with 74 additions and 53 deletions
|
@ -77,11 +77,11 @@ class Play(object):
|
||||||
# tasks/handlers as they may have inventory scope overrides
|
# tasks/handlers as they may have inventory scope overrides
|
||||||
_tasks = ds.pop('tasks', [])
|
_tasks = ds.pop('tasks', [])
|
||||||
_handlers = ds.pop('handlers', [])
|
_handlers = ds.pop('handlers', [])
|
||||||
ds = template(basedir, ds, self.vars)
|
ds = template(basedir, ds, self.vars)
|
||||||
ds['tasks'] = _tasks
|
ds['tasks'] = _tasks
|
||||||
ds['handlers'] = _handlers
|
ds['handlers'] = _handlers
|
||||||
|
|
||||||
self._ds = ds
|
self._ds = ds
|
||||||
|
|
||||||
hosts = ds.get('hosts')
|
hosts = ds.get('hosts')
|
||||||
if hosts is None:
|
if hosts is None:
|
||||||
|
@ -114,7 +114,7 @@ class Play(object):
|
||||||
|
|
||||||
if self.sudo_user != 'root':
|
if self.sudo_user != 'root':
|
||||||
self.sudo = True
|
self.sudo = True
|
||||||
|
|
||||||
|
|
||||||
# *************************************************
|
# *************************************************
|
||||||
|
|
||||||
|
@ -146,7 +146,7 @@ class Play(object):
|
||||||
# flush handlers after pre_tasks
|
# flush handlers after pre_tasks
|
||||||
new_tasks.append(dict(meta='flush_handlers'))
|
new_tasks.append(dict(meta='flush_handlers'))
|
||||||
|
|
||||||
# variables if the role was parameterized (i.e. given as a hash)
|
# variables if the role was parameterized (i.e. given as a hash)
|
||||||
has_dict = {}
|
has_dict = {}
|
||||||
|
|
||||||
for orig_path in roles:
|
for orig_path in roles:
|
||||||
|
@ -178,14 +178,14 @@ class Play(object):
|
||||||
raise errors.AnsibleError("found role at %s, but cannot find %s or %s or %s or %s" % (path, task, handler, vars_file, library))
|
raise errors.AnsibleError("found role at %s, but cannot find %s or %s or %s or %s" % (path, task, handler, vars_file, library))
|
||||||
if os.path.isfile(task):
|
if os.path.isfile(task):
|
||||||
nt = dict(include=task, vars=has_dict)
|
nt = dict(include=task, vars=has_dict)
|
||||||
if when:
|
if when:
|
||||||
nt['when'] = when
|
nt['when'] = when
|
||||||
if with_items:
|
if with_items:
|
||||||
nt['with_items'] = with_items
|
nt['with_items'] = with_items
|
||||||
new_tasks.append(nt)
|
new_tasks.append(nt)
|
||||||
if os.path.isfile(handler):
|
if os.path.isfile(handler):
|
||||||
nt = dict(include=handler, vars=has_dict)
|
nt = dict(include=handler, vars=has_dict)
|
||||||
if when:
|
if when:
|
||||||
nt['when'] = when
|
nt['when'] = when
|
||||||
if with_items:
|
if with_items:
|
||||||
nt['with_items'] = with_items
|
nt['with_items'] = with_items
|
||||||
|
@ -242,7 +242,7 @@ class Play(object):
|
||||||
if x['meta'] == 'flush_handlers':
|
if x['meta'] == 'flush_handlers':
|
||||||
results.append(Task(self,x))
|
results.append(Task(self,x))
|
||||||
continue
|
continue
|
||||||
|
|
||||||
task_vars = self.vars.copy()
|
task_vars = self.vars.copy()
|
||||||
task_vars.update(vars)
|
task_vars.update(vars)
|
||||||
if original_file:
|
if original_file:
|
||||||
|
@ -269,7 +269,7 @@ class Play(object):
|
||||||
raise errors.AnsibleError("parse error: task includes cannot be used with other directives: %s" % k)
|
raise errors.AnsibleError("parse error: task includes cannot be used with other directives: %s" % k)
|
||||||
|
|
||||||
if 'vars' in x:
|
if 'vars' in x:
|
||||||
task_vars.update(x['vars'])
|
task_vars = utils.combine_vars(task_vars, x['vars'])
|
||||||
if 'only_if' in x:
|
if 'only_if' in x:
|
||||||
included_additional_conditions.append(x['only_if'])
|
included_additional_conditions.append(x['only_if'])
|
||||||
|
|
||||||
|
@ -281,7 +281,7 @@ class Play(object):
|
||||||
mv[k] = template(self.basedir, v, mv)
|
mv[k] = template(self.basedir, v, mv)
|
||||||
dirname = self.basedir
|
dirname = self.basedir
|
||||||
if original_file:
|
if original_file:
|
||||||
dirname = os.path.dirname(original_file)
|
dirname = os.path.dirname(original_file)
|
||||||
include_file = template(dirname, tokens[0], mv)
|
include_file = template(dirname, tokens[0], mv)
|
||||||
include_filename = utils.path_dwim(dirname, include_file)
|
include_filename = utils.path_dwim(dirname, include_file)
|
||||||
data = utils.parse_yaml_from_file(include_filename)
|
data = utils.parse_yaml_from_file(include_filename)
|
||||||
|
|
|
@ -335,24 +335,23 @@ def parse_kv(args):
|
||||||
return options
|
return options
|
||||||
|
|
||||||
def merge_hash(a, b):
|
def merge_hash(a, b):
|
||||||
''' merges hash b into a
|
''' recursively merges hash b into a
|
||||||
this means that if b has key k, the resulting has will have a key k
|
keys from b take precedende over keys from a '''
|
||||||
which value comes from b
|
|
||||||
said differently, all key/value combination from b will override a's '''
|
|
||||||
|
|
||||||
# and iterate over b keys
|
result = copy.deepcopy(a)
|
||||||
|
|
||||||
|
# next, iterate over b keys and values
|
||||||
for k, v in b.iteritems():
|
for k, v in b.iteritems():
|
||||||
if k in a and isinstance(a[k], dict):
|
# if there's already such key in a
|
||||||
# if this key is a hash and exists in a
|
# and that key contains dict
|
||||||
# we recursively call ourselves with
|
if k in result and isinstance(result[k], dict):
|
||||||
# the key value of b
|
# merge those dicts recursively
|
||||||
a[k] = merge_hash(a[k], v)
|
result[k] = merge_hash(a[k], v)
|
||||||
else:
|
else:
|
||||||
# k is not in a, no need to merge b, we just deecopy
|
# otherwise, just copy a value from b to a
|
||||||
# or k is not a dictionnary, no need to merge b either, we just deecopy it
|
result[k] = v
|
||||||
a[k] = v
|
|
||||||
# finally, return the resulting hash when we're done iterating keys
|
return result
|
||||||
return a
|
|
||||||
|
|
||||||
def md5s(data):
|
def md5s(data):
|
||||||
''' Return MD5 hex digest of data. '''
|
''' Return MD5 hex digest of data. '''
|
||||||
|
@ -604,7 +603,7 @@ def compile_when_to_only_if(expression):
|
||||||
# when: int $x in $alist
|
# when: int $x in $alist
|
||||||
# when: float $x > 2 and $y <= $z
|
# when: float $x > 2 and $y <= $z
|
||||||
# when: str $x != $y
|
# when: str $x != $y
|
||||||
# when: jinja2_compare asdf # implies {{ asdf }}
|
# when: jinja2_compare asdf # implies {{ asdf }}
|
||||||
|
|
||||||
if type(expression) not in [ str, unicode ]:
|
if type(expression) not in [ str, unicode ]:
|
||||||
raise errors.AnsibleError("invalid usage of when_ operator: %s" % expression)
|
raise errors.AnsibleError("invalid usage of when_ operator: %s" % expression)
|
||||||
|
@ -723,13 +722,13 @@ def get_diff(diff):
|
||||||
return ">> the files are different, but the diff library cannot compare unicode strings"
|
return ">> the files are different, but the diff library cannot compare unicode strings"
|
||||||
|
|
||||||
def is_list_of_strings(items):
|
def is_list_of_strings(items):
|
||||||
for x in items:
|
for x in items:
|
||||||
if not isinstance(x, basestring):
|
if not isinstance(x, basestring):
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def safe_eval(str):
|
def safe_eval(str):
|
||||||
'''
|
'''
|
||||||
this is intended for allowing things like:
|
this is intended for allowing things like:
|
||||||
with_items: {{ a_list_variable }}
|
with_items: {{ a_list_variable }}
|
||||||
where Jinja2 would return a string
|
where Jinja2 would return a string
|
||||||
|
@ -737,7 +736,7 @@ def safe_eval(str):
|
||||||
the env is constrained)
|
the env is constrained)
|
||||||
'''
|
'''
|
||||||
# FIXME: is there a more native way to do this?
|
# FIXME: is there a more native way to do this?
|
||||||
|
|
||||||
def is_set(var):
|
def is_set(var):
|
||||||
return not var.startswith("$") and not '{{' in var
|
return not var.startswith("$") and not '{{' in var
|
||||||
|
|
||||||
|
@ -776,7 +775,7 @@ def listify_lookup_plugin_terms(terms, basedir, inject):
|
||||||
if isinstance(new_terms, basestring) and new_terms.find("{{") != -1:
|
if isinstance(new_terms, basestring) and new_terms.find("{{") != -1:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
terms = new_terms
|
terms = new_terms
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
|
@ -96,6 +96,8 @@ class TestPlaybook(unittest.TestCase):
|
||||||
os.unlink('/tmp/ansible_test_data_template.out')
|
os.unlink('/tmp/ansible_test_data_template.out')
|
||||||
if os.path.exists('/tmp/ansible_test_messages.out'):
|
if os.path.exists('/tmp/ansible_test_messages.out'):
|
||||||
os.unlink('/tmp/ansible_test_messages.out')
|
os.unlink('/tmp/ansible_test_messages.out')
|
||||||
|
if os.path.exists('/tmp/ansible_test_role_messages.out'):
|
||||||
|
os.unlink('/tmp/ansible_test_role_messages.out')
|
||||||
|
|
||||||
def _prepare_stage_dir(self):
|
def _prepare_stage_dir(self):
|
||||||
stage_path = os.path.join(self.test_dir, 'test_data')
|
stage_path = os.path.join(self.test_dir, 'test_data')
|
||||||
|
@ -304,20 +306,17 @@ class TestPlaybook(unittest.TestCase):
|
||||||
)
|
)
|
||||||
playbook.run()
|
playbook.run()
|
||||||
|
|
||||||
with open('/tmp/ansible_test_messages.out') as f:
|
filename = '/tmp/ansible_test_messages.out'
|
||||||
actual = [l.strip() for l in f.readlines()]
|
expected_lines = [
|
||||||
|
|
||||||
print "**ACTUAL**"
|
|
||||||
print actual
|
|
||||||
|
|
||||||
expected = [
|
|
||||||
"goodbye: Goodbye World!"
|
"goodbye: Goodbye World!"
|
||||||
]
|
]
|
||||||
|
self._compare_file_output(filename, expected_lines)
|
||||||
|
|
||||||
print "**EXPECTED**"
|
filename = '/tmp/ansible_test_role_messages.out'
|
||||||
print expected
|
expected_lines = [
|
||||||
|
"inside_a_role: Indeed!"
|
||||||
assert actual == expected
|
]
|
||||||
|
self._compare_file_output(filename, expected_lines)
|
||||||
|
|
||||||
# restore default hash behavior
|
# restore default hash behavior
|
||||||
C.DEFAULT_HASH_BEHAVIOUR = saved_hash_behavior
|
C.DEFAULT_HASH_BEHAVIOUR = saved_hash_behavior
|
||||||
|
@ -337,21 +336,34 @@ class TestPlaybook(unittest.TestCase):
|
||||||
)
|
)
|
||||||
playbook.run()
|
playbook.run()
|
||||||
|
|
||||||
with open('/tmp/ansible_test_messages.out') as f:
|
filename = '/tmp/ansible_test_messages.out'
|
||||||
actual = [l.strip() for l in f.readlines()]
|
expected_lines = [
|
||||||
|
"goodbye: Goodbye World!",
|
||||||
print "**ACTUAL**"
|
"hello: Hello World!"
|
||||||
print actual
|
|
||||||
|
|
||||||
expected = [
|
|
||||||
"hello: Hello World!",
|
|
||||||
"goodbye: Goodbye World!"
|
|
||||||
]
|
]
|
||||||
|
self._compare_file_output(filename, expected_lines)
|
||||||
|
|
||||||
print "**EXPECTED**"
|
filename = '/tmp/ansible_test_role_messages.out'
|
||||||
print expected
|
expected_lines = [
|
||||||
|
"goodbye: Goodbye World!",
|
||||||
assert actual == expected
|
"hello: Hello World!",
|
||||||
|
"inside_a_role: Indeed!"
|
||||||
|
]
|
||||||
|
self._compare_file_output(filename, expected_lines)
|
||||||
|
|
||||||
# restore default hash behavior
|
# restore default hash behavior
|
||||||
C.DEFAULT_HASH_BEHAVIOUR = saved_hash_behavior
|
C.DEFAULT_HASH_BEHAVIOUR = saved_hash_behavior
|
||||||
|
|
||||||
|
def _compare_file_output(self, filename, expected_lines):
|
||||||
|
actual_lines = []
|
||||||
|
with open(filename) as f:
|
||||||
|
actual_lines = [l.strip() for l in f.readlines()]
|
||||||
|
actual_lines = sorted(actual_lines)
|
||||||
|
|
||||||
|
print "**ACTUAL**"
|
||||||
|
print actual_lines
|
||||||
|
|
||||||
|
print "**EXPECTED**"
|
||||||
|
print expected_lines
|
||||||
|
|
||||||
|
assert actual_lines == expected_lines
|
||||||
|
|
|
@ -9,3 +9,8 @@
|
||||||
tasks:
|
tasks:
|
||||||
- name: generate messages
|
- name: generate messages
|
||||||
action: template src=message.j2 dest=/tmp/ansible_test_messages.out
|
action: template src=message.j2 dest=/tmp/ansible_test_messages.out
|
||||||
|
|
||||||
|
roles:
|
||||||
|
- role: hash_behavior_test_role
|
||||||
|
messages:
|
||||||
|
inside_a_role: "Indeed!"
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
- name: generate role messages
|
||||||
|
action: template src=role_message.j2 dest=/tmp/ansible_test_role_messages.out
|
|
@ -0,0 +1,3 @@
|
||||||
|
{% for k, v in messages.iteritems() %}
|
||||||
|
{{ k }}: {{ v }}
|
||||||
|
{% endfor %}
|
Loading…
Reference in a new issue