| import os |
| import json |
| import pwd |
| import sys |
| import yaml |
| |
| from cfg_checker.common.const import ENV_TYPE_GLOB, ENV_TYPE_SALT |
| from cfg_checker.common.const import ENV_TYPE_KUBE, ENV_TYPE_LINUX, ENV_LOCAL |
| from cfg_checker.common.const import supported_envs |
| |
| from cfg_checker.common.exception import ConfigException |
| from cfg_checker.common.log import logger_cli |
| |
| from cfg_checker.common.other import utils, shell |
| from cfg_checker.common.ssh_utils import ssh_shell_p |
| |
| from cfg_checker.clients import get_kube_remote |
| |
| pkg_dir = os.path.dirname(__file__) |
| pkg_dir = os.path.join(pkg_dir, os.pardir, os.pardir) |
| pkg_dir = os.path.normpath(pkg_dir) |
| pkg_dir = os.path.abspath(pkg_dir) |
| |
| _default_work_folder = os.path.normpath(pkg_dir) |
| |
| |
| def _extract_salt_return(_raw): |
| if not isinstance(_raw, str): |
| _json = _raw |
| logger_cli.debug("... ambigious return detected") |
| else: |
| try: |
| _json = json.loads(_raw) |
| except ValueError: |
| _json = _raw |
| logger_cli.debug( |
| "... return value is not a json: '{}'".format(_raw) |
| ) |
| |
| return _json |
| |
| |
| class CheckerConfiguration(object): |
| @staticmethod |
| def load_nodes_list(): |
| _file = os.environ.get('SALT_NODE_LIST_FILE', None) |
| if _file: |
| _v, _ = utils.get_nodes_list( |
| os.path.join(pkg_dir, _file), |
| env_sting=os.environ.get('CFG_ALL_NODES', None) |
| ) |
| return _v |
| else: |
| return None |
| |
| def _detect(self, _type): |
| logger_cli.debug("... detecting '{}'".format(_type)) |
| if _type is None: |
| raise ConfigException("# Unexpected supported env type") |
| elif _type == ENV_TYPE_SALT: |
| # Detect salt env |
| _detect_cmd = ["curl", "-s"] |
| _detect_cmd.append( |
| "http://" + self.mcp_host + ':' + self.salt_port |
| ) |
| # Try to call salt API on target host |
| _r = None |
| logger_cli.debug("... detecting env type '{}'".format(_type)) |
| if self.env_name == ENV_LOCAL: |
| _r = shell(" ".join(_detect_cmd)) |
| else: |
| _r = ssh_shell_p( |
| " ".join(_detect_cmd), |
| self.ssh_host, |
| username=self.ssh_user, |
| keypath=self.ssh_key, |
| piped=False, |
| use_sudo=self.ssh_uses_sudo, |
| silent=True |
| ) |
| # Parse return |
| _r = _extract_salt_return(_r) |
| |
| if len(_r) < 1: |
| return False |
| elif _r["return"] == "Welcome": |
| return True |
| else: |
| return False |
| elif _type == ENV_TYPE_KUBE: |
| _kube = get_kube_remote(self) |
| if not _kube.initialized: |
| logger_cli.debug( |
| "... failed to init kube using '{}'".format( |
| _kube.kConfigPath |
| ) |
| ) |
| return False |
| else: |
| logger_cli.debug( |
| "... config loaded from '{}'".format( |
| _kube.kConfigPath |
| ) |
| ) |
| try: |
| _vApi = _kube.get_versions_api() |
| _v = _vApi.get_code() |
| if hasattr(_v, "platform") and \ |
| hasattr(_v, "major") and \ |
| hasattr(_v, "minor"): |
| logger_cli.info( |
| "# Kube server found: {}:{} on '{}'".format( |
| _v.major, |
| _v.minor, |
| _kube.kConfigPath |
| ) |
| ) |
| return True |
| else: |
| return False |
| except Exception as e: |
| logger_cli.debug( |
| "... kube env error: '{}' ".format( |
| str(e) |
| ) |
| ) |
| return False |
| elif _type == ENV_TYPE_LINUX: |
| # Detect Linux env |
| from platform import system, release |
| _s = system() |
| _r = release() |
| logger_cli.debug("... running on {} {}".format(_s, _r)) |
| if _s in ['Linux', 'Darwin']: |
| return True |
| else: |
| return False |
| else: |
| raise ConfigException( |
| "# Env type of '{}' is not supported".format( |
| _type |
| ) |
| ) |
| |
| def _detect_types(self): |
| """Try to detect env type based on the name |
| """ |
| self.detected_envs = [] |
| logger_cli.info('# Detecting env types') |
| for _env in supported_envs: |
| if self._detect(_env): |
| logger_cli.info("# '{}' found".format(_env)) |
| self.detected_envs.append(_env) |
| else: |
| logger_cli.info("# '{}' not found".format(_env)) |
| |
| return |
| |
| def _init_mcp_values(self): |
| """Load values from environment variables or put default ones |
| """ |
| # filter vars and preload if needed |
| self.salt_vars = [] |
| self.kube_vars = [] |
| for _key, _value in self.vars: |
| if _key in os.environ: |
| logger_cli.info( |
| "-> Using env var '{}={}'".format(_key, os.environ[_key]) |
| ) |
| if _key.startswith(ENV_TYPE_GLOB): |
| os.environ[_key] = _value |
| elif _key.startswith(ENV_TYPE_SALT): |
| self.salt_vars.append([_key, _value]) |
| elif _key.startswith(ENV_TYPE_KUBE): |
| self.kube_vars.append([_key, _value]) |
| else: |
| logger_cli.warn( |
| "Unsupported config variable: '{}={}'".format( |
| _key, |
| _value |
| ) |
| ) |
| self.name = "CheckerConfig" |
| self.working_folder = os.environ.get( |
| 'CFG_TESTS_WORK_DIR', |
| _default_work_folder |
| ) |
| self.date_format = "%Y-%m-%d %H:%M:%S.%f%z" |
| self.default_tz = "UTC" |
| |
| self.pkg_versions_map = 'versions_map.csv' |
| |
| # self.ssh_uses_sudo = False |
| self.ssh_key = os.environ.get('MCP_SSH_KEY', None) |
| self.ssh_user = os.environ.get('MCP_SSH_USER', None) |
| self.ssh_host = os.environ.get('MCP_SSH_HOST', None) |
| |
| self.mcp_host = os.environ.get('MCP_ENV_HOST', None) |
| self.salt_port = os.environ.get('MCP_SALT_PORT', '6969') |
| self.threads = int(os.environ.get('MCP_THREADS', "5")) |
| |
| self.skip_nodes = utils.node_string_to_list(os.environ.get( |
| 'CFG_SKIP_NODES', |
| None |
| )) |
| # prebuild user data and folder path |
| self.pw_user = pwd.getpwuid(os.getuid()) |
| if self.env_name == "local": |
| pass |
| else: |
| if not self.ssh_key and not self.force_no_key: |
| raise ConfigException( |
| "Please, supply a key for the cluster's master node. " |
| "Use MCP_SSH_KEY, see 'etc/example.env'" |
| ) |
| |
| def _init_env_values(self): |
| if ENV_TYPE_SALT in self.detected_envs: |
| for _key, _value in self.salt_vars: |
| if _key not in os.environ: |
| os.environ[_key] = _value |
| |
| self.salt_user = os.environ.get('SALT_USER', 'salt') |
| self.salt_timeout = os.environ.get('SALT_TIMEOUT', 30) |
| self.salt_file_root = os.environ.get( |
| 'SALT_FILE_ROOT', |
| "/usr/share/salt-formulas/env/" |
| ) |
| self.salt_scripts_folder = os.environ.get( |
| 'SALT_SCRIPTS_FOLDER', |
| 'cfg_checker_scripts' |
| ) |
| elif ENV_TYPE_KUBE in self.detected_envs: |
| for _key, _value in self.kube_vars: |
| if _key not in os.environ: |
| os.environ[_key] = _value |
| |
| self.kube_config_root = os.environ.get('KUBE_CONFIG_ROOT', "/root") |
| self.kube_scripts_folder = os.environ.get( |
| 'KUBE_SCRIPTS_FOLDER', |
| "cfg-checker-scripts" |
| ) |
| self.kube_node_user = os.environ.get( |
| 'KUBE_NODE_USER', |
| 'ubuntu' |
| ) |
| self.kube_node_keypath = os.environ.get( |
| 'KUBE_NODE_KEYPATH', |
| None |
| ) |
| # Warn user only if Kube env is detected locally |
| if self.env_name == ENV_LOCAL: |
| if not os.path.exists(self.kube_config_path): |
| logger_cli.warn( |
| "Kube config path not found on local env: '{}'".format( |
| self.kube_config_path |
| ) |
| ) |
| # On local envs, KUBE_NODE_KEYPATH is mandatory and is |
| # provided to give cfg-checker access to kube nodes |
| if not self.kube_node_keypath and not self.force_no_key: |
| raise ConfigException( |
| "Please, supply a key for the cluster nodes. " |
| "Use KUBE_NODE_KEYPATH, see 'etc/example.env'. " |
| "Consider checking KUBE_NODE_USER as well" |
| ) |
| self.kube_node_homepath = os.path.join( |
| '/home', |
| self.kube_node_user |
| ) |
| else: |
| # Init key for nodes |
| # KUBE_NODE_KEYPATH is provided in case of node keys would be |
| # different to master node key, which is supplied |
| # using MCP_SSH_KEY (mandatory) and, for the most cases, |
| # should be the same for remote envs |
| if not self.kube_node_keypath and not self.force_no_key: |
| logger_cli.debug( |
| "... using MCP_SSH_KEY as node keys. " |
| "Supply KUBE_NODE_KEYPATH to update." |
| ) |
| self.kube_node_keypath = self.ssh_key |
| self.kube_node_homepath = self.homepath |
| |
| def _init_env(self, config_path, env_name=None): |
| """Inits the environment vars from the env file |
| Uses simple validation for the values and names |
| |
| Keyword Arguments: |
| env_name {str} -- environment name to search configuration |
| files in etc/<env_name>.env (default: {None}) |
| env_type {str} -- environment type to use: salt/kube |
| |
| Raises: |
| ConfigException -- on IO error when loading env file |
| ConfigException -- on env file failed validation |
| """ |
| # detect kubeconfig placement |
| _env_kubeconf_path = os.environ.get('KUBECONFIG', None) |
| if not os.path.exists(self.kube_config_path): |
| logger_cli.debug( |
| "... kubeconfig not detected at '{}'".format( |
| self.kube_config_path |
| ) |
| ) |
| # not exists, get KUBECONFIG var |
| if _env_kubeconf_path: |
| # get the env var path |
| self.kube_config_path = _env_kubeconf_path |
| logger_cli.debug( |
| "... KUBECONFIG var points to '{}'".format( |
| self.kube_config_path |
| ) |
| ) |
| self.kube_config_detected = True |
| else: |
| logger_cli.debug("... KUBECONFIG env var not found") |
| self.kube_config_path = None |
| self.kube_config_detected = False |
| else: |
| logger_cli.debug( |
| "... kubeconfig detected at '{}'".format( |
| self.kube_config_path |
| ) |
| ) |
| self.kube_config_detected = True |
| |
| # try to load values from KUBECONF |
| _kube_conf = None |
| if self.kube_config_path: |
| with open(self.kube_config_path) as kF: |
| _kube_conf = yaml.load(kF, Loader=yaml.SafeLoader) |
| # extract host ip |
| try: |
| _server = _kube_conf["clusters"][0]["cluster"]["server"] |
| except KeyError as e: |
| logger_cli.debug( |
| "... failed to extract server ip: " |
| "no '{}' key in 'clusters/[0]/cluster/server".format(e) |
| ) |
| except IndexError: |
| logger_cli.debug( |
| "... failed to extract server ip: empty cluster list" |
| ) |
| _ip = _server.split(':') |
| self.remote_ip = _ip[1].replace('/', '') |
| logger_cli.debug("... detected ip: '{}'".format(self.remote_ip)) |
| |
| # load env file as init os.environment with its values |
| if os.path.isfile(config_path): |
| with open(config_path) as _f: |
| _list = _f.read().splitlines() |
| logger_cli.info( |
| "# Loading env vars from '{}'".format( |
| config_path |
| ) |
| ) |
| else: |
| raise ConfigException( |
| "# Failed to load enviroment vars from '{}'".format( |
| config_path |
| ) |
| ) |
| self.vars = [] |
| for index in range(len(_list)): |
| _line = _list[index] |
| # skip comments |
| if _line.strip().startswith('#'): |
| continue |
| # validate |
| _errors = [] |
| if len(_line) < 1: |
| _errors.append("Line {}: empty".format(index)) |
| elif _line.find('=') < 0 or _line.count('=') > 1: |
| _errors.append("Line {}: {}".format(index, _line)) |
| else: |
| # save values |
| _t = _line.split('=') |
| self.vars.append([_t[0], _t[1]]) |
| # if there was errors, report them |
| if _errors: |
| raise ConfigException( |
| "# Environment file failed validation in lines: {}".format( |
| "\n".join(_errors) |
| ) |
| ) |
| else: |
| logger_cli.debug( |
| "... loaded total of '{}' vars".format( |
| len(_list) |
| ) |
| ) |
| self.env_name = env_name |
| |
| def __init__(self, args): |
| """Base configuration class. Only values that are common for all scripts |
| """ |
| self.ssh_uses_sudo = args.sudo |
| self.ssh_direct = args.ssh_direct |
| self.kube_config_path = args.kube_config |
| self.debug = args.debug |
| self.insecure = args.insecure |
| self.force_no_key = args.force_no_key |
| # Make sure we running on Python 3 |
| if sys.version_info[0] < 3 and sys.version_info[1] < 5: |
| logger_cli.error("# ERROR: Python 3.5+ is required") |
| sys.exit(1) |
| else: |
| logger_cli.debug("### Python version is {}.{}".format( |
| sys.version_info[0], |
| sys.version_info[1] |
| )) |
| |
| # if env name is default, check var too |
| if args.env_name == ENV_LOCAL: |
| _env = os.getenv('MCP_ENV', None) |
| _env = _env if _env else args.env_name |
| _env_config_path = os.path.join(pkg_dir, 'etc', _env + '.env') |
| else: |
| _env = args.env_name |
| _env_config_path = args.env_config |
| |
| # Init environment variables from file, validate |
| self._init_env(_env_config_path, env_name=_env) |
| # Load Common vars for any type of the env |
| self._init_mcp_values() |
| # Detect env types present |
| self._detect_types() |
| # handle forced env type var |
| _forced_type = os.getenv('MCP_TYPE_FORCE', None) |
| if _forced_type in supported_envs: |
| self.detected_envs.append(_forced_type) |
| elif _forced_type is not None: |
| logger_cli.warn( |
| "Unsupported forced type of '{}'".format( |
| _forced_type |
| ) |
| ) |
| # Check if any of the envs detected |
| if len(self.detected_envs) < 1: |
| if _env is None: |
| raise ConfigException("No environment types detected locally") |
| else: |
| raise ConfigException( |
| "No environment types detected at '{}'".format( |
| self.mcp_host |
| ) |
| ) |
| # initialize path to folders |
| if self.env_name == ENV_LOCAL: |
| # names and folders |
| self.user = self.pw_user.pw_name |
| self.homepath = self.pw_user.pw_dir |
| else: |
| # names and folders in case of remote env |
| self.user = self.ssh_user |
| self.homepath = os.path.join('/home', self.ssh_user) |
| |
| # Init vars that is specific to detected envs only |
| self._init_env_values() |