ok
Direktori : /opt/cloudlinux/venv/lib/python3.11/site-packages/clwpos/user/ |
Current File : //opt/cloudlinux/venv/lib/python3.11/site-packages/clwpos/user/config.py |
# -*- coding: utf-8 -*- # Copyright © Cloud Linux GmbH & Cloud Linux Software, Inc 2010-2021 All Rights Reserved # # Licensed under CLOUD LINUX LICENSE AGREEMENT # http://cloudlinux.com/docs/LICENSE.TXT from __future__ import absolute_import import datetime import json import logging import os import pwd import traceback from copy import deepcopy from enum import IntEnum, auto from typing import Iterable, Optional from clwpos.optimization_features import ALL_OPTIMIZATION_FEATURES, Feature from clwpos.logsetup import setup_logging from clwpos.utils import ( get_relative_docroot, create_clwpos_dir_if_not_exists, is_run_under_user ) from clcommon.clwpos_lib import is_wp_path from clwpos import constants from clwpos.cl_wpos_exceptions import WposError from clwpos import gettext as _ class ConfigError(WposError): """ Used for all exceptions during handling clwpos user config in UserConfig methods """ pass class LicenseApproveStatus(IntEnum): # feature does not require approve to work NOT_REQUIRED = auto() # feature required approve, but it was not given yet NOT_APPROVED = auto() # feature required approve and it was given APPROVED = auto() # feature required approve,it was given, # but license changed and we need another approve # TODO: currently unused # UPDATE_REQUIRED = auto() class UserConfig(object): """ Class to manage clwpos user config - read, write, set params in config. """ CONFIG_PATH = os.path.join("{homedir}", constants.USER_WPOS_DIR, constants.USER_CLWPOS_CONFIG) DEFAULT_MAX_CACHE_MEMORY = f"{constants.DEFAULT_MAX_CACHE_MEMORY}mb" DEFAULT_CONFIG = {"docroots": {}, "max_cache_memory": DEFAULT_MAX_CACHE_MEMORY} def __init__(self, username, allow_root=False, setup_logs=True): if not allow_root: self._validate_permissions() self.username = username self.homedir = pwd.getpwnam(username).pw_dir self.config_path = self.CONFIG_PATH.format(homedir=self.homedir) if setup_logs: self._logger = setup_logging(__name__) else: self._logger = logging.getLogger("UserConfig") create_clwpos_dir_if_not_exists(username) def _validate_permissions(self): if not is_run_under_user(): raise ConfigError(_("Trying to use UserConfig class as root")) def read_config(self): """ Reads config from self.config_path DO NOT USE THIS DIRECTLY! USE get_config INSTEAD! """ try: with open(self.config_path, "r") as f: return json.loads(f.read()) except Exception: exc_string = traceback.format_exc() raise ConfigError( message=_("Error while reading config %(config_path)s: %(exception_string)s"), context={"config_path": self.config_path, "exception_string": exc_string} ) def write_config(self, config: dict): """ Writes config (as json) to self.config_path """ try: config_json = json.dumps(config, indent=4, sort_keys=True) with open(self.config_path, "w") as f: f.write(config_json) except Exception as e: raise ConfigError( message=_("Attempt of writing to config file failed due to error:\n%(exception)s"), context={"exception": e} ) def is_default_config(self): """ Checks if user customized his config already. """ return not os.path.exists(self.config_path) def get_config(self): """ Returns default config or config content from self.config_path """ # if config file is not exists, returns DEFAULT CONFIG if self.is_default_config(): return deepcopy(self.DEFAULT_CONFIG) # Otherwise, reads config from file # and returns it if it's not broken try: config = self.read_config() except ConfigError: return deepcopy(self.DEFAULT_CONFIG) return config if isinstance(config, dict) else deepcopy(self.DEFAULT_CONFIG) def set_params(self, params: dict): """ Set outer (not "docroots") params in config. Example: Old config: { "docroots": ..., "max_cache_memory": "123mb", } Input params: { "max_cache_memory": "1024mb", "param": "value" } New config: { "docroots": ..., "max_cache_memory": "1024mb", "param": "value" } """ config = self.get_config() for key, value in params.items(): config[key] = value self.write_config(config) def is_module_enabled( self, domain: str, wp_path: str, module: str, config: Optional[dict] = None) -> bool: config = config or self.get_config() try: docroot = get_relative_docroot(domain, self.homedir) except Exception as e: self._logger.warning(e, exc_info=True) raise ConfigError( message=_("Can't find docroot for domain '%(domain)s' and homedir '%(homedir)s'"), context={"domain": domain, "homedir": self.homedir} ) if not is_wp_path(os.path.join(self.homedir, docroot, wp_path)): raise ConfigError( message=_("Wrong wordpress path '%(wp_path)s' passed"), context={"wp_path": wp_path} ) if module not in ALL_OPTIMIZATION_FEATURES: raise ConfigError( message=_("Invalid feature %(feature)s, available choices: %(choices)s"), context={"feature": module, "choices": ALL_OPTIMIZATION_FEATURES} ) try: docroots = config["docroots"] module_info = docroots.get(docroot, {}).get(wp_path, []) return module in module_info except (KeyError, AttributeError, TypeError) as e: self._logger.warning(f"config {self.config_path} is broken: {e}", exc_info=True) raise ConfigError( message=_("Config is broken.\nRepair %(config_path)s or restore from backup."), context={"config_path": self.config_path} ) def get_license_approve_status(self, feature: Feature) -> LicenseApproveStatus: """ Returns NOT_REQUIRED if feature does not require any approve Returns NOT_APPROVED in case if user is required to approve license terms before he can use the feature. Returns APPROVED in case if license terms were applied. """ if not feature.HAS_LICENSE_TERMS: return LicenseApproveStatus.NOT_REQUIRED if feature.NAME not in self.get_config().get('approved_licenses', {}): return LicenseApproveStatus.NOT_APPROVED return LicenseApproveStatus.APPROVED def approve_license_agreement(self, feature: Feature): """ Writes information about approved license terms for given feature to config file. """ config = self.get_config() approved_licenses = config.get('approved_licenses', {}) approved_licenses[feature.NAME] = dict( approve_date=datetime.datetime.now().isoformat() ) config['approved_licenses'] = approved_licenses self.write_config(config) def disable_module(self, domain: str, wp_path: str, module: str) -> None: try: docroot = get_relative_docroot(domain, self.homedir) except Exception as e: self._logger.exception(e) raise ConfigError( message=_("Docroot for domain '%(domain)s' is not found"), context={"domain": domain} ) if not is_wp_path(os.path.join(self.homedir, docroot, wp_path)): raise ConfigError( message=_("Wrong wordpress path '%(wp_path)s' passed"), context={"wp_path": wp_path} ) if module not in ALL_OPTIMIZATION_FEATURES: raise ConfigError( message=_("Invalid feature %(feature)s, available choices: %(choices)s"), context={"feature": module, "choices": ALL_OPTIMIZATION_FEATURES} ) config = self.get_config() # check here as well that config has expected structure if not self.is_module_enabled(domain, wp_path, module, config): return # remove module from the list config["docroots"][docroot][wp_path].remove(module) # delete wp_path if all modules are disabled if not config["docroots"][docroot][wp_path]: del config["docroots"][docroot][wp_path] # delete docroot in it doesn't have wordpresses if not config["docroots"][docroot]: del config["docroots"][docroot] self.write_config(config) def enable_module(self, domain: str, wp_path: str, feature: str) -> None: try: docroot = get_relative_docroot(domain, self.homedir) except Exception as e: self._logger.exception(e) raise ConfigError( message=_("Docroot for domain '%(domain)s' is not found"), context={"domain": domain} ) if not is_wp_path(os.path.join(self.homedir, docroot, wp_path)): raise ConfigError( message=_("Wrong wordpress path '%(wp_path)s' passed"), context={"wp_path": wp_path} ) if feature not in ALL_OPTIMIZATION_FEATURES: raise ConfigError( message=_("Invalid feature %(feature)s, available choices: %(choices)s"), context={"feature": feature, "choices": ALL_OPTIMIZATION_FEATURES} ) config = self.get_config() # check here as well that config has expected structure if self.is_module_enabled(domain, wp_path, feature, config): return if "docroots" not in config: config["docroots"] = {} if docroot not in config["docroots"]: config["docroots"][docroot] = {} if wp_path not in config["docroots"][docroot]: config["docroots"][docroot][wp_path] = [] config["docroots"][docroot][wp_path].append(feature) self.write_config(config) def enabled_modules(self): for doc_root, doc_root_info in self.get_config()["docroots"].items(): for wp_path, module_names in doc_root_info.items(): for name in module_names: yield doc_root, wp_path, name def wp_paths_with_enabled_module(self, module_name: str) -> Iterable[str]: """ Return absolute WP paths with specified module enabled. """ for doc_root, wp_path, name in self.enabled_modules(): if name == module_name: yield os.path.join(self.homedir, doc_root, wp_path) def wp_paths_with_active_suite_features(self, features_set: set): """ Unique set of sites with active features from feature set SET is used here, because one site may have several features activated from one set e.g: site1 with activated object_cache, shortcodes = 1 path """ sites = set() for feature in features_set: sites_with_enabled_feature = self.wp_paths_with_enabled_module(feature) for site in sites_with_enabled_feature: sites.add(site) return sites def get_enabled_sites_count_by_modules(self, checked_module_names): """ Returns count of sites with enabled module """ sites_count = 0 for _, doc_root_info in self.get_config().get('docroots', {}).items(): for _, module_names in doc_root_info.items(): sites_count += any(checked_module_name in module_names for checked_module_name in checked_module_names) return sites_count