AlkantarClanX12

Your IP : 18.225.195.4


Current Path : /opt/cloudlinux/venv/lib64/python3.11/site-packages/clselect/baseclselect/
Upload File :
Current File : //opt/cloudlinux/venv/lib64/python3.11/site-packages/clselect/baseclselect/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()