|
Packit |
5acf3f |
# Copyright (C) 2017 Red Hat, Inc., Bryn M. Reeves <bmr@redhat.com>
|
|
Packit |
5acf3f |
#
|
|
Packit |
5acf3f |
# config.py - Boom persistent configuration
|
|
Packit |
5acf3f |
#
|
|
Packit |
5acf3f |
# This file is part of the boom project.
|
|
Packit |
5acf3f |
#
|
|
Packit |
5acf3f |
# This copyrighted material is made available to anyone wishing to use,
|
|
Packit |
5acf3f |
# modify, copy, or redistribute it subject to the terms and conditions
|
|
Packit |
5acf3f |
# of the GNU General Public License v.2.
|
|
Packit |
5acf3f |
#
|
|
Packit |
5acf3f |
# You should have received a copy of the GNU Lesser General Public License
|
|
Packit |
5acf3f |
# along with this program; if not, write to the Free Software Foundation,
|
|
Packit |
5acf3f |
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
Packit |
5acf3f |
"""The ``boom.config`` module defines classes, constants and functions
|
|
Packit |
5acf3f |
for reading and writing persistent (on-disk) configuration for the boom
|
|
Packit |
5acf3f |
library and tools.
|
|
Packit |
5acf3f |
|
|
Packit |
5acf3f |
Users of the module can load and write configuration data, and obtain
|
|
Packit |
5acf3f |
the values of configuration keys defined in the boom configuration file.
|
|
Packit |
5acf3f |
"""
|
|
Packit |
5acf3f |
from __future__ import print_function
|
|
Packit |
5acf3f |
|
|
Packit |
5acf3f |
from boom import *
|
|
Packit |
5acf3f |
|
|
Packit |
5acf3f |
from os.path import dirname
|
|
Packit |
5acf3f |
|
|
Packit |
5acf3f |
from os import fdopen, rename, chmod, fdatasync
|
|
Packit |
5acf3f |
from tempfile import mkstemp
|
|
Packit |
5acf3f |
import logging
|
|
Packit |
5acf3f |
|
|
Packit |
5acf3f |
try:
|
|
Packit |
5acf3f |
# Python2
|
|
Packit |
5acf3f |
from ConfigParser import SafeConfigParser as ConfigParser, ParsingError
|
|
Packit |
5acf3f |
except ModuleNotFoundError:
|
|
Packit |
5acf3f |
# Python3
|
|
Packit |
5acf3f |
from configparser import ConfigParser, ParsingError
|
|
Packit |
5acf3f |
|
|
Packit |
5acf3f |
|
|
Packit |
5acf3f |
class BoomConfigError(BoomError):
|
|
Packit |
5acf3f |
"""Base class for boom configuration errors.
|
|
Packit |
5acf3f |
"""
|
|
Packit |
5acf3f |
pass
|
|
Packit |
5acf3f |
|
|
Packit |
5acf3f |
|
|
Packit |
5acf3f |
# Module logging configuration
|
|
Packit |
5acf3f |
_log = logging.getLogger(__name__)
|
|
Packit |
5acf3f |
|
|
Packit |
5acf3f |
_log_debug = _log.debug
|
|
Packit |
5acf3f |
_log_info = _log.info
|
|
Packit |
5acf3f |
_log_warn = _log.warning
|
|
Packit |
5acf3f |
_log_error = _log.error
|
|
Packit |
5acf3f |
|
|
Packit |
5acf3f |
#
|
|
Packit |
5acf3f |
# Constants for configuration sections and options: to add a new option,
|
|
Packit |
5acf3f |
# create a new _CFG_* constant giving the name of the option and add a
|
|
Packit |
5acf3f |
# hook to load_boom_config() to set the value when read.
|
|
Packit |
5acf3f |
#
|
|
Packit |
5acf3f |
# To add a new section add the section name constant as _CFG_SECT_* and
|
|
Packit |
5acf3f |
# add a new branch to load_boom_config() to test for the presence of
|
|
Packit |
5acf3f |
# the section and handle the options found within.
|
|
Packit |
5acf3f |
#
|
|
Packit |
5acf3f |
_CFG_SECT_GLOBAL = "global"
|
|
Packit |
5acf3f |
_CFG_SECT_LEGACY = "legacy"
|
|
Packit |
5acf3f |
_CFG_BOOT_ROOT = "boot_root"
|
|
Packit |
5acf3f |
_CFG_BOOM_ROOT = "boom_root"
|
|
Packit |
5acf3f |
_CFG_LEGACY_ENABLE = "enable"
|
|
Packit |
5acf3f |
_CFG_LEGACY_FMT = "format"
|
|
Packit |
5acf3f |
_CFG_LEGACY_SYNC = "sync"
|
|
Packit |
5acf3f |
|
|
Packit |
5acf3f |
|
|
Packit |
5acf3f |
def _read_boom_config(path=None):
|
|
Packit |
5acf3f |
"""Read boom persistent configuration values from the defined path
|
|
Packit |
5acf3f |
and return them as a ``BoomConfig`` object.
|
|
Packit |
5acf3f |
|
|
Packit |
5acf3f |
:param path: the configuration file to read, or None to read the
|
|
Packit |
5acf3f |
currently configured config file path.
|
|
Packit |
5acf3f |
|
|
Packit |
5acf3f |
:rtype: BoomConfig
|
|
Packit |
5acf3f |
"""
|
|
Packit |
5acf3f |
path = path or get_boom_config_path()
|
|
Packit |
5acf3f |
_log_debug("reading boom configuration from '%s'" % path)
|
|
Packit |
5acf3f |
cfg = ConfigParser()
|
|
Packit |
5acf3f |
try:
|
|
Packit |
5acf3f |
cfg.read(path)
|
|
Packit |
5acf3f |
except ParsingError as e:
|
|
Packit |
5acf3f |
_log_error("Failed to parse configuration file '%s': %s" %
|
|
Packit |
5acf3f |
(path, e))
|
|
Packit |
5acf3f |
|
|
Packit |
5acf3f |
bc = BoomConfig()
|
|
Packit |
5acf3f |
|
|
Packit |
5acf3f |
trues = ['True', 'true', 'Yes', 'yes']
|
|
Packit |
5acf3f |
|
|
Packit |
5acf3f |
if not cfg.has_section(_CFG_SECT_GLOBAL):
|
|
Packit |
5acf3f |
raise ValueError("Missing 'global' section in %s" % path)
|
|
Packit |
5acf3f |
|
|
Packit |
5acf3f |
if cfg.has_section(_CFG_SECT_GLOBAL):
|
|
Packit |
5acf3f |
if cfg.has_option(_CFG_SECT_GLOBAL, _CFG_BOOT_ROOT):
|
|
Packit |
5acf3f |
_log_debug("Found global.boot_path")
|
|
Packit |
5acf3f |
bc.boot_path = cfg.get(_CFG_SECT_GLOBAL, _CFG_BOOT_ROOT)
|
|
Packit |
5acf3f |
if cfg.has_option(_CFG_SECT_GLOBAL, _CFG_BOOM_ROOT):
|
|
Packit |
5acf3f |
_log_debug("Found global.boom_path")
|
|
Packit |
5acf3f |
bc.boom_path = cfg.get(_CFG_SECT_GLOBAL, _CFG_BOOM_ROOT)
|
|
Packit |
5acf3f |
|
|
Packit |
5acf3f |
if cfg.has_section(_CFG_SECT_LEGACY):
|
|
Packit |
5acf3f |
if cfg.has_option(_CFG_SECT_LEGACY, _CFG_LEGACY_ENABLE):
|
|
Packit |
5acf3f |
_log_debug("Found legacy.enable")
|
|
Packit |
5acf3f |
enable = cfg.get(_CFG_SECT_LEGACY, _CFG_LEGACY_ENABLE)
|
|
Packit |
5acf3f |
bc.legacy_enable = any([t for t in trues if t in enable])
|
|
Packit |
5acf3f |
|
|
Packit |
5acf3f |
if cfg.has_option(_CFG_SECT_LEGACY, _CFG_LEGACY_FMT):
|
|
Packit |
5acf3f |
bc.legacy_format = cfg.get(_CFG_SECT_LEGACY,
|
|
Packit |
5acf3f |
_CFG_LEGACY_FMT)
|
|
Packit |
5acf3f |
|
|
Packit |
5acf3f |
if cfg.has_option(_CFG_SECT_LEGACY, _CFG_LEGACY_SYNC):
|
|
Packit |
5acf3f |
_log_debug("Found legacy.sync")
|
|
Packit |
5acf3f |
sync = cfg.get(_CFG_SECT_LEGACY, _CFG_LEGACY_SYNC)
|
|
Packit |
5acf3f |
bc.legacy_sync = any([t for t in trues if t in sync])
|
|
Packit |
5acf3f |
|
|
Packit |
5acf3f |
_log_debug("read configuration: %s" % repr(bc))
|
|
Packit |
5acf3f |
bc._cfg = cfg
|
|
Packit |
5acf3f |
return bc
|
|
Packit |
5acf3f |
|
|
Packit |
5acf3f |
|
|
Packit |
5acf3f |
def load_boom_config(path=None):
|
|
Packit |
5acf3f |
"""Load boom persistent configuration values from the defined path
|
|
Packit |
5acf3f |
and make the them the active configuration.
|
|
Packit |
5acf3f |
|
|
Packit |
5acf3f |
:param path: the configuration file to read, or None to read the
|
|
Packit |
5acf3f |
currently configured config file path
|
|
Packit |
5acf3f |
|
|
Packit |
5acf3f |
:rtype: None
|
|
Packit |
5acf3f |
"""
|
|
Packit |
5acf3f |
bc = _read_boom_config(path=path)
|
|
Packit |
5acf3f |
set_boom_config(bc)
|
|
Packit |
5acf3f |
|
|
Packit |
5acf3f |
|
|
Packit |
5acf3f |
def _sync_config(bc, cfg):
|
|
Packit |
5acf3f |
"""Sync the configuration values of ``BoomConfig`` object ``bc`` to
|
|
Packit |
5acf3f |
the ``ConfigParser`` ``cfg``.
|
|
Packit |
5acf3f |
"""
|
|
Packit |
5acf3f |
def yes_no(value):
|
|
Packit |
5acf3f |
if value:
|
|
Packit |
5acf3f |
return "yes"
|
|
Packit |
5acf3f |
return "no"
|
|
Packit |
5acf3f |
|
|
Packit |
5acf3f |
def attr_has_value(obj, attr):
|
|
Packit |
5acf3f |
return hasattr(obj, attr) and getattr(obj, attr) is not None
|
|
Packit |
5acf3f |
|
|
Packit |
5acf3f |
if attr_has_value(bc, "boot_path"):
|
|
Packit |
5acf3f |
cfg.set(_CFG_SECT_GLOBAL, _CFG_BOOT_ROOT, bc.boot_path)
|
|
Packit |
5acf3f |
if attr_has_value(bc, "boom_path"):
|
|
Packit |
5acf3f |
cfg.set(_CFG_SECT_GLOBAL, _CFG_BOOM_ROOT, bc.boom_path)
|
|
Packit |
5acf3f |
if attr_has_value(bc, "legacy_enable"):
|
|
Packit |
5acf3f |
cfg.set(_CFG_SECT_LEGACY, _CFG_LEGACY_ENABLE, yes_no(bc.legacy_enable))
|
|
Packit |
5acf3f |
if attr_has_value(bc, "legacy_format"):
|
|
Packit |
5acf3f |
cfg.set(_CFG_SECT_LEGACY, _CFG_LEGACY_FMT, bc.legacy_format)
|
|
Packit |
5acf3f |
if attr_has_value(bc, "legacy_sync"):
|
|
Packit |
5acf3f |
cfg.set(_CFG_SECT_LEGACY, _CFG_LEGACY_SYNC, yes_no(bc.legacy_sync))
|
|
Packit |
5acf3f |
|
|
Packit |
5acf3f |
|
|
Packit |
5acf3f |
def __make_config(bc):
|
|
Packit |
5acf3f |
"""Create a new ``ConfigParser`` corresponding to the ``BoomConfig``
|
|
Packit |
5acf3f |
object ``bc`` and return the result.
|
|
Packit |
5acf3f |
"""
|
|
Packit |
5acf3f |
cfg = ConfigParser()
|
|
Packit |
5acf3f |
cfg.add_section("global")
|
|
Packit |
5acf3f |
cfg.add_section("legacy")
|
|
Packit |
5acf3f |
_sync_config(bc, cfg)
|
|
Packit |
5acf3f |
return bc
|
|
Packit |
5acf3f |
|
|
Packit |
5acf3f |
|
|
Packit |
5acf3f |
def write_boom_config(config=None, path=None):
|
|
Packit |
5acf3f |
"""Write boom configuration to disk.
|
|
Packit |
5acf3f |
|
|
Packit |
5acf3f |
:param config: the configuration values to write, or None to
|
|
Packit |
5acf3f |
write the current configuration
|
|
Packit |
5acf3f |
:param path: the configuration file to read, or None to read the
|
|
Packit |
5acf3f |
currently configured config file path
|
|
Packit |
5acf3f |
|
|
Packit |
5acf3f |
:rtype: None
|
|
Packit |
5acf3f |
"""
|
|
Packit |
5acf3f |
path = path or get_boom_config_path()
|
|
Packit |
5acf3f |
cfg_dir = dirname(path)
|
|
Packit |
5acf3f |
(tmp_fd, tmp_path) = mkstemp(prefix="boom", dir=cfg_dir)
|
|
Packit |
5acf3f |
|
|
Packit |
5acf3f |
config = config or get_boom_config()
|
|
Packit |
5acf3f |
|
|
Packit |
5acf3f |
if not config._cfg:
|
|
Packit |
5acf3f |
config._cfg = __make_config(config)
|
|
Packit |
5acf3f |
else:
|
|
Packit |
5acf3f |
_sync_config(config, config._cfg)
|
|
Packit |
5acf3f |
|
|
Packit |
5acf3f |
with fdopen(tmp_fd, "w") as f_tmp:
|
|
Packit |
5acf3f |
config._cfg.write(f_tmp)
|
|
Packit |
5acf3f |
fdatasync(tmp_fd)
|
|
Packit |
5acf3f |
|
|
Packit |
5acf3f |
try:
|
|
Packit |
5acf3f |
rename(tmp_path, path)
|
|
Packit |
5acf3f |
chmod(path, BOOT_CONFIG_MODE)
|
|
Packit |
5acf3f |
except Exception as e:
|
|
Packit |
5acf3f |
_log_error("Error writing configuration file %s: %s" %
|
|
Packit |
5acf3f |
(path, e))
|
|
Packit |
5acf3f |
try:
|
|
Packit |
5acf3f |
unlink(tmp_path)
|
|
Packit |
5acf3f |
except Exception:
|
|
Packit |
5acf3f |
pass
|
|
Packit |
5acf3f |
raise e
|
|
Packit |
5acf3f |
|
|
Packit |
5acf3f |
|
|
Packit |
5acf3f |
__all__ = [
|
|
Packit |
5acf3f |
'BoomConfigError',
|
|
Packit |
5acf3f |
|
|
Packit |
5acf3f |
# Configuration file handling
|
|
Packit |
5acf3f |
'load_boom_config',
|
|
Packit |
5acf3f |
'write_boom_config'
|
|
Packit |
5acf3f |
]
|