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:
parent
7e7ffec87e
commit
e7636a7714
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
Loading…
Reference in New Issue