diff --git a/ansible/action_plugins/merge_configs.py b/ansible/action_plugins/merge_configs.py index 7218a2ac99..27fcc53232 100644 --- a/ansible/action_plugins/merge_configs.py +++ b/ansible/action_plugins/merge_configs.py @@ -1,6 +1,7 @@ #!/usr/bin/python # Copyright 2015 Sam Yaple +# Copyright 2017 99Cloud Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,12 +15,71 @@ # See the License for the specific language governing permissions and # limitations under the License. -import ConfigParser + +import collections import inspect import os -from six import StringIO from ansible.plugins import action +from six import StringIO + +from oslo_config import iniparser + + +class OverrideConfigParser(iniparser.BaseParser): + + def __init__(self): + self._cur_sections = collections.OrderedDict() + self._sections = collections.OrderedDict() + self._cur_section = None + + def assignment(self, key, value): + cur_value = self._cur_section.get(key) + if len(value) == 1 and value[0] == '': + value = [] + if not cur_value: + self._cur_section[key] = [value] + else: + self._cur_section[key].append(value) + + def parse(self, lineiter): + self._cur_sections = collections.OrderedDict() + super(OverrideConfigParser, self).parse(lineiter) + + # merge _cur_sections into _sections + for section, values in self._cur_sections.items(): + if section not in self._sections: + self._sections[section] = collections.OrderedDict() + for key, value in values.items(): + self._sections[section][key] = value + + def new_section(self, section): + cur_section = self._cur_sections.get(section) + if not cur_section: + cur_section = collections.OrderedDict() + self._cur_sections[section] = cur_section + self._cur_section = cur_section + return cur_section + + def write(self, fp): + def write_key_value(key, values): + for v in values: + if not v: + fp.write('{} =\n'.format(key)) + for index, value in enumerate(v): + if index == 0: + fp.write('{} = {}\n'.format(key, value)) + else: + fp.write('{} {}\n'.format(len(key)*' ', value)) + + def write_section(section): + for key, values in section.items(): + write_key_value(key, values) + + for section in self._sections: + fp.write('[{}]\n'.format(section)) + write_section(self._sections[section]) + fp.write('\n') class ActionModule(action.ActionBase): @@ -33,7 +93,7 @@ class ActionModule(action.ActionBase): template_data = f.read() result = self._templar.template(template_data) fakefile = StringIO(result) - config.readfp(fakefile) + config.parse(fakefile) fakefile.close() def run(self, tmp=None, task_vars=None): @@ -62,7 +122,7 @@ class ActionModule(action.ActionBase): temp_vars = task_vars.copy() temp_vars.update(extra_vars) - config = ConfigParser.ConfigParser() + config = OverrideConfigParser() old_vars = self._templar._available_variables self._templar.set_available_variables(temp_vars) diff --git a/tests/test_merge_config.py b/tests/test_merge_config.py new file mode 100644 index 0000000000..2300308cf1 --- /dev/null +++ b/tests/test_merge_config.py @@ -0,0 +1,106 @@ +#!/usr/bin/python + +# Copyright 2016 99cloud Inc. +# 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 +import sys + +import mock +from oslotest import base +from six import StringIO + + +PROJECT_DIR = os.path.abspath(os.path.join(os. path.dirname(__file__), '../')) +MERGE_CONFIG_FILE = os.path.join(PROJECT_DIR, + 'ansible/action_plugins/merge_configs.py') + +sys.modules['ansible.plugins'] = mock.MagicMock() + +merge_configs = imp.load_source('merge_configs', MERGE_CONFIG_FILE) + +TESTA = '''[DEFAULT] +key1 = b + c +key2 = v1 + v2 +key3 = v3 +key3 = v4 +key4 = v5 + +[b] +b_key1 = 1 +b_key2 = 1 + 2 + +[c] +c_key1 = +c_key2 = 1 2 3 + 4 5 6 + +''' + +TESTB = '''[DEFAULT] +key2 = v3 + v4 + v5 +key4 = v4 +key4 = + +[b] +b_key2 = 2 + +''' + +# TESTC is TESTA + TESTB +TESTC = '''[DEFAULT] +key1 = b + c +key2 = v3 + v4 + v5 +key3 = v3 +key3 = v4 +key4 = v4 +key4 = + +[b] +b_key1 = 1 +b_key2 = 2 + +[c] +c_key1 = +c_key2 = 1 2 3 + 4 5 6 + +''' + + +class OverrideConfigParserTest(base.BaseTestCase): + + def test_read_write(self): + for ini in [TESTA, TESTB, TESTC]: + parser = merge_configs.OverrideConfigParser() + parser.parse(StringIO(ini)) + output = StringIO() + parser.write(output) + self.assertEqual(ini, output.getvalue()) + + def test_merge(self): + parser = merge_configs.OverrideConfigParser() + parser.parse(StringIO(TESTA)) + parser.parse(StringIO(TESTB)) + output = StringIO() + parser.write(output) + self.assertEqual(TESTC, output.getvalue())