Previously the logic around hitting the end of the file without having found the insertion point didn't account for the fact that you might be in roughly the right area, and would not need to duplicate the meta section or section headers. This takes that into account during the else phase. It will help with the neutron functional jobs that merge a lot of snippets together. Change-Id: Ifaa1176e9fdfbc4fdb43192ed2f85e7306823848
332 lines
12 KiB
Python
332 lines
12 KiB
Python
# 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 os.path
|
|
import re
|
|
import shutil
|
|
import tempfile
|
|
|
|
|
|
class IniFile(object):
|
|
"""Class for manipulating ini files in place."""
|
|
|
|
def __init__(self, fname):
|
|
self.fname = fname
|
|
|
|
def has(self, section, name):
|
|
"""Returns True if section has a key that is name"""
|
|
|
|
current_section = ""
|
|
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:
|
|
current_section = m.group(1)
|
|
if current_section == section:
|
|
if re.match("%s\s*\=" % name, line):
|
|
return True
|
|
return False
|
|
|
|
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')
|
|
if os.path.exists(self.fname):
|
|
shutil.copyfile(self.fname, temp.name)
|
|
else:
|
|
with open(temp.name, "w+"):
|
|
pass
|
|
|
|
found = False
|
|
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))
|
|
|
|
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:
|
|
for line in reader.readlines():
|
|
m = re.match("\[([^\[\]]+)\]", line)
|
|
if m:
|
|
current_section = m.group(1)
|
|
if current_section == section:
|
|
if re.match(match % name, line):
|
|
# run function with writer and found line
|
|
func(writer, line)
|
|
else:
|
|
writer.write(line)
|
|
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):
|
|
pass
|
|
|
|
self._at_existing_key(section, name, _do_remove)
|
|
|
|
def comment(self, section, name):
|
|
def _do_comment(writer, line):
|
|
writer.write("# %s" % line)
|
|
|
|
self._at_existing_key(section, name, _do_comment)
|
|
|
|
def uncomment(self, section, name):
|
|
def _do_uncomment(writer, line):
|
|
writer.write(re.sub("^#\s*", "", line))
|
|
|
|
self._at_existing_key(section, name, _do_uncomment,
|
|
match="#\s*%s\s*\=")
|
|
|
|
def set(self, section, name, value):
|
|
def _do_set(writer, line):
|
|
writer.write("%s = %s\n" % (name, value))
|
|
if self.has(section, name):
|
|
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):
|
|
current_section = ""
|
|
for line in self._section(group, conf):
|
|
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 groups(self):
|
|
"""Return a list of all groups in the local.conf"""
|
|
groups = []
|
|
with open(self.fname) as reader:
|
|
for line in reader.readlines():
|
|
m = re.match(r"\[\[([^\[\]]+)\|([^\[\]]+)\]\]", line)
|
|
if m:
|
|
group = (m.group(1), m.group(2))
|
|
groups.append(group)
|
|
return groups
|
|
|
|
def _section(self, group, conf):
|
|
"""Yield all the lines out of a meta section."""
|
|
in_section = False
|
|
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:
|
|
yield line
|
|
|
|
def _has_local_section(self):
|
|
for group in self.groups():
|
|
if group == ("local", "localrc"):
|
|
return True
|
|
return False
|
|
|
|
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)
|
|
|
|
def extract_localrc(self, target):
|
|
with open(target, "a+") as f:
|
|
for line in self._section("local", "localrc"):
|
|
f.write(line)
|
|
|
|
def _at_insert_point_local(self, name, func):
|
|
"""Run function when we are at the right insertion point in file.
|
|
|
|
This lets us process an arbitrary file and insert content at
|
|
the correct point. It has a few different state flags that we
|
|
are looking for.
|
|
|
|
Does this file have a local section at all? If not, we need to
|
|
write one early in the file (this means we work with an empty
|
|
file, as well as a file that has only post-config sections.
|
|
|
|
Are we currently in a local section, if so, we need to write
|
|
out content to the end, because items added to local always
|
|
have to be added at the end.
|
|
|
|
Did we write out the work that we expected? If so, just blast
|
|
all lines to the end of the file.
|
|
|
|
"""
|
|
temp = tempfile.NamedTemporaryFile(mode='r')
|
|
shutil.copyfile(self.fname, temp.name)
|
|
in_local = False
|
|
has_local = self._has_local_section()
|
|
done = False
|
|
with open(self.fname, "w+") as writer:
|
|
with open(temp.name) as reader:
|
|
for line in reader.readlines():
|
|
if done:
|
|
writer.write(line)
|
|
continue
|
|
|
|
if re.match(re.escape("[[local|localrc]]"), line):
|
|
in_local = True
|
|
elif in_local and re.match(re.escape("[["), line):
|
|
func(writer, None)
|
|
done = True
|
|
in_local = False
|
|
elif not has_local and re.match(re.escape("[["), line):
|
|
writer.write("[[local|localrc]]\n")
|
|
func(writer, None)
|
|
done = True
|
|
in_local = False
|
|
has_local = True
|
|
|
|
# otherwise, just write what we found
|
|
writer.write(line)
|
|
if not done:
|
|
func(writer, None)
|
|
|
|
def set_local(self, line):
|
|
if not os.path.exists(self.fname):
|
|
with open(self.fname, "w+") as writer:
|
|
writer.write("[[local|localrc]]\n")
|
|
writer.write("%s\n" % line.rstrip())
|
|
return
|
|
|
|
def _do_set(writer, no_line):
|
|
writer.write("%s\n" % line.rstrip())
|
|
self._at_insert_point_local(line, _do_set)
|
|
|
|
def _at_insert_point(self, group, conf, section, name, func):
|
|
temp = tempfile.NamedTemporaryFile(mode='r')
|
|
shutil.copyfile(self.fname, temp.name)
|
|
in_meta = False
|
|
in_section = False
|
|
done = False
|
|
with open(self.fname, "w+") as writer:
|
|
with open(temp.name) as reader:
|
|
for line in reader.readlines():
|
|
if done:
|
|
writer.write(line)
|
|
continue
|
|
|
|
if re.match(re.escape("[[%s|%s]]" % (group, conf)), line):
|
|
in_meta = True
|
|
writer.write(line)
|
|
elif re.match("\[\[.*\|.*\]\]", line):
|
|
# if we're not done yet, we
|
|
if in_meta:
|
|
if not in_section:
|
|
# if we've not found the section yet,
|
|
# write out section as well.
|
|
writer.write("[%s]\n" % section)
|
|
func(writer, None)
|
|
done = True
|
|
writer.write(line)
|
|
in_meta = False
|
|
in_section = False
|
|
elif re.match(re.escape("[%s]" % section), line):
|
|
# we found a relevant section
|
|
writer.write(line)
|
|
in_section = True
|
|
elif re.match("\[[^\[\]]+\]", line):
|
|
if in_meta and in_section:
|
|
# We've ended our section, in our meta,
|
|
# never found the key. Time to add it.
|
|
func(writer, None)
|
|
done = True
|
|
in_section = False
|
|
writer.write(line)
|
|
elif (in_meta and in_section and
|
|
re.match("\s*%s\s*\=" % re.escape(name), line)):
|
|
# we found our match point
|
|
func(writer, line)
|
|
done = True
|
|
else:
|
|
# write out whatever we find
|
|
writer.write(line)
|
|
if not done:
|
|
if not in_meta:
|
|
writer.write("[[%s|%s]]\n" % (group, conf))
|
|
in_section = False
|
|
if not in_section:
|
|
writer.write("[%s]\n" % (section))
|
|
func(writer, None)
|
|
|
|
def set(self, group, conf, section, name, value):
|
|
if not os.path.exists(self.fname):
|
|
with open(self.fname, "w+") as writer:
|
|
writer.write("[[%s|%s]]\n" % (group, conf))
|
|
writer.write("[%s]\n" % section)
|
|
writer.write("%s = %s\n" % (name, value))
|
|
return
|
|
|
|
def _do_set(writer, line):
|
|
writer.write("%s = %s\n" % (name, value))
|
|
self._at_insert_point(group, conf, section, name, _do_set)
|
|
|
|
def merge_lc(self, lcfile):
|
|
lc = LocalConf(lcfile)
|
|
groups = lc.groups()
|
|
for group, conf in groups:
|
|
if group == "local":
|
|
for line in lc._section(group, conf):
|
|
self.set_local(line)
|
|
else:
|
|
for section, name, value in lc._conf(group, conf):
|
|
self.set(group, conf, section, name, value)
|