Skip to content
Snippets Groups Projects
Commit e2b25023 authored by Zuul's avatar Zuul Committed by Gerrit Code Review
Browse files

Merge "Add extend_lists option to merge_yaml"

parents c3a4f78d 69a6acf7
No related branches found
No related tags found
No related merge requests found
...@@ -30,6 +30,7 @@ except ImportError: ...@@ -30,6 +30,7 @@ except ImportError:
from ansible import constants from ansible import constants
from ansible import errors as ansible_errors
from ansible.plugins import action from ansible.plugins import action
DOCUMENTATION = ''' DOCUMENTATION = '''
...@@ -50,6 +51,20 @@ options: ...@@ -50,6 +51,20 @@ options:
default: None default: None
required: True required: True
type: str type: str
extend_lists:
description:
- For a given key referencing a list, this determines whether
the list items should be combined with the items in another
document if an equivalent key is found. An equivalent key
has the same parents and value as the first. The default
behaviour is to replace existing entries i.e if you have
two yaml documents that both define a list with an equivalent
key, the value from the document that appears later in the
list of sources will replace the value that appeared in the
earlier one.
default: False
required: False
type: bool
author: Sean Mooney author: Sean Mooney
''' '''
...@@ -107,10 +122,12 @@ class ActionModule(action.ActionBase): ...@@ -107,10 +122,12 @@ class ActionModule(action.ActionBase):
output = {} output = {}
sources = self._task.args.get('sources', None) sources = self._task.args.get('sources', None)
extend_lists = self._task.args.get('extend_lists', False)
if not isinstance(sources, list): if not isinstance(sources, list):
sources = [sources] sources = [sources]
for source in sources: for source in sources:
Utils.update_nested_conf(output, self.read_config(source)) Utils.update_nested_conf(
output, self.read_config(source), extend_lists)
# restore original vars # restore original vars
self._templar.set_available_variables(old_vars) self._templar.set_available_variables(old_vars)
...@@ -124,7 +141,7 @@ class ActionModule(action.ActionBase): ...@@ -124,7 +141,7 @@ class ActionModule(action.ActionBase):
new_task = self._task.copy() new_task = self._task.copy()
new_task.args.pop('sources', None) new_task.args.pop('sources', None)
new_task.args.pop('extend_lists', None)
new_task.args.update( new_task.args.update(
dict( dict(
src=result_file src=result_file
...@@ -147,10 +164,22 @@ class ActionModule(action.ActionBase): ...@@ -147,10 +164,22 @@ class ActionModule(action.ActionBase):
class Utils(object): class Utils(object):
@staticmethod @staticmethod
def update_nested_conf(conf, update): def update_nested_conf(conf, update, extend_lists=False):
for k, v in update.items(): for k, v in update.items():
if isinstance(v, dict): if isinstance(v, dict):
conf[k] = Utils.update_nested_conf(conf.get(k, {}), v) conf[k] = Utils.update_nested_conf(
conf.get(k, {}), v, extend_lists)
elif k in conf and isinstance(conf[k], list) and extend_lists:
if not isinstance(v, list):
errmsg = (
"Failure merging key `%(key)s` in dictionary "
"`%(dictionary)s`. Expecting a list, but received: "
"`%(value)s`, which is of type: `%(type)s`" % {
"key": k, "dictionary": conf,
"value": v, "type": type(v)}
)
raise ansible_errors.AnsibleModuleError(errmsg)
conf[k].extend(v)
else: else:
conf[k] = v conf[k] = v
return conf return conf
...@@ -16,12 +16,12 @@ ...@@ -16,12 +16,12 @@
import imp import imp
import os import os
from ansible.errors import AnsibleModuleError
from oslotest import base from oslotest import base
PROJECT_DIR = os.path.abspath(os.path.join(os. path.dirname(__file__), '../')) PROJECT_DIR = os.path.abspath(os.path.join(os. path.dirname(__file__), '../'))
MERGE_YAML_FILE = os.path.join(PROJECT_DIR, MERGE_YAML_FILE = os.path.join(PROJECT_DIR,
'ansible/action_plugins/merge_yaml.py') 'ansible/action_plugins/merge_yaml.py')
merge_yaml = imp.load_source('merge_yaml', MERGE_YAML_FILE) merge_yaml = imp.load_source('merge_yaml', MERGE_YAML_FILE)
...@@ -126,3 +126,51 @@ class MergeYamlConfigTest(base.BaseTestCase): ...@@ -126,3 +126,51 @@ class MergeYamlConfigTest(base.BaseTestCase):
} }
} }
self.assertDictEqual(actual, expected) self.assertDictEqual(actual, expected)
def test_merge_nested_extend_lists(self):
initial_conf = {
'level0': {
'level1': {
"mylist": ["one", "two"]
},
}
}
extension = {
'level0': {
'level1': {
"mylist": ["three"]
},
}
}
actual = merge_yaml.Utils.update_nested_conf(
initial_conf, extension, extend_lists=True)
expected = {
'level0': {
'level1': {
"mylist": ["one", "two", "three"]
},
}
}
self.assertDictEqual(actual, expected)
def test_merge_nested_extend_lists_mismatch_types(self):
initial_conf = {
'level0': {
'level1': {
"mylist": ["one", "two"]
},
}
}
extension = {
'level0': {
'level1': {
"mylist": "three"
},
}
}
with self.assertRaisesRegex(AnsibleModuleError, "Failure merging key"):
merge_yaml.Utils.update_nested_conf(
initial_conf, extension, extend_lists=True)
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment