Ditch all use of pyanaconda's simpleconfig
lorax uses pyanaconda's SimpleConfigParser in three different places (twice with a copy that's been dumped into pylorax, once by importing it), just to do a fairly simple job: read some values out of /etc/os-release. The only value SimpleConfigParser is adding over Python's own ConfigParser here is to read a file with no section headers, and to unquote the values. The cost is either a dependency on pyanaconda, or needing to copy the whole of simpleparser plus some other utility bits from pyanaconda into lorax. This seems like a bad trade-off. This changes the approach: we copy one very simple utility function from pyanaconda (`unquote`), and do some very simple wrapping of ConfigParser to handle reading a file without any section headers, and returning unquoted values. This way we can read what we need out of os-release without needing a dep on pyanaconda or to copy lots of things from it into pylorax. Resolves: #449 Resolves: #450 Signed-off-by: Adam Williamson <awilliam@redhat.com>
This commit is contained in:
parent
fa1ba96a73
commit
75400f6a7f
@ -40,8 +40,6 @@ import pytoml as toml
|
|||||||
import shutil
|
import shutil
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
from pyanaconda.simpleconfig import SimpleConfigFile
|
|
||||||
|
|
||||||
# Use pykickstart to calculate disk image size
|
# Use pykickstart to calculate disk image size
|
||||||
from pykickstart.parser import KickstartParser
|
from pykickstart.parser import KickstartParser
|
||||||
from pykickstart.version import makeVersion
|
from pykickstart.version import makeVersion
|
||||||
@ -51,7 +49,7 @@ from pylorax.api.projects import ProjectsError
|
|||||||
from pylorax.api.recipes import read_recipe_and_id
|
from pylorax.api.recipes import read_recipe_and_id
|
||||||
from pylorax.api.timestamp import TS_CREATED, write_timestamp
|
from pylorax.api.timestamp import TS_CREATED, write_timestamp
|
||||||
from pylorax.imgutils import default_image_name
|
from pylorax.imgutils import default_image_name
|
||||||
from pylorax.sysutils import joinpaths
|
from pylorax.sysutils import joinpaths, flatconfig
|
||||||
|
|
||||||
|
|
||||||
def test_templates(dbo, share_dir):
|
def test_templates(dbo, share_dir):
|
||||||
@ -356,14 +354,13 @@ def start_build(cfg, dnflock, gitlock, branch, recipe_name, compose_type, test_m
|
|||||||
# Get the title, project, and release version from the host
|
# Get the title, project, and release version from the host
|
||||||
if not os.path.exists("/etc/os-release"):
|
if not os.path.exists("/etc/os-release"):
|
||||||
log.error("/etc/os-release is missing, cannot determine product or release version")
|
log.error("/etc/os-release is missing, cannot determine product or release version")
|
||||||
os_release = SimpleConfigFile("/etc/os-release")
|
os_release = flatconfig("/etc/os-release")
|
||||||
os_release.read()
|
|
||||||
|
|
||||||
log.debug("os_release = %s", os_release)
|
log.debug("os_release = %s", dict(os_release.items()))
|
||||||
|
|
||||||
cfg_args["title"] = os_release.get("PRETTY_NAME")
|
cfg_args["title"] = os_release.get("PRETTY_NAME", "")
|
||||||
cfg_args["project"] = os_release.get("NAME")
|
cfg_args["project"] = os_release.get("NAME", "")
|
||||||
cfg_args["releasever"] = os_release.get("VERSION_ID")
|
cfg_args["releasever"] = os_release.get("VERSION_ID", "")
|
||||||
cfg_args["volid"] = ""
|
cfg_args["volid"] = ""
|
||||||
|
|
||||||
cfg_args.update({
|
cfg_args.update({
|
||||||
|
@ -26,7 +26,7 @@ import os
|
|||||||
import shutil
|
import shutil
|
||||||
|
|
||||||
from pylorax import DEFAULT_PLATFORM_ID
|
from pylorax import DEFAULT_PLATFORM_ID
|
||||||
from pylorax.simpleconfig import SimpleConfigFile
|
from pylorax.sysutils import flatconfig
|
||||||
|
|
||||||
def get_base_object(conf):
|
def get_base_object(conf):
|
||||||
"""Get the DNF object with settings from the config file
|
"""Get the DNF object with settings from the config file
|
||||||
@ -76,9 +76,8 @@ def get_base_object(conf):
|
|||||||
log.warning("/etc/os-release is missing, cannot determine platform id, falling back to %s", DEFAULT_PLATFORM_ID)
|
log.warning("/etc/os-release is missing, cannot determine platform id, falling back to %s", DEFAULT_PLATFORM_ID)
|
||||||
platform_id = DEFAULT_PLATFORM_ID
|
platform_id = DEFAULT_PLATFORM_ID
|
||||||
else:
|
else:
|
||||||
os_release = SimpleConfigFile("/etc/os-release")
|
os_release = flatconfig("/etc/os-release")
|
||||||
os_release.read()
|
platform_id = os_release.get("PLATFORM_ID", DEFAULT_PLATFORM_ID)
|
||||||
platform_id = os_release.get("PLATFORM_ID") or DEFAULT_PLATFORM_ID
|
|
||||||
log.info("Using %s for module_platform_id", platform_id)
|
log.info("Using %s for module_platform_id", platform_id)
|
||||||
dbc.module_platform_id = platform_id
|
dbc.module_platform_id = platform_id
|
||||||
|
|
||||||
|
@ -1,203 +0,0 @@
|
|||||||
#
|
|
||||||
# simpleconifg.py - representation of a simple configuration file (sh-like)
|
|
||||||
#
|
|
||||||
# Copyright (C) 1999-2015 Red Hat, Inc.
|
|
||||||
#
|
|
||||||
# This copyrighted material is made available to anyone wishing to use,
|
|
||||||
# modify, copy, or redistribute it subject to the terms and conditions of
|
|
||||||
# the GNU General Public License v.2, or (at your option) any later version.
|
|
||||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
|
||||||
# ANY WARRANTY expressed or implied, including the implied warranties of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
|
||||||
# Public License for more details. You should have received a copy of the
|
|
||||||
# GNU General Public License along with this program; if not, write to the
|
|
||||||
# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
|
||||||
# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the
|
|
||||||
# source code or documentation are not subject to the GNU General Public
|
|
||||||
# License and may only be used or replicated with the express permission of
|
|
||||||
# Red Hat, Inc.
|
|
||||||
#
|
|
||||||
import os
|
|
||||||
import shlex
|
|
||||||
import string # pylint: disable=deprecated-module
|
|
||||||
import tempfile
|
|
||||||
from pyanaconda.core.util import upperASCII
|
|
||||||
|
|
||||||
_SAFECHARS = frozenset(string.ascii_letters + string.digits + '@%_-+=:,./')
|
|
||||||
|
|
||||||
def unquote(s):
|
|
||||||
return ' '.join(shlex.split(s))
|
|
||||||
|
|
||||||
def quote(s, always=False):
|
|
||||||
""" If always is set it returns a quoted value
|
|
||||||
"""
|
|
||||||
if not always:
|
|
||||||
for c in s:
|
|
||||||
if c not in _SAFECHARS:
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
return s
|
|
||||||
return '"' + s.replace('"', '\\"') + '"'
|
|
||||||
|
|
||||||
def find_comment(s):
|
|
||||||
""" Look for a # comment outside of a quoted string.
|
|
||||||
If there are no quotes, find the last # in the string.
|
|
||||||
|
|
||||||
:param str s: string to check for comment and quotes
|
|
||||||
:returns: index of comment or None
|
|
||||||
:rtype: int or None
|
|
||||||
|
|
||||||
Handles comments inside quotes and quotes inside quotes.
|
|
||||||
"""
|
|
||||||
q = None
|
|
||||||
for i in range(len(s)):
|
|
||||||
if not q and s[i] == '#':
|
|
||||||
return i
|
|
||||||
|
|
||||||
# Ignore quotes inside other quotes
|
|
||||||
if s[i] in "'\"":
|
|
||||||
if s[i] == q:
|
|
||||||
q = None
|
|
||||||
elif q is None:
|
|
||||||
q = s[i]
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def write_tmpfile(filename, data):
|
|
||||||
# Create a temporary in the same directory as the target file to ensure
|
|
||||||
# the new file is on the same filesystem
|
|
||||||
tmpf = tempfile.NamedTemporaryFile(mode="w", delete=False,
|
|
||||||
dir=os.path.dirname(filename) or '.',
|
|
||||||
prefix="." + os.path.basename(filename))
|
|
||||||
tmpf.write(data)
|
|
||||||
tmpf.close()
|
|
||||||
|
|
||||||
# Change the permissions (currently 0600) to match the original file
|
|
||||||
if os.path.exists(filename):
|
|
||||||
m = os.stat(filename).st_mode
|
|
||||||
else:
|
|
||||||
m = 0o0644
|
|
||||||
os.chmod(tmpf.name, m)
|
|
||||||
|
|
||||||
# Move the temporary file over the top of the original
|
|
||||||
os.rename(tmpf.name, filename)
|
|
||||||
|
|
||||||
class SimpleConfigFile(object):
|
|
||||||
""" Edit values in a configuration file without changing comments.
|
|
||||||
Supports KEY=VALUE lines and ignores everything else.
|
|
||||||
Supports adding new keys.
|
|
||||||
Supports deleting keys.
|
|
||||||
Preserves comment, blank lines and comments on KEY lines
|
|
||||||
Does not support duplicate key entries.
|
|
||||||
"""
|
|
||||||
def __init__(self, filename=None, read_unquote=True, write_quote=True,
|
|
||||||
always_quote=False):
|
|
||||||
self.filename = filename
|
|
||||||
self.read_unquote = read_unquote
|
|
||||||
self.write_quote = write_quote
|
|
||||||
self.always_quote = always_quote
|
|
||||||
self.reset()
|
|
||||||
|
|
||||||
def reset(self):
|
|
||||||
self._lines = []
|
|
||||||
self.info = {}
|
|
||||||
|
|
||||||
def read(self, filename=None):
|
|
||||||
""" passing filename will override the filename passed to init.
|
|
||||||
|
|
||||||
save the lines into self._lines and the key/value pairs into
|
|
||||||
self.info
|
|
||||||
"""
|
|
||||||
filename = filename or self.filename
|
|
||||||
with open(filename) as f:
|
|
||||||
for line in f:
|
|
||||||
self._lines.append(line)
|
|
||||||
key, value, _comment = self._parseline(line)
|
|
||||||
if key:
|
|
||||||
self.info[key] = value
|
|
||||||
|
|
||||||
def write(self, filename=None, use_tmp=True):
|
|
||||||
""" passing filename will override the filename passed to init.
|
|
||||||
"""
|
|
||||||
filename = filename or self.filename
|
|
||||||
if not filename:
|
|
||||||
return None
|
|
||||||
|
|
||||||
if use_tmp:
|
|
||||||
write_tmpfile(filename, str(self))
|
|
||||||
else:
|
|
||||||
# write directly to the file
|
|
||||||
with open(filename, "w") as fobj:
|
|
||||||
fobj.write(str(self))
|
|
||||||
|
|
||||||
def set(self, *args):
|
|
||||||
for key, value in args:
|
|
||||||
self.info[upperASCII(key)] = value
|
|
||||||
|
|
||||||
def unset(self, *keys):
|
|
||||||
for key in (upperASCII(k) for k in keys):
|
|
||||||
if key in self.info:
|
|
||||||
del self.info[key]
|
|
||||||
|
|
||||||
def get(self, key):
|
|
||||||
return self.info.get(upperASCII(key), "")
|
|
||||||
|
|
||||||
def _parseline(self, line):
|
|
||||||
""" parse a line into a key, value and comment
|
|
||||||
|
|
||||||
:param str line: Line to be parsed
|
|
||||||
:returns: Tuple of key, value, comment
|
|
||||||
:rtype: tuple
|
|
||||||
|
|
||||||
Handle comments and optionally unquote quoted strings
|
|
||||||
Returns (key, value, comment) or (None, None, comment)
|
|
||||||
key is always UPPERCASE and comment may by "" if none was found.
|
|
||||||
"""
|
|
||||||
s = line.strip()
|
|
||||||
# Look for a # outside any quotes
|
|
||||||
comment = ""
|
|
||||||
comment_index = find_comment(s)
|
|
||||||
if comment_index is not None:
|
|
||||||
comment = s[comment_index:]
|
|
||||||
s = s[:comment_index] # remove from comment to EOL
|
|
||||||
|
|
||||||
key, eq, val = s.partition('=')
|
|
||||||
key = key.strip()
|
|
||||||
val = val.strip()
|
|
||||||
if self.read_unquote:
|
|
||||||
val = unquote(val)
|
|
||||||
if key != '' and eq == '=':
|
|
||||||
return (upperASCII(key), val, comment)
|
|
||||||
else:
|
|
||||||
return (None, None, comment)
|
|
||||||
|
|
||||||
def _kvpair(self, key, comment=""):
|
|
||||||
value = self.info[key]
|
|
||||||
if self.write_quote or self.always_quote:
|
|
||||||
value = quote(value, self.always_quote)
|
|
||||||
if comment:
|
|
||||||
comment = " " + comment
|
|
||||||
return key + '=' + value + comment + "\n"
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
""" Return the file that was read, replacing existing keys with new values
|
|
||||||
removing keys that have been deleted and adding new keys.
|
|
||||||
"""
|
|
||||||
oldkeys = []
|
|
||||||
s = ""
|
|
||||||
for line in self._lines:
|
|
||||||
key, _value, comment = self._parseline(line)
|
|
||||||
if key is None:
|
|
||||||
s += line
|
|
||||||
else:
|
|
||||||
if key not in self.info:
|
|
||||||
continue
|
|
||||||
oldkeys.append(key)
|
|
||||||
s += self._kvpair(key, comment)
|
|
||||||
|
|
||||||
# Add new keys
|
|
||||||
for key in self.info:
|
|
||||||
if key not in oldkeys:
|
|
||||||
s += self._kvpair(key)
|
|
||||||
|
|
||||||
return s
|
|
@ -30,6 +30,8 @@ import pwd
|
|||||||
import grp
|
import grp
|
||||||
import glob
|
import glob
|
||||||
import shutil
|
import shutil
|
||||||
|
import shlex
|
||||||
|
from configparser import ConfigParser
|
||||||
|
|
||||||
from pylorax.executils import runcmd
|
from pylorax.executils import runcmd
|
||||||
|
|
||||||
@ -106,3 +108,24 @@ def remove(target):
|
|||||||
|
|
||||||
def linktree(src, dst):
|
def linktree(src, dst):
|
||||||
runcmd(["/bin/cp", "-alx", src, dst])
|
runcmd(["/bin/cp", "-alx", src, dst])
|
||||||
|
|
||||||
|
def unquote(s):
|
||||||
|
return ' '.join(shlex.split(s))
|
||||||
|
|
||||||
|
class UnquotingConfigParser(ConfigParser):
|
||||||
|
"""A ConfigParser, only with unquoting of the values."""
|
||||||
|
def get(self, *args, **kwargs):
|
||||||
|
ret = super().get(*args, **kwargs)
|
||||||
|
if ret:
|
||||||
|
ret = unquote(ret)
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def flatconfig(filename):
|
||||||
|
"""Use UnquotingConfigParser to read a flat config file (without
|
||||||
|
section headers) by adding a section header.
|
||||||
|
"""
|
||||||
|
with open (filename, 'r') as conffh:
|
||||||
|
conftext = "[main]\n" + conffh.read()
|
||||||
|
config = UnquotingConfigParser()
|
||||||
|
config.read_string(conftext)
|
||||||
|
return config['main']
|
||||||
|
@ -35,7 +35,7 @@ import librepo
|
|||||||
import pylorax
|
import pylorax
|
||||||
from pylorax import DRACUT_DEFAULT, DEFAULT_PLATFORM_ID
|
from pylorax import DRACUT_DEFAULT, DEFAULT_PLATFORM_ID
|
||||||
from pylorax.cmdline import lorax_parser
|
from pylorax.cmdline import lorax_parser
|
||||||
from pylorax.simpleconfig import SimpleConfigFile
|
from pylorax.sysutils import flatconfig
|
||||||
import selinux
|
import selinux
|
||||||
|
|
||||||
def setup_logging(opts):
|
def setup_logging(opts):
|
||||||
@ -225,9 +225,8 @@ def get_dnf_base_object(installroot, sources, mirrorlists=None, repos=None,
|
|||||||
log.warning("/etc/os-release is missing, cannot determine platform id, falling back to %s", DEFAULT_PLATFORM_ID)
|
log.warning("/etc/os-release is missing, cannot determine platform id, falling back to %s", DEFAULT_PLATFORM_ID)
|
||||||
platform_id = DEFAULT_PLATFORM_ID
|
platform_id = DEFAULT_PLATFORM_ID
|
||||||
else:
|
else:
|
||||||
os_release = SimpleConfigFile("/etc/os-release")
|
os_release = flatconfig("/etc/os-release")
|
||||||
os_release.read()
|
platform_id = os_release.get("PLATFORM_ID", DEFAULT_PLATFORM_ID)
|
||||||
platform_id = os_release.get("PLATFORM_ID") or DEFAULT_PLATFORM_ID
|
|
||||||
log.info("Using %s for module_platform_id", platform_id)
|
log.info("Using %s for module_platform_id", platform_id)
|
||||||
conf.module_platform_id = platform_id
|
conf.module_platform_id = platform_id
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user