Blame boom/config.py

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
]