diff --git a/ansible/action_plugins/merge_yaml.py b/ansible/action_plugins/merge_yaml.py index 371905d0d4..0bff14dc24 100755 --- a/ansible/action_plugins/merge_yaml.py +++ b/ansible/action_plugins/merge_yaml.py @@ -32,6 +32,7 @@ except ImportError: from ansible import constants from ansible.plugins import action +import six class ActionModule(action.ActionBase): @@ -76,7 +77,7 @@ class ActionModule(action.ActionBase): if not isinstance(sources, list): sources = [sources] for source in sources: - output.update(self.read_config(source)) + Utils.update_nested_conf(output, self.read_config(source)) # restore original vars self._templar.set_available_variables(old_vars) @@ -109,3 +110,14 @@ class ActionModule(action.ActionBase): finally: shutil.rmtree(local_tempdir) return result + + +class Utils(object): + @staticmethod + def update_nested_conf(conf, update): + for k, v in six.iteritems(update): + if isinstance(v, dict): + conf[k] = Utils.update_nested_conf(conf.get(k, {}), v) + else: + conf[k] = v + return conf diff --git a/tests/test_merge_yaml.py b/tests/test_merge_yaml.py new file mode 100644 index 0000000000..00b56614c4 --- /dev/null +++ b/tests/test_merge_yaml.py @@ -0,0 +1,128 @@ +#!/usr/bin/env python + +# Copyright 2018 StackHPC Ltd. +# 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. + +import imp +import os + +from oslotest import base + +PROJECT_DIR = os.path.abspath(os.path.join(os. path.dirname(__file__), '../')) +MERGE_YAML_FILE = os.path.join(PROJECT_DIR, + 'ansible/action_plugins/merge_yaml.py') + +merge_yaml = imp.load_source('merge_yaml', MERGE_YAML_FILE) + + +class MergeYamlConfigTest(base.BaseTestCase): + + def test_merge_no_update(self): + initial_conf = { + 'foo': 'bar', + 'egg': 'spam' + } + actual = merge_yaml.Utils.update_nested_conf(initial_conf, {}) + expected = { + 'foo': 'bar', + 'egg': 'spam' + } + self.assertDictEqual(actual, expected) + + def test_merge_flat_update_key(self): + initial_conf = { + 'foo': 'bar', + 'egg': 'spam' + } + actual = merge_yaml.Utils.update_nested_conf( + initial_conf, {'egg': 'ham'}) + expected = { + 'foo': 'bar', + 'egg': 'ham' + } + self.assertDictEqual(actual, expected) + + def test_merge_flat_new_key(self): + initial_conf = { + 'foo': 'bar', + 'egg': 'spam' + } + actual = merge_yaml.Utils.update_nested_conf( + initial_conf, {'spam': 'ham'}) + expected = { + 'foo': 'bar', + 'egg': 'spam', + 'spam': 'ham' + } + self.assertDictEqual(actual, expected) + + def test_merge_nested_update_key(self): + initial_conf = { + 'foo': { + 'a': 'b', + }, + 'bar': { + 'a': False, + 'b': 'INFO' + } + } + actual = merge_yaml.Utils.update_nested_conf( + initial_conf, {'bar': {'a': True}}) + expected = { + 'foo': { + 'a': 'b', + }, + 'bar': { + 'a': True, + 'b': 'INFO' + } + } + self.assertDictEqual(actual, expected) + + def test_merge_nested_new_key(self): + initial_conf = { + 'foo': { + 'a': 'b', + 'c': 30 + } + } + actual = merge_yaml.Utils.update_nested_conf( + initial_conf, {'egg': {'spam': 10}}) + expected = { + 'foo': { + 'a': 'b', + 'c': 30, + }, + 'egg': { + 'spam': 10, + } + } + self.assertDictEqual(actual, expected) + + def test_merge_nested_new_nested_key(self): + initial_conf = { + 'foo': { + 'a': 'b', + 'c': 30 + } + } + actual = merge_yaml.Utils.update_nested_conf( + initial_conf, {'foo': {'spam': 10}}) + expected = { + 'foo': { + 'a': 'b', + 'c': 30, + 'spam': 10, + } + } + self.assertDictEqual(actual, expected)