Compare dict vars to determine changed
Compare dict vars of before and after configuration to determine whether the config keys or values have changed so a configuration file will not be incorrectly marked as changed when only the ordering has changed. Set diff return variable to a dict of changes applied. Change-Id: Ie67119b1420936c8ed89f8338ea9dce4c47e185c
This commit is contained in:
parent
fb906fb061
commit
b74d7acbf4
@ -26,6 +26,7 @@ try:
|
||||
from StringIO import StringIO
|
||||
except ImportError:
|
||||
from io import StringIO
|
||||
import base64
|
||||
import json
|
||||
import os
|
||||
import pwd
|
||||
@ -297,6 +298,90 @@ class ConfigTemplateParser(ConfigParser.RawConfigParser):
|
||||
options[name] = _temp_item
|
||||
|
||||
|
||||
class DictCompare(object):
|
||||
"""
|
||||
Calculate the difference between two dictionaries.
|
||||
|
||||
Example Usage:
|
||||
>>> base_dict = {'test1': 'val1', 'test2': 'val2', 'test3': 'val3'}
|
||||
>>> new_dict = {'test1': 'val2', 'test3': 'val3', 'test4': 'val3', 'test5': 'val5'}
|
||||
>>> dc = DictCompare(base_dict, new_dict)
|
||||
>>> dc.added()
|
||||
... ['test5', 'test4']
|
||||
>>> dc.removed()
|
||||
... ['test2']
|
||||
>>> dc.changed()
|
||||
... ['test1']
|
||||
>>> dc.get_changes()
|
||||
... {'added':
|
||||
... {'test4': 'val3','test5': 'val5'},
|
||||
... 'removed':
|
||||
... {'test2': 'val2'},
|
||||
... 'changed':
|
||||
... {'test1': {'current_val': 'vol1', 'new_val': 'val2'}
|
||||
... }
|
||||
"""
|
||||
def __init__(self, base_dict, new_dict):
|
||||
self.new_dict, self.base_dict = new_dict, base_dict
|
||||
self.base_items, self.new_items = set(self.base_dict.keys()), set(self.new_dict.keys())
|
||||
self.intersect = self.new_items.intersection(self.base_items)
|
||||
|
||||
def added(self):
|
||||
return self.new_items - self.intersect
|
||||
|
||||
def removed(self):
|
||||
return self.base_items - self.intersect
|
||||
|
||||
def changed(self):
|
||||
return set(x for x in self.intersect if self.base_dict[x] != self.new_dict[x])
|
||||
|
||||
def get_changes(self):
|
||||
"""Returns dict of differences between 2 dicts and bool indicating if there are differences
|
||||
|
||||
:param base_dict: ``dict``
|
||||
:param new_dict: ``dict``
|
||||
:returns: ``dict``, ``bool``
|
||||
"""
|
||||
changed=False
|
||||
mods={'added': {}, 'removed': {}, 'changed': {}}
|
||||
|
||||
for s in self.changed():
|
||||
changed=True
|
||||
if type(self.base_dict[s]) is not dict:
|
||||
mods['changed'] = {s: {'current_val': self.base_dict[s], 'new_val': self.new_dict[s]}}
|
||||
continue
|
||||
|
||||
diff = DictCompare(self.base_dict[s], self.new_dict[s])
|
||||
for a in diff.added():
|
||||
if s not in mods['added']:
|
||||
mods['added'][s] = {a: self.new_dict[s][a]}
|
||||
else:
|
||||
mods['added'][s][a] = self.new_dict[s][a]
|
||||
|
||||
for r in diff.removed():
|
||||
if s not in mods['removed']:
|
||||
mods['removed'][s] = {r: self.base_dict[s][r]}
|
||||
else:
|
||||
mods['removed'][s][r] = self.base_dict[s][r]
|
||||
|
||||
for c in diff.changed():
|
||||
if s not in mods['changed']:
|
||||
mods['changed'][s] = {c: {'current_val': self.base_dict[s][c], 'new_val': self.new_dict[s][c]}}
|
||||
else:
|
||||
mods['changed'][s][c] = {'current_val': self.base_dict[s][c], 'new_val': self.new_dict[s][c]}
|
||||
|
||||
for s in self.added():
|
||||
changed=True
|
||||
mods['added'][s] = self.new_dict[s]
|
||||
|
||||
for s in self.removed():
|
||||
changed=True
|
||||
mods['removed'][s] = self.base_dict[s]
|
||||
|
||||
|
||||
return mods, changed
|
||||
|
||||
|
||||
class ActionModule(ActionBase):
|
||||
TRANSFERS_FILES = True
|
||||
|
||||
@ -306,11 +391,11 @@ class ActionModule(ActionBase):
|
||||
list_extend=True,
|
||||
ignore_none_type=True,
|
||||
default_section='DEFAULT'):
|
||||
"""Returns string value from a modified config file.
|
||||
"""Returns string value from a modified config file and dict of merged config
|
||||
|
||||
:param config_overrides: ``dict``
|
||||
:param resultant: ``str`` || ``unicode``
|
||||
:returns: ``str``
|
||||
:returns: ``str``, ``dict``
|
||||
"""
|
||||
# If there is an exception loading the RawConfigParser The config obj
|
||||
# is loaded again without the extra option. This is being done to
|
||||
@ -328,6 +413,7 @@ class ActionModule(ActionBase):
|
||||
|
||||
config_object = StringIO(resultant)
|
||||
config.readfp(config_object)
|
||||
|
||||
for section, items in config_overrides.items():
|
||||
# If the items value is not a dictionary it is assumed that the
|
||||
# value is a default item for this config type.
|
||||
@ -361,10 +447,23 @@ class ActionModule(ActionBase):
|
||||
else:
|
||||
config_object.close()
|
||||
|
||||
config_dict_new = {}
|
||||
config_defaults = config.defaults()
|
||||
for s in config.sections():
|
||||
config_dict_new[s] = {}
|
||||
for k, v in config.items(s):
|
||||
if k not in config_defaults or config_defaults[k] != v:
|
||||
config_dict_new[s][k] = v
|
||||
else:
|
||||
if default_section in config_dict_new:
|
||||
config_dict_new[default_section][k] = v
|
||||
else:
|
||||
config_dict_new[default_section] = {k: v}
|
||||
|
||||
resultant_stringio = StringIO()
|
||||
try:
|
||||
config.write(resultant_stringio)
|
||||
return resultant_stringio.getvalue()
|
||||
return resultant_stringio.getvalue(), config_dict_new
|
||||
finally:
|
||||
resultant_stringio.close()
|
||||
|
||||
@ -391,27 +490,26 @@ class ActionModule(ActionBase):
|
||||
list_extend=True,
|
||||
ignore_none_type=True,
|
||||
default_section='DEFAULT'):
|
||||
"""Returns config json
|
||||
"""Returns config json and dict of merged config
|
||||
|
||||
Its important to note that file ordering will not be preserved as the
|
||||
information within the json file will be sorted by keys.
|
||||
|
||||
:param config_overrides: ``dict``
|
||||
:param resultant: ``str`` || ``unicode``
|
||||
:returns: ``str``
|
||||
:returns: ``str``, ``dict``
|
||||
"""
|
||||
original_resultant = json.loads(resultant)
|
||||
merged_resultant = self._merge_dict(
|
||||
base_items=original_resultant,
|
||||
new_items=config_overrides,
|
||||
list_extend=list_extend,
|
||||
default_section=default_section
|
||||
list_extend=list_extend
|
||||
)
|
||||
return json.dumps(
|
||||
merged_resultant,
|
||||
indent=4,
|
||||
sort_keys=True
|
||||
)
|
||||
), merged_resultant
|
||||
|
||||
def return_config_overrides_yaml(self,
|
||||
config_overrides,
|
||||
@ -419,11 +517,11 @@ class ActionModule(ActionBase):
|
||||
list_extend=True,
|
||||
ignore_none_type=True,
|
||||
default_section='DEFAULT'):
|
||||
"""Return config yaml.
|
||||
"""Return config yaml and dict of merged config
|
||||
|
||||
:param config_overrides: ``dict``
|
||||
:param resultant: ``str`` || ``unicode``
|
||||
:returns: ``str``
|
||||
:returns: ``str``, ``dict``
|
||||
"""
|
||||
original_resultant = yaml.safe_load(resultant)
|
||||
merged_resultant = self._merge_dict(
|
||||
@ -436,7 +534,7 @@ class ActionModule(ActionBase):
|
||||
Dumper=IDumper,
|
||||
default_flow_style=False,
|
||||
width=1000,
|
||||
)
|
||||
), merged_resultant
|
||||
|
||||
def _merge_dict(self, base_items, new_items, list_extend=True):
|
||||
"""Recursively merge new_items into base_items.
|
||||
@ -631,16 +729,47 @@ class ActionModule(ActionBase):
|
||||
self._templar._available_variables
|
||||
)
|
||||
|
||||
if _vars['config_overrides']:
|
||||
type_merger = getattr(self, CONFIG_TYPES.get(_vars['config_type']))
|
||||
resultant = type_merger(
|
||||
config_overrides=_vars['config_overrides'],
|
||||
resultant=resultant,
|
||||
list_extend=_vars.get('list_extend', True),
|
||||
ignore_none_type=_vars.get('ignore_none_type', True),
|
||||
default_section=_vars.get('default_section', 'DEFAULT')
|
||||
config_dict_base = {}
|
||||
type_merger = getattr(self, CONFIG_TYPES.get(_vars['config_type']))
|
||||
resultant, config_dict_base = type_merger(
|
||||
config_overrides=_vars['config_overrides'],
|
||||
resultant=resultant,
|
||||
list_extend=_vars.get('list_extend', True),
|
||||
ignore_none_type=_vars.get('ignore_none_type', True),
|
||||
default_section=_vars.get('default_section', 'DEFAULT')
|
||||
)
|
||||
|
||||
changed=False
|
||||
if self._play_context.diff:
|
||||
slurpee = self._execute_module(
|
||||
module_name='slurp',
|
||||
module_args=dict(src=_vars['dest']),
|
||||
task_vars=task_vars
|
||||
)
|
||||
|
||||
config_dict_new = {}
|
||||
if 'content' in slurpee:
|
||||
dest_data = base64.b64decode(slurpee['content']).decode('utf-8')
|
||||
resultant_dest = self._templar.template(
|
||||
dest_data,
|
||||
preserve_trailing_newlines=True,
|
||||
escape_backslashes=False,
|
||||
convert_data=False
|
||||
)
|
||||
type_merger = getattr(self, CONFIG_TYPES.get(_vars['config_type']))
|
||||
resultant_new, config_dict_new = type_merger(
|
||||
config_overrides={},
|
||||
resultant=resultant_dest,
|
||||
list_extend=_vars.get('list_extend', True),
|
||||
ignore_none_type=_vars.get('ignore_none_type', True),
|
||||
default_section=_vars.get('default_section', 'DEFAULT')
|
||||
)
|
||||
|
||||
# Compare source+overrides with dest to look for changes and build diff
|
||||
cmp_dicts = DictCompare(config_dict_new, config_dict_base)
|
||||
mods, changed = cmp_dicts.get_changes()
|
||||
|
||||
|
||||
# Re-template the resultant object as it may have new data within it
|
||||
# as provided by an override variable.
|
||||
resultant = self._templar.template(
|
||||
@ -691,6 +820,11 @@ class ActionModule(ActionBase):
|
||||
module_args=new_module_args,
|
||||
task_vars=task_vars
|
||||
)
|
||||
|
||||
rc['changed'] = changed
|
||||
if self._play_context.diff:
|
||||
rc['diff'] = []
|
||||
rc['diff'].append({'prepared': json.dumps(mods, indent=4, sort_keys=True)})
|
||||
if self._task.args.get('content'):
|
||||
os.remove(_vars['source'])
|
||||
return rc
|
||||
|
6
releasenotes/notes/diffmode-e8f9a041f662a2ef.yaml
Normal file
6
releasenotes/notes/diffmode-e8f9a041f662a2ef.yaml
Normal file
@ -0,0 +1,6 @@
|
||||
---
|
||||
features:
|
||||
- Compare dict vars of before and after configuration to determine whether
|
||||
the config keys or values have changed so a configuration file will not
|
||||
be incorrectly marked as changed when only the ordering has changed.
|
||||
- Set diff return variable to a dict of changes applied.
|
@ -1,4 +1,24 @@
|
||||
- name: apt_package_pinning
|
||||
src: https://git.openstack.org/openstack/openstack-ansible-apt_package_pinning
|
||||
scm: git
|
||||
version: master
|
||||
- name: pip_install
|
||||
src: https://git.openstack.org/openstack/openstack-ansible-pip_install
|
||||
scm: git
|
||||
version: master
|
||||
- name: openstack_hosts
|
||||
src: https://git.openstack.org/openstack/openstack-ansible-openstack_hosts
|
||||
scm: git
|
||||
version: master
|
||||
- name: lxc_hosts
|
||||
src: https://git.openstack.org/openstack/openstack-ansible-lxc_hosts
|
||||
scm: git
|
||||
version: master
|
||||
- name: lxc_container_create
|
||||
src: https://git.openstack.org/openstack/openstack-ansible-lxc_container_create
|
||||
scm: git
|
||||
version: master
|
||||
- name: openstack_openrc
|
||||
src: https://git.openstack.org/openstack/openstack-ansible-openstack_openrc
|
||||
scm: git
|
||||
version: master
|
||||
|
@ -20,11 +20,8 @@ container_networks:
|
||||
address: "{{ ansible_host }}"
|
||||
bridge: "br-mgmt"
|
||||
interface: "eth1"
|
||||
netmask: "255.255.252.0"
|
||||
netmask: "255.255.255.0"
|
||||
type: "veth"
|
||||
static_routes:
|
||||
- cidr: 10.100.100.0/24
|
||||
gateway: 10.100.100.1
|
||||
|
||||
properties: {}
|
||||
|
||||
|
@ -1,20 +1,9 @@
|
||||
[all]
|
||||
localhost
|
||||
container1
|
||||
|
||||
[hosts]
|
||||
localhost
|
||||
|
||||
[all_containers]
|
||||
container1
|
||||
container2
|
||||
|
||||
# This is used to test I75f9d0f55ecd875caa1bf608a77c92f950b679a1
|
||||
[hosts]
|
||||
localhost_alt ansible_host=localhost
|
||||
[all_containers]
|
||||
container3 physical_host=localhost_alt
|
||||
|
||||
# This is meant to test If594914df53efacc6d5bba148f4f46280f5a117d
|
||||
[fake_hosts]
|
||||
fakehost ansible_host=1.1.1.1
|
||||
[hosts:children]
|
||||
fake_hosts
|
||||
[fake_containers]
|
||||
fakecontainer container_name="{{ inventory_hostname }}" physical_host=fakehost
|
||||
|
7
tests/templates/test_diff.ini
Normal file
7
tests/templates/test_diff.ini
Normal file
@ -0,0 +1,7 @@
|
||||
[DEFAULT]
|
||||
|
||||
[section1]
|
||||
baz = baz
|
||||
|
||||
[section2]
|
||||
foo = bar
|
6
tests/templates/test_diff_remove.ini
Normal file
6
tests/templates/test_diff_remove.ini
Normal file
@ -0,0 +1,6 @@
|
||||
[DEFAULT]
|
||||
|
||||
[section1]
|
||||
baz = baz
|
||||
|
||||
[section2]
|
259
tests/test-common-tasks.yml
Normal file
259
tests/test-common-tasks.yml
Normal file
@ -0,0 +1,259 @@
|
||||
---
|
||||
# Copyright 2018, Rackspace US
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# Test basic function of config_template
|
||||
- name: Template test INI template
|
||||
config_template:
|
||||
src: "{{ playbook_dir }}/templates/test.ini"
|
||||
dest: "/tmp/test.ini"
|
||||
config_overrides: "{{ test_config_ini_overrides }}"
|
||||
config_type: "ini"
|
||||
register: test_ini
|
||||
notify: test_ini check diff
|
||||
|
||||
- name: Read test.ini
|
||||
slurp:
|
||||
src: /tmp/test.ini
|
||||
register: ini_file
|
||||
- debug:
|
||||
msg: "ini - {{ ini_file.content | b64decode }}"
|
||||
- name: Validate output
|
||||
assert:
|
||||
that:
|
||||
- "(lookup('ini', 'new_key section=DEFAULT file=/tmp/test.ini')) == 'new_value'"
|
||||
- "(lookup('ini', 'baz section=foo file=/tmp/test.ini')) == 'bar'"
|
||||
|
||||
# Test basic function of config_template with content instead of src
|
||||
- name: Template test INI template
|
||||
config_template:
|
||||
content: "{{ lookup('file', playbook_dir + '/templates/test.ini') }}"
|
||||
dest: "/tmp/test_with_content.ini"
|
||||
config_overrides: "{{ test_config_ini_overrides }}"
|
||||
config_type: "ini"
|
||||
register: test_with_content_ini
|
||||
notify: test_with_content_ini check diff
|
||||
|
||||
- name: Read test.ini
|
||||
slurp:
|
||||
src: /tmp/test_with_content.ini
|
||||
register: ini_file_with_content
|
||||
- debug:
|
||||
msg: "ini - {{ ini_file_with_content.content | b64decode }}"
|
||||
- name: Validate output
|
||||
assert:
|
||||
that:
|
||||
- "(lookup('ini', 'new_key section=DEFAULT file=/tmp/test_with_content.ini')) == 'new_value'"
|
||||
- "(lookup('ini', 'baz section=foo file=/tmp/test_with_content.ini')) == 'bar'"
|
||||
|
||||
# Test list additions in config_template
|
||||
- name: Template test YML template
|
||||
config_template:
|
||||
src: "{{ playbook_dir }}/templates/test.yml"
|
||||
dest: "/tmp/test_extend.yml"
|
||||
config_overrides: "{{ test_config_yml_overrides }}"
|
||||
config_type: "yaml"
|
||||
list_extend: True
|
||||
register: test_extend_yml
|
||||
notify: test_extend_yml check diff
|
||||
|
||||
- name: Read test_extend.yml
|
||||
slurp:
|
||||
src: /tmp/test_extend.yml
|
||||
register: extend_file
|
||||
- debug:
|
||||
msg: "extend - {{ extend_file.content | b64decode }}"
|
||||
- debug:
|
||||
msg: "extend.expected - {{ extend_file_expected.content | b64decode }}"
|
||||
- name: Compare files
|
||||
assert:
|
||||
that:
|
||||
- "(extend_file.content | b64decode) == (extend_file_expected.content | b64decode)"
|
||||
|
||||
# Test list replacement in config_template
|
||||
- name: Template test YML template
|
||||
config_template:
|
||||
src: "{{ playbook_dir }}/templates/test.yml"
|
||||
dest: "/tmp/test_no_extend.yml"
|
||||
config_overrides: "{{ test_config_yml_overrides }}"
|
||||
config_type: "yaml"
|
||||
list_extend: False
|
||||
register: test_no_extend_yml
|
||||
notify: test_no_extend_yml check diff
|
||||
|
||||
- name: Read test_no_extend.yml
|
||||
slurp:
|
||||
src: /tmp/test_no_extend.yml
|
||||
register: no_extend_file
|
||||
- debug:
|
||||
msg: "no_extend - {{ no_extend_file.content | b64decode }}"
|
||||
- debug:
|
||||
msg: "no_extend.expected - {{ no_extend_file_expected.content | b64decode }}"
|
||||
- name: Compare files
|
||||
assert:
|
||||
that:
|
||||
- "(no_extend_file.content | b64decode) == (no_extend_file_expected.content | b64decode)"
|
||||
|
||||
# Test dumping hostvars using config overrides
|
||||
- name: Template test YML template with hostvars override
|
||||
config_template:
|
||||
src: "{{ playbook_dir }}/templates/test.yml"
|
||||
dest: "/tmp/test_hostvars.yml"
|
||||
config_overrides: "{{ test_config_yml_hostvars_overrides }}"
|
||||
config_type: "yaml"
|
||||
register: test_hostvars_yml
|
||||
notify: test_hostvars_yml check diff
|
||||
|
||||
- name: Read test_hostvars.yml
|
||||
slurp:
|
||||
src: /tmp/test_hostvars.yml
|
||||
register: hostvars_file
|
||||
- debug:
|
||||
msg: "hostvars - {{ (hostvars_file.content | b64decode | from_yaml).test_hostvar }}"
|
||||
- debug:
|
||||
msg: "hostvars.expected - {{ test_config_yml_hostvars_overrides.test_hostvar }}"
|
||||
- name: Compare files
|
||||
assert:
|
||||
that:
|
||||
- "((hostvars_file.content | b64decode | from_yaml).test_hostvar) == (test_config_yml_hostvars_overrides.test_hostvar)"
|
||||
|
||||
|
||||
# Test content attribute with a dictionary input and config_type equal to 'json'
|
||||
- name: Template test JSON template with content attribute
|
||||
config_template:
|
||||
dest: "/tmp/test_content_no_overrides.json"
|
||||
config_overrides: {}
|
||||
config_type: "json"
|
||||
content: "{{ lookup('file', playbook_dir ~ '/templates/test.json') | from_json }}"
|
||||
register: test_content_no_overrides_json
|
||||
notify: test_content_no_overrides_json check diff
|
||||
|
||||
- name: Read test_content_no_overrides.json
|
||||
slurp:
|
||||
src: /tmp/test_content_no_overrides.json
|
||||
register: content_no_overrides_file
|
||||
- debug:
|
||||
msg: "content_no_overrides.json - {{ content_no_overrides_file.content | b64decode | from_json }}"
|
||||
- debug:
|
||||
msg: "content_no_overrides.json.expected - {{ content_no_overrides_file_expected.content | b64decode | from_json }}"
|
||||
# NOTE (alextricity25): The config_template module doesn't use ordered dicts when reading and writing json
|
||||
# data, so we can't guarantee that the string literal of both file's content will be the same. Instead, we compare
|
||||
# the content after transforming it into a dictionary.
|
||||
- name: Compare file content
|
||||
assert:
|
||||
that:
|
||||
- "(content_no_overrides_file.content | b64decode | from_json) == (content_no_overrides_file_expected.content | b64decode | from_json)"
|
||||
|
||||
# Test the ignore_none_type attribute when set to False
|
||||
- name: Template test with ignore_none_type set to false
|
||||
config_template:
|
||||
src: "{{ playbook_dir }}/templates/test_ignore_none_type.ini"
|
||||
dest: "/tmp/test_ignore_none_type.ini"
|
||||
config_overrides: "{{ test_config_ini_overrides }}"
|
||||
config_type: "ini"
|
||||
ignore_none_type: False
|
||||
register: test_ignore_none_type_ini
|
||||
notify: test_ignore_none_type_ini check diff
|
||||
|
||||
- name: Read test_ignore_none_type.ini
|
||||
slurp:
|
||||
src: /tmp/test_ignore_none_type.ini
|
||||
register: test_ignore_none_type
|
||||
- debug:
|
||||
msg: "test_ignore_none_type.ini - {{ test_ignore_none_type.content | b64decode }}"
|
||||
- name: Validate output has valueless options printed out
|
||||
assert:
|
||||
that:
|
||||
- "{{ test_ignore_none_type.content | b64decode | search('(?m)^india$') }}"
|
||||
- "{{ test_ignore_none_type.content | b64decode | search('(?m)^juliett kilo$') }}"
|
||||
|
||||
# Test basic function of config_template
|
||||
- name: Template test INI comments
|
||||
config_template:
|
||||
src: "{{ playbook_dir }}/templates/test_with_comments.ini"
|
||||
dest: "/tmp/test_with_comments.ini"
|
||||
config_overrides: "{{ test_config_ini_overrides }}"
|
||||
config_type: "ini"
|
||||
tags: test
|
||||
register: test_with_comments_ini
|
||||
notify: test_with_comments_ini check diff
|
||||
|
||||
- name: Read test.ini
|
||||
slurp:
|
||||
src: /tmp/test_with_comments.ini
|
||||
register: ini_file
|
||||
tags: test
|
||||
|
||||
- debug:
|
||||
msg: "ini - {{ ini_file.content | b64decode }}"
|
||||
- name: Validate output
|
||||
tags: test
|
||||
assert:
|
||||
that:
|
||||
- "(lookup('ini', 'new_key section=DEFAULT file=/tmp/test_with_comments.ini')) == 'new_value'"
|
||||
- "(lookup('ini', 'baz section=foo file=/tmp/test_with_comments.ini')) == 'bar'"
|
||||
- "{{ ini_file.content | b64decode | search('#This is a comment')}}"
|
||||
- "{{ ini_file.content | b64decode | search('# A default section comment\n# broken into multiple lines\n\\[DEFAULT\\]')}}"
|
||||
|
||||
- name: Template multiple times to assert no changes
|
||||
config_template:
|
||||
src: "{{ playbook_dir }}/templates/test_with_comments.ini"
|
||||
dest: "/tmp/test_with_comments.ini"
|
||||
config_type: "ini"
|
||||
config_overrides: "{{ item[1] }}"
|
||||
register: template_changed
|
||||
failed_when: template_changed is changed
|
||||
with_nested:
|
||||
- [ 0, 1, 2 ]
|
||||
- [ "{{ test_config_ini_overrides }}" ]
|
||||
|
||||
- name: Put down default_section_expected file
|
||||
copy:
|
||||
src: "{{ playbook_dir }}/files/test_default_section.ini.expected"
|
||||
dest: "/tmp/test_default_section.ini"
|
||||
|
||||
- name: Template using default_section
|
||||
config_template:
|
||||
src: "{{ playbook_dir }}/templates/test_default_section.ini"
|
||||
dest: "/tmp/test_default_section.ini"
|
||||
config_type: "ini"
|
||||
config_overrides: "{{ test_default_section_overrides }}"
|
||||
default_section: "global"
|
||||
register: template_changed
|
||||
failed_when: template_changed is changed
|
||||
|
||||
- name: Write ini for testing diff output
|
||||
config_template:
|
||||
src: "{{ playbook_dir }}/templates/test_diff.ini"
|
||||
dest: "/tmp/test_diff.ini"
|
||||
config_type: "ini"
|
||||
config_overrides: {}
|
||||
|
||||
- name: Test ini with additions and changed
|
||||
config_template:
|
||||
src: "{{ playbook_dir }}/templates/test_diff.ini"
|
||||
dest: "/tmp/test_diff.ini"
|
||||
config_type: "ini"
|
||||
config_overrides: "{{ test_diff_overrides }}"
|
||||
register: test_diff_ini
|
||||
notify: test_diff_ini check diff
|
||||
|
||||
- name: Test ini with removes
|
||||
config_template:
|
||||
src: "{{ playbook_dir }}/templates/test_diff_remove.ini"
|
||||
dest: "/tmp/test_diff.ini"
|
||||
config_type: "ini"
|
||||
config_overrides: "{{ test_diff_overrides }}"
|
||||
register: test_diff_remove_ini
|
||||
notify: test_diff_remove_ini check diff
|
450
tests/test.yml
450
tests/test.yml
@ -13,222 +13,109 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# Setup the host
|
||||
- include: common/test-prepare-keys.yml
|
||||
|
||||
- include: common/test-prepare-host.yml
|
||||
|
||||
# Temporary work around for issue with lxc_host where on second run after
|
||||
# all iptables rules have been removed, the lxc_host role is not checking
|
||||
# that the rules are defined and adding them back in if they are not.
|
||||
- name: Add iptables rules back in
|
||||
hosts: localhost
|
||||
connection: local
|
||||
gather_facts: yes
|
||||
become: yes
|
||||
|
||||
vars:
|
||||
do_not_fail_lint: true
|
||||
|
||||
tasks:
|
||||
- name: Ensure lxc iptables rules
|
||||
command: /usr/local/bin/lxc-system-manage iptables-create
|
||||
when: do_not_fail_lint
|
||||
|
||||
- include: common/test-prepare-containers.yml
|
||||
|
||||
- name: Test config_template
|
||||
hosts: localhost
|
||||
connection: local
|
||||
gather_facts: yes
|
||||
|
||||
tasks:
|
||||
# Test basic function of config_template
|
||||
- name: Template test INI template
|
||||
config_template:
|
||||
src: "{{ playbook_dir }}/templates/test.ini"
|
||||
dest: "/tmp/test.ini"
|
||||
config_overrides: "{{ test_config_ini_overrides }}"
|
||||
config_type: "ini"
|
||||
|
||||
- name: Read test.ini
|
||||
slurp:
|
||||
src: /tmp/test.ini
|
||||
register: ini_file
|
||||
- debug:
|
||||
msg: "ini - {{ ini_file.content | b64decode }}"
|
||||
- name: Validate output
|
||||
assert:
|
||||
that:
|
||||
- "(lookup('ini', 'new_key section=DEFAULT file=/tmp/test.ini')) == 'new_value'"
|
||||
- "(lookup('ini', 'baz section=foo file=/tmp/test.ini')) == 'bar'"
|
||||
|
||||
# Test basic function of config_template with content instead of src
|
||||
- name: Template test INI template
|
||||
config_template:
|
||||
content: "{{ lookup('file', playbook_dir + '/templates/test.ini') }}"
|
||||
dest: "/tmp/test_with_content.ini"
|
||||
config_overrides: "{{ test_config_ini_overrides }}"
|
||||
config_type: "ini"
|
||||
|
||||
- name: Read test.ini
|
||||
slurp:
|
||||
src: /tmp/test_with_content.ini
|
||||
register: ini_file_with_content
|
||||
- debug:
|
||||
msg: "ini - {{ ini_file_with_content.content | b64decode }}"
|
||||
- name: Validate output
|
||||
assert:
|
||||
that:
|
||||
- "(lookup('ini', 'new_key section=DEFAULT file=/tmp/test_with_content.ini')) == 'new_value'"
|
||||
- "(lookup('ini', 'baz section=foo file=/tmp/test_with_content.ini')) == 'bar'"
|
||||
|
||||
# Test list additions in config_template
|
||||
- name: Template test YML template
|
||||
config_template:
|
||||
src: "{{ playbook_dir }}/templates/test.yml"
|
||||
dest: "/tmp/test_extend.yml"
|
||||
config_overrides: "{{ test_config_yml_overrides }}"
|
||||
config_type: "yaml"
|
||||
list_extend: True
|
||||
|
||||
- name: Read test_extend.yml
|
||||
slurp:
|
||||
src: /tmp/test_extend.yml
|
||||
register: extend_file
|
||||
- name: Read expected test_extend.yml
|
||||
slurp:
|
||||
src: "{{ playbook_dir }}/files/test_extend.yml.expected"
|
||||
register: extend_file_expected
|
||||
- debug:
|
||||
msg: "extend - {{ extend_file.content | b64decode }}"
|
||||
- debug:
|
||||
msg: "extend.expected - {{ extend_file_expected.content | b64decode }}"
|
||||
- name: Compare files
|
||||
assert:
|
||||
that:
|
||||
- "(extend_file.content | b64decode) == (extend_file_expected.content | b64decode)"
|
||||
|
||||
# Test list replacement in config_template
|
||||
- name: Template test YML template
|
||||
config_template:
|
||||
src: "{{ playbook_dir }}/templates/test.yml"
|
||||
dest: "/tmp/test_no_extend.yml"
|
||||
config_overrides: "{{ test_config_yml_overrides }}"
|
||||
config_type: "yaml"
|
||||
list_extend: False
|
||||
- name: Read test_no_extend.yml
|
||||
slurp:
|
||||
src: /tmp/test_no_extend.yml
|
||||
register: no_extend_file
|
||||
- name: Read expected test_no_extend.yml
|
||||
slurp:
|
||||
src: "{{ playbook_dir }}/files/test_no_extend.yml.expected"
|
||||
register: no_extend_file_expected
|
||||
- debug:
|
||||
msg: "no_extend - {{ no_extend_file.content | b64decode }}"
|
||||
- debug:
|
||||
msg: "no_extend.expected - {{ no_extend_file_expected.content | b64decode }}"
|
||||
- name: Compare files
|
||||
assert:
|
||||
that:
|
||||
- "(no_extend_file.content | b64decode) == (no_extend_file_expected.content | b64decode)"
|
||||
|
||||
# Test dumping hostvars using config overrides
|
||||
- name: Template test YML template with hostvars override
|
||||
config_template:
|
||||
src: "{{ playbook_dir }}/templates/test.yml"
|
||||
dest: "/tmp/test_hostvars.yml"
|
||||
config_overrides: "{{ test_config_yml_hostvars_overrides }}"
|
||||
config_type: "yaml"
|
||||
- name: Read test_hostvars.yml
|
||||
slurp:
|
||||
src: /tmp/test_hostvars.yml
|
||||
register: hostvars_file
|
||||
- debug:
|
||||
msg: "hostvars - {{ (hostvars_file.content | b64decode | from_yaml).test_hostvar }}"
|
||||
- debug:
|
||||
msg: "hostvars.expected - {{ test_config_yml_hostvars_overrides.test_hostvar }}"
|
||||
- name: Compare files
|
||||
assert:
|
||||
that:
|
||||
- "((hostvars_file.content | b64decode | from_yaml).test_hostvar) == (test_config_yml_hostvars_overrides.test_hostvar)"
|
||||
|
||||
|
||||
# Test content attribute with a dictionary input and config_type equal to 'json'
|
||||
- name: Template test JSON template with content attribute
|
||||
config_template:
|
||||
dest: "/tmp/test_content_no_overrides.json"
|
||||
config_overrides: {}
|
||||
config_type: "json"
|
||||
content: "{{ lookup('file', playbook_dir ~ '/templates/test.json') | from_json }}"
|
||||
- name: Read test_content_no_overrides.json
|
||||
slurp:
|
||||
src: /tmp/test_content_no_overrides.json
|
||||
register: content_no_overrides_file
|
||||
- name: Read expected test_content_no_overrides.json
|
||||
slurp:
|
||||
src: "{{ playbook_dir }}/files/test_content_no_overrides.json.expected"
|
||||
register: content_no_overrides_file_expected
|
||||
- debug:
|
||||
msg: "content_no_overrides.json - {{ content_no_overrides_file.content | b64decode | from_json }}"
|
||||
- debug:
|
||||
msg: "content_no_overrides.json.expected - {{ content_no_overrides_file_expected.content | b64decode | from_json }}"
|
||||
# NOTE (alextricity25): The config_template module doesn't use ordered dicts when reading and writing json
|
||||
# data, so we can't guarantee that the string literal of both file's content will be the same. Instead, we compare
|
||||
# the content after transforming it into a dictionary.
|
||||
- name: Compare file content
|
||||
|
||||
- import_tasks: test-common-tasks.yml
|
||||
|
||||
- import_tasks: test-common-tasks.yml
|
||||
delegate_to: container1
|
||||
|
||||
handlers:
|
||||
- name: test_ini check diff
|
||||
assert:
|
||||
that:
|
||||
- "(content_no_overrides_file.content | b64decode | from_json) == (content_no_overrides_file_expected.content | b64decode | from_json)"
|
||||
- test_ini.diff[0].prepared|from_json == diff_ini
|
||||
|
||||
# Test the ignore_none_type attribute when set to False
|
||||
- name: Template test with ignore_none_type set to false
|
||||
config_template:
|
||||
src: "{{ playbook_dir }}/templates/test_ignore_none_type.ini"
|
||||
dest: "/tmp/test_ignore_none_type.ini"
|
||||
config_overrides: "{{ test_config_ini_overrides }}"
|
||||
config_type: "ini"
|
||||
ignore_none_type: False
|
||||
- name: Read test_ignore_none_type.ini
|
||||
slurp:
|
||||
src: /tmp/test_ignore_none_type.ini
|
||||
register: test_ignore_none_type
|
||||
- debug:
|
||||
msg: "test_ignore_none_type.ini - {{ test_ignore_none_type.content | b64decode }}"
|
||||
- name: Validate output has valueless options printed out
|
||||
- name: test_with_content_ini check diff
|
||||
assert:
|
||||
that:
|
||||
- "{{ test_ignore_none_type.content | b64decode | search('(?m)^india$') }}"
|
||||
- "{{ test_ignore_none_type.content | b64decode | search('(?m)^juliett kilo$') }}"
|
||||
- test_with_content_ini.diff[0].prepared|from_json == diff_ini
|
||||
|
||||
# Test basic function of config_template
|
||||
- name: Template test INI comments
|
||||
config_template:
|
||||
src: "{{ playbook_dir }}/templates/test_with_comments.ini"
|
||||
dest: "/tmp/test_with_comments.ini"
|
||||
config_overrides: "{{ test_config_ini_overrides }}"
|
||||
config_type: "ini"
|
||||
tags: test
|
||||
- name: test_extend_yml check diff
|
||||
assert:
|
||||
that:
|
||||
- test_extend_yml.diff[0].prepared|from_json == diff_extend_yml
|
||||
|
||||
- name: Read test.ini
|
||||
slurp:
|
||||
src: /tmp/test_with_comments.ini
|
||||
register: ini_file
|
||||
tags: test
|
||||
- name: test_no_extend_yml check diff
|
||||
assert:
|
||||
that:
|
||||
- test_no_extend_yml.diff[0].prepared|from_json == diff_no_extend_yml
|
||||
|
||||
- debug:
|
||||
msg: "ini - {{ ini_file.content | b64decode }}"
|
||||
- name: Validate output
|
||||
- name: test_hostvars_yml check diff
|
||||
assert:
|
||||
that:
|
||||
- test_hostvars_yml.diff[0].prepared|from_yaml == diff_hostvars_yml
|
||||
|
||||
- name: test_content_no_overrides_json check diff
|
||||
assert:
|
||||
that:
|
||||
- test_content_no_overrides_json.diff[0].prepared|from_json == diff_content_no_overrides_json
|
||||
|
||||
- name: test_ignore_none_type_ini check diff
|
||||
assert:
|
||||
that:
|
||||
- test_ignore_none_type_ini.diff[0].prepared|from_json == diff_ignore_none_type_ini
|
||||
|
||||
- name: test_with_comments_ini check diff
|
||||
tags: test
|
||||
assert:
|
||||
that:
|
||||
- "(lookup('ini', 'new_key section=DEFAULT file=/tmp/test_with_comments.ini')) == 'new_value'"
|
||||
- "(lookup('ini', 'baz section=foo file=/tmp/test_with_comments.ini')) == 'bar'"
|
||||
- "{{ ini_file.content | b64decode | search('#This is a comment')}}"
|
||||
- "{{ ini_file.content | b64decode | search('# A default section comment\n# broken into multiple lines\n\\[DEFAULT\\]')}}"
|
||||
- test_with_comments_ini.diff[0].prepared|from_json == diff_with_comments_ini
|
||||
|
||||
- name: Template multiple times to assert no changes
|
||||
config_template:
|
||||
src: "{{ playbook_dir }}/templates/test_with_comments.ini"
|
||||
dest: "/tmp/test_with_comments.ini"
|
||||
config_type: "ini"
|
||||
config_overrides: "{{ item[1] }}"
|
||||
register: template_changed
|
||||
failed_when: template_changed is changed
|
||||
with_nested:
|
||||
- [ 0, 1, 2 ]
|
||||
- [ "{{ test_config_ini_overrides }}" ]
|
||||
- name: test_diff_ini check diff
|
||||
tags: test
|
||||
assert:
|
||||
that:
|
||||
- test_diff_ini.diff[0].prepared|from_json == diff_diff_ini
|
||||
|
||||
- name: Put down default_section_expected file
|
||||
copy:
|
||||
src: "{{ playbook_dir }}/files/test_default_section.ini.expected"
|
||||
dest: "/tmp/test_default_section.ini"
|
||||
|
||||
- name: Template using default_section
|
||||
config_template:
|
||||
src: "{{ playbook_dir }}/templates/test_default_section.ini"
|
||||
dest: "/tmp/test_default_section.ini"
|
||||
config_type: "ini"
|
||||
config_overrides: "{{ test_default_section_overrides }}"
|
||||
default_section: "global"
|
||||
register: template_changed
|
||||
failed_when: template_changed is changed
|
||||
- name: test_diff_remove_ini check diff
|
||||
tags: test
|
||||
assert:
|
||||
that:
|
||||
- test_diff_remove_ini.diff[0].prepared|from_json == diff_diff_remove_ini
|
||||
|
||||
vars:
|
||||
test_config_ini_overrides:
|
||||
@ -279,3 +166,198 @@
|
||||
test2: 2
|
||||
section1:
|
||||
setting2: 2
|
||||
test_diff_overrides:
|
||||
section1:
|
||||
baz: "hotel"
|
||||
section3:
|
||||
alfa: "bravo"
|
||||
diff_with_comments_ini:
|
||||
added:
|
||||
DEFAULT:
|
||||
new_key: "new_value"
|
||||
test_hosts: "\n+_unicode\n1\nstring"
|
||||
bar: {}
|
||||
foo:
|
||||
baz: "bar"
|
||||
section1:
|
||||
key1: "String1"
|
||||
key10: "10"
|
||||
key11: "11"
|
||||
key2: "string2"
|
||||
key3: "string3"
|
||||
key4: "string4"
|
||||
key5: "string5"
|
||||
key6: "string6"
|
||||
key7: "1"
|
||||
key8: "2"
|
||||
key9: "3"
|
||||
section10:
|
||||
key1: "1"
|
||||
section11:
|
||||
key1: "1"
|
||||
section2:
|
||||
key1: "value1"
|
||||
section3:
|
||||
key1: "value1"
|
||||
section4:
|
||||
key1: "value1"
|
||||
section5:
|
||||
key1: "value1"
|
||||
section6:
|
||||
key1: "value1"
|
||||
section7:
|
||||
key1: "value1"
|
||||
section8:
|
||||
key1: "1"
|
||||
section9:
|
||||
key1: "1"
|
||||
changed: {}
|
||||
removed: {}
|
||||
diff_ini:
|
||||
added:
|
||||
DEFAULT:
|
||||
new_key: "new_value"
|
||||
bar: {}
|
||||
foo:
|
||||
baz: "bar"
|
||||
section1:
|
||||
key1: "String1"
|
||||
key10: "10"
|
||||
key11: "11"
|
||||
key2: "string2"
|
||||
key3: "string3"
|
||||
key4: "string4"
|
||||
key5: "string5"
|
||||
key6: "string6"
|
||||
key7: "1"
|
||||
key8: "2"
|
||||
key9: "3"
|
||||
section10:
|
||||
key1: "1"
|
||||
section11:
|
||||
key1: "1"
|
||||
section2:
|
||||
key1: "value1"
|
||||
section3:
|
||||
key1: "value1"
|
||||
section4:
|
||||
key1: "value1"
|
||||
section5:
|
||||
key1: "value1"
|
||||
section6:
|
||||
key1: "value1"
|
||||
section7:
|
||||
key1: "value1"
|
||||
section8:
|
||||
key1: "1"
|
||||
section9:
|
||||
key1: "1"
|
||||
changed: {}
|
||||
removed: {}
|
||||
diff_extend_yml:
|
||||
added:
|
||||
list_one:
|
||||
- "one"
|
||||
- "two"
|
||||
- "three"
|
||||
- "four"
|
||||
- 4
|
||||
list_two:
|
||||
- "one"
|
||||
- "two"
|
||||
changed: {}
|
||||
removed: {}
|
||||
diff_no_extend_yml:
|
||||
added:
|
||||
list_one:
|
||||
- "four"
|
||||
- 4
|
||||
list_two:
|
||||
- "one"
|
||||
- "two"
|
||||
changed: {}
|
||||
removed: {}
|
||||
diff_hostvars_yml:
|
||||
added:
|
||||
list_one:
|
||||
- "one"
|
||||
- "two"
|
||||
- "three"
|
||||
list_two:
|
||||
- "one"
|
||||
- "two"
|
||||
test_hostvar: "{{ ansible_default_ipv4.address }}"
|
||||
changed: {}
|
||||
removed: {}
|
||||
diff_ignore_none_type_ini:
|
||||
added:
|
||||
DEFAULT:
|
||||
new_key: "new_value"
|
||||
alfa:
|
||||
bravo: "charlie"
|
||||
delta: "echo"
|
||||
foo:
|
||||
baz: "bar"
|
||||
foxtrot:
|
||||
golf: "hotel"
|
||||
india: null
|
||||
juliett kilo: null
|
||||
lima: "mike"
|
||||
section1:
|
||||
key1: "String1"
|
||||
key10: "10"
|
||||
key11: "11"
|
||||
key2: "string2"
|
||||
key3: "string3"
|
||||
key4: "string4"
|
||||
key5: "string5"
|
||||
key6: "string6"
|
||||
key7: "1"
|
||||
key8: "2"
|
||||
key9: "3"
|
||||
section10:
|
||||
key1: "1"
|
||||
section11:
|
||||
key1: "1"
|
||||
section2:
|
||||
key1: "value1"
|
||||
section3:
|
||||
key1: "value1"
|
||||
section4:
|
||||
key1: "value1"
|
||||
section5:
|
||||
key1: "value1"
|
||||
section6:
|
||||
key1: "value1"
|
||||
section7:
|
||||
key1: "value1"
|
||||
section8:
|
||||
key1: "1"
|
||||
section9:
|
||||
key1: "1"
|
||||
changed: {}
|
||||
removed: {}
|
||||
diff_content_no_overrides_json:
|
||||
added:
|
||||
alfa: "bravo"
|
||||
charlie: "echo"
|
||||
foxtrot:
|
||||
golf: "hotel"
|
||||
changed: {}
|
||||
removed: {}
|
||||
diff_diff_ini:
|
||||
added:
|
||||
section3:
|
||||
alfa: "bravo"
|
||||
changed:
|
||||
section1:
|
||||
baz:
|
||||
current_val: "baz"
|
||||
new_val: "hotel"
|
||||
removed: {}
|
||||
diff_diff_remove_ini:
|
||||
added: {}
|
||||
changed: {}
|
||||
removed:
|
||||
section2:
|
||||
foo: "bar"
|
||||
|
7
tox.ini
7
tox.ini
@ -13,6 +13,7 @@ commands =
|
||||
passenv =
|
||||
COMMON_TESTS_PATH
|
||||
HOME
|
||||
USER
|
||||
http_proxy
|
||||
HTTP_PROXY
|
||||
https_proxy
|
||||
@ -29,12 +30,8 @@ setenv =
|
||||
TEST_IDEMPOTENCE=false
|
||||
VIRTUAL_ENV={envdir}
|
||||
WORKING_DIR={toxinidir}
|
||||
ANSIBLE_PARAMETERS=--diff
|
||||
ANSIBLE_ACTION_PLUGINS={toxinidir}/action
|
||||
ANSIBLE_CALLBACK_PLUGINS={toxinidir}/callback
|
||||
ANSIBLE_CONNECTION_PLUGINS={toxinidir}/connection
|
||||
ANSIBLE_FILTER_PLUGINS={toxinidir}/filter
|
||||
ANSIBLE_LOOKUP_PLUGINS={toxinidir}/lookup
|
||||
ANSIBLE_STRATEGY_PLUGINS={toxinidir}/strategy
|
||||
ANSIBLE_LIBRARY={toxinidir}/library
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user