make iniset work with files that do not yet exist

This allows the add and set commands to work when files do not yet
exist, which becomes important for extracting things like post config
files.
This commit is contained in:
Sean Dague 2017-01-16 12:07:10 -05:00
parent 7e7ffec87e
commit e7636a7714
3 changed files with 153 additions and 11 deletions

View File

@ -16,6 +16,7 @@
# python ConfigFile parser because that ends up rewriting the entire
# file and doesn't ensure comments remain.
import os.path
import re
import shutil
import tempfile
@ -31,7 +32,10 @@ class IniFile(object):
"""Returns True if section has a key that is name"""
current_section = ""
with open(self.fname) as reader:
if not os.path.exists(self.fname):
return False
with open(self.fname, "r+") as reader:
for line in reader.readlines():
m = re.match("\[([^\[\]]+)\]", line)
if m:
@ -41,7 +45,6 @@ class IniFile(object):
return True
return False
def add(self, section, name, value):
"""add a key / value to an ini file in a section.
@ -50,26 +53,38 @@ class IniFile(object):
will be added to the end of the file.
"""
temp = tempfile.NamedTemporaryFile(mode='r')
shutil.copyfile(self.fname, temp.name)
if os.path.exists(self.fname):
shutil.copyfile(self.fname, temp.name)
else:
with open(temp.name, "w+"):
pass
found = False
with open(temp.name) as reader:
with open(self.fname, "w") as writer:
with open(self.fname, "w+") as writer:
with open(temp.name) as reader:
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))
if not found:
writer.write("[%s]\n" % section)
writer.write("%s = %s\n" % (name, value))
def _at_existing_key(self, section, name, func, match="%s\s*\="):
"""Run a function at a found key.
NOTE(sdague): if the file isn't found, we end up
exploding. This seems like the right behavior in nearly all
circumstances.
"""
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:
with open(self.fname, "w+") as writer:
for line in reader.readlines():
m = re.match("\[([^\[\]]+)\]", line)
if m:
@ -83,7 +98,6 @@ class IniFile(object):
else:
writer.write(line)
def remove(self, section, name):
"""remove a key / value from an ini file in a section."""
def _do_remove(writer, line):
@ -91,7 +105,6 @@ class IniFile(object):
self._at_existing_key(section, name, _do_remove)
def comment(self, section, name):
def _do_comment(writer, line):
writer.write("# %s" % line)
@ -112,3 +125,42 @@ class IniFile(object):
self._at_existing_key(section, name, _do_set)
else:
self.add(section, name, value)
class LocalConf(object):
"""Class for manipulating local.conf files in place."""
def __init__(self, fname):
self.fname = fname
def _conf(self, group, conf):
in_section = False
current_section = ""
with open(self.fname) as reader:
for line in reader.readlines():
if re.match(r"\[\[%s\|%s\]\]" % (
re.escape(group),
re.escape(conf)),
line):
in_section = True
continue
# any other meta section means we aren't in the
# section we want to be.
elif re.match("\[\[.*\|.*\]\]", line):
in_section = False
continue
if in_section:
m = re.match("\[([^\[\]]+)\]", line)
if m:
current_section = m.group(1)
continue
else:
m2 = re.match(r"(\w+)\s*\=\s*(.+)", line)
if m2:
yield current_section, m2.group(1), m2.group(2)
def extract(self, group, conf, target):
ini_file = IniFile(target)
for section, name, value in self._conf(group, conf):
ini_file.set(section, name, value)

View File

@ -110,3 +110,22 @@ class TestIniSet(testtools.TestCase):
with open(self._path) as f:
content = f.read()
self.assertEqual(content, RESULT4)
class TestIniCreate(testtools.TestCase):
def setUp(self):
super(TestIniCreate, self).setUp()
self._path = self.useFixture(fixtures.TempDir()).path
self._path += "/test.ini"
def test_add_items(self):
conf = dsconf.IniFile(self._path)
conf.set("default", "c", "d")
conf.set("default", "a", "b")
conf.set("second", "g", "h")
conf.set("second", "e", "f")
conf.set("new", "s", "t")
with open(self._path) as f:
content = f.read()
self.assertEqual(BASIC, content)

View File

@ -0,0 +1,71 @@
# 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 os.path
import testtools
from devstack import dsconf
BASIC = """
[[local|localrc]]
a = b
c = d
f = 1
[[post-config|$NEUTRON_CONF]]
[DEFAULT]
global_physnet_mtu=1450
[[post-config|$NOVA_CONF]]
[upgrade_levels]
compute = auto
"""
NOVA = """[upgrade_levels]
compute = auto
"""
NEUTRON = """[DEFAULT]
global_physnet_mtu = 1450
"""
class TestLcExtract(testtools.TestCase):
def setUp(self):
super(TestLcExtract, self).setUp()
self._path = self.useFixture(fixtures.TempDir()).path
self._path += "/local.conf"
with open(self._path, "w") as f:
f.write(BASIC)
def test_extract_neutron(self):
dirname = self.useFixture(fixtures.TempDir()).path
neutron = os.path.join(dirname, "neutron.conf")
nova = os.path.join(dirname, "nova.conf")
conf = dsconf.LocalConf(self._path)
conf.extract("post-config", "$NEUTRON_CONF", neutron)
conf.extract("post-config", "$NOVA_CONF", nova)
with open(neutron) as f:
content = f.read()
self.assertEqual(content, NEUTRON)
with open(nova) as f:
content = f.read()
self.assertEqual(content, NOVA)