From 0c14c589133db28f041c641eea08bd2f25fb610b Mon Sep 17 00:00:00 2001 From: Sean Dague Date: Mon, 16 Jan 2017 09:37:39 -0500 Subject: [PATCH] add inifile add/remove code --- devstack/dsconf.py | 70 ++++++++++++++++++++++ devstack/tests/test_ini_add.py | 90 ++++++++++++++++++++++++++++ devstack/tests/test_ini_remove.py | 98 +++++++++++++++++++++++++++++++ test-requirements.txt | 2 +- 4 files changed, 259 insertions(+), 1 deletion(-) create mode 100644 devstack/dsconf.py create mode 100644 devstack/tests/test_ini_add.py create mode 100644 devstack/tests/test_ini_remove.py diff --git a/devstack/dsconf.py b/devstack/dsconf.py new file mode 100644 index 0000000..b31e221 --- /dev/null +++ b/devstack/dsconf.py @@ -0,0 +1,70 @@ +# Copyright 2017 IBM +# +# 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. + +# Implementation of ini add / remove for devstack. We don't use the +# python ConfigFile parser because that ends up rewriting the entire +# file and doesn't ensure comments remain. + +import re +import shutil +import tempfile + + +class IniFile(object): + """Class for manipulating ini files in place.""" + + def __init__(self, fname): + self.fname = fname + + def add(self, section, name, value): + """add a key / value to an ini file in a section. + + The new key value will be added at the beginning of the + section, if no section is found a new section and key value + will be added to the end of the file. + """ + temp = tempfile.NamedTemporaryFile(mode='r') + shutil.copyfile(self.fname, temp.name) + found = False + with open(temp.name) as reader: + with open(self.fname, "w") as writer: + for line in reader.readlines(): + writer.write(line) + m = re.match("\[([^\[\]]+)\]", line) + if m and m.group(1) == section: + found = True + writer.write("%s = %s\n" % (name, value)) + if not found: + writer.write("[%s]\n" % section) + writer.write("%s = %s\n" % (name, value)) + + def remove(self, section, name): + """remove a key / value from an ini file in a section.""" + + temp = tempfile.NamedTemporaryFile(mode='r') + shutil.copyfile(self.fname, temp.name) + current_section = "" + with open(temp.name) as reader: + with open(self.fname, "w") as writer: + for line in reader.readlines(): + m = re.match("\[([^\[\]]+)\]", line) + if m: + current_section = m.group(1) + if current_section == section: + if re.match("%s\s*\=" % name, line): + continue + else: + writer.write(line) + else: + writer.write(line) diff --git a/devstack/tests/test_ini_add.py b/devstack/tests/test_ini_add.py new file mode 100644 index 0000000..2654e3e --- /dev/null +++ b/devstack/tests/test_ini_add.py @@ -0,0 +1,90 @@ +# Copyright 2017 IBM +# +# 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. + +# Implementation of ini add / remove for devstack. We don't use the +# python ConfigFile parser because that ends up rewriting the entire +# file and doesn't ensure comments remain. + +import fixtures +import testtools + +from devstack import dsconf + + +BASIC = """[default] +a = b +c = d +[second] +e = f +g = h +""" + +RESULT1 = """[default] +s = t +a = b +c = d +[second] +e = f +g = h +""" + +RESULT2 = """[default] +a = b +c = d +[second] +s = t +e = f +g = h +""" + +RESULT3 = """[default] +a = b +c = d +[second] +e = f +g = h +[new] +s = t +""" + + +class TestIniAdd(testtools.TestCase): + + def setUp(self): + super(TestIniAdd, self).setUp() + self._path = self.useFixture(fixtures.TempDir()).path + self._path += "/test.ini" + with open(self._path, "w") as f: + f.write(BASIC) + + def test_add_ini_default(self): + conf = dsconf.IniFile(self._path) + conf.add("default", "s", "t") + with open(self._path) as f: + content = f.read() + self.assertEqual(content, RESULT1) + + def test_add_ini_second(self): + conf = dsconf.IniFile(self._path) + conf.add("second", "s", "t") + with open(self._path) as f: + content = f.read() + self.assertEqual(content, RESULT2) + + def test_add_ini_new(self): + conf = dsconf.IniFile(self._path) + conf.add("new", "s", "t") + with open(self._path) as f: + content = f.read() + self.assertEqual(content, RESULT3) diff --git a/devstack/tests/test_ini_remove.py b/devstack/tests/test_ini_remove.py new file mode 100644 index 0000000..9472270 --- /dev/null +++ b/devstack/tests/test_ini_remove.py @@ -0,0 +1,98 @@ +# Copyright 2017 IBM +# +# 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. + +# Implementation of ini add / remove for devstack. We don't use the +# python ConfigFile parser because that ends up rewriting the entire +# file and doesn't ensure comments remain. + +import fixtures +import testtools + +from devstack import dsconf + + +BASIC = """[default] +a = b +c = d +[second] +e = f +g = h +[new] +s = t +""" + +RESULT1 = """[default] +c = d +[second] +e = f +g = h +[new] +s = t +""" + +RESULT2 = """[default] +a = b +c = d +[second] +e = f +[new] +s = t +""" + +RESULT3 = """[default] +a = b +c = d +[second] +e = f +g = h +[new] +""" + + +class TestIniRemove(testtools.TestCase): + + def setUp(self): + super(TestIniRemove, self).setUp() + self._path = self.useFixture(fixtures.TempDir()).path + self._path += "/test.ini" + with open(self._path, "w") as f: + f.write(BASIC) + + def test_remove_ini_default(self): + conf = dsconf.IniFile(self._path) + conf.remove("default", "a") + with open(self._path) as f: + content = f.read() + self.assertEqual(content, RESULT1) + + def test_remove_ini_second(self): + conf = dsconf.IniFile(self._path) + conf.remove("second", "g") + with open(self._path) as f: + content = f.read() + self.assertEqual(content, RESULT2) + + def test_remove_ini_new(self): + conf = dsconf.IniFile(self._path) + conf.remove("new", "s") + with open(self._path) as f: + content = f.read() + self.assertEqual(content, RESULT3) + + def test_remove_ini_none(self): + conf = dsconf.IniFile(self._path) + conf.remove("default", "s") + with open(self._path) as f: + content = f.read() + self.assertEqual(content, BASIC) diff --git a/test-requirements.txt b/test-requirements.txt index 4a7d204..794a973 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -7,10 +7,10 @@ hacking>=0.12.0,<0.13 # Apache-2.0 coverage>=4.0 # Apache-2.0 python-subunit>=0.0.18 # Apache-2.0/BSD sphinx>=1.2.1,!=1.3b1,<1.4 # BSD +fixtures oslosphinx>=4.7.0 # Apache-2.0 oslotest>=1.10.0 # Apache-2.0 testrepository>=0.0.18 # Apache-2.0/BSD -testscenarios>=0.4 # Apache-2.0/BSD testtools>=1.4.0 # MIT # releasenotes