관리-도구
편집 파일: config.py
# coding=utf-8 # Copyright © Cloud Linux GmbH & Cloud Linux Software, Inc 2010-2019 All Rights Reserved # # Licensed under CLOUD LINUX LICENSE AGREEMENT # http://cloudlinux.com/docs/LICENSE.TXT from __future__ import absolute_import from __future__ import print_function from __future__ import division import json import os from abc import ABCMeta, abstractmethod, abstractproperty from future.utils import iteritems from secureio import write_file_via_tempfile from clselect import utils from .pkgmanager import BasePkgManager # NOQA from . import BaseSelectorError, ENABLED_STATUS, DISABLED_STATUS from future.utils import with_metaclass class BaseSelectorConfig(with_metaclass(ABCMeta, object)): """ Base class that responsible for all interaction with CL selector config files """ def __init__(self, pkg): self.Cfg = self._get_config_object() self.pkg = pkg # type: BasePkgManager self.reload() @abstractproperty def _config_file(self): """Should return path to the config file""" raise NotImplementedError() @abstractmethod def _create_config_dirs(self): """Should create all needed directories for configs""" raise NotImplementedError() @staticmethod def _get_config_object(): """Override this method to change config parameters""" # Useful for IDE-level auto-completion and type checking class Cfg: # Defaults. None values means that it's not specified in config yet # and effective values depends on some logic in class properties default_version = None selector_enabled = None disabled_versions = None return Cfg @property def is_config_exists(self): """Check whether config file exists and is a regular file""" return os.path.isfile(self._config_file) def _dump(self): """ Returns underlying config as a plain dict. It will contain only explicitly configured options (e.g. no elements with None values) """ tmp = {} for k, v in iteritems(self.Cfg.__dict__): if not k.startswith('__') and v is not None: tmp[k] = v return tmp def _reset_cfg(self): """ Reset self.Cfg object to all None values before it will be loaded from file as a part of self.reload() """ for k, v in iteritems(self.Cfg.__dict__): if not k.startswith('__'): setattr(self.Cfg, k, None) def reload(self): data = self._read_file_data() if not data: return # No file or it's empty - nothing to load, use defaults try: tmp = json.loads(data) except (ValueError, TypeError) as e: raise BaseSelectorError('Unable to parse json from {} ; Error: {}' .format(self._config_file, e)) self._reset_cfg() for k, v in iteritems(tmp): setattr(self.Cfg, k, v) def _read_file_data(self): """ Should return: - whole file data for normal case - None if file doesn't exists - '' for empty file """ if not self.is_config_exists: return None try: with open(self._config_file, 'rb') as fd: data = fd.read() except (IOError, OSError) as e: raise BaseSelectorError('Unable to read data from {} ; Error: {}' .format(self._config_file, e)) return data def save(self): if not self.is_config_exists: self._create_config_dirs() data = utils.pretty_json(self._dump()) return self._write_file_data(data) def _write_file_data(self, data): try: write_file_via_tempfile( content=data, dest_path=self._config_file, perm=0o644, suffix='_tmp', ) except (IOError, OSError) as e: raise BaseSelectorError('Could not write system config ({})'.format(e)) def _ensure_version_installed(self, version): if version not in self.pkg.installed_versions: raise BaseSelectorError('Version "{}" is not installed' .format(version)) @property def selector_enabled(self): """Returns effective selector_enabled value""" if self.Cfg.selector_enabled is None: # Selector is disabled by default until explicitly enabled by admin return False return self.Cfg.selector_enabled and bool(self.pkg.installed_versions) @selector_enabled.setter def selector_enabled(self, value): if value and not self.pkg.installed_versions: raise BaseSelectorError( "It's not allowed to enable Selector when " "interpreter is not installed") self.Cfg.selector_enabled = value def get_default_version(self): # If unspecified - we still return None so Frontend can show this # somehow user-friendly return self.Cfg.default_version def set_default_version(self, version): if version is None: # We allow to reset to 'unspecified' state self.Cfg.default_version = None return if version in (self.Cfg.disabled_versions or []): raise BaseSelectorError( "It's not allowed to set disabled version as the default one") self._ensure_version_installed(version) self.Cfg.default_version = version def set_version_status(self, version, new_status): disabled_list = self.Cfg.disabled_versions if new_status == ENABLED_STATUS: if disabled_list is not None and version in disabled_list: disabled_list.remove(version) if len(disabled_list) == 0: self.Cfg.disabled_versions = None elif new_status == DISABLED_STATUS: if version == self.get_default_version(): raise BaseSelectorError("It's not allowed to disable currently " "default version") # We explicitly allow to disable even not installed versions too # for future usage if disabled_list is None: self.Cfg.disabled_versions = [version] else: if version not in disabled_list: disabled_list.append(version) else: raise BaseSelectorError('Unknown version status: "{}"' .format(new_status)) @abstractproperty def available_versions(self): raise NotImplementedError()