blob: dac917e1fa575fa767e562d4636f3c29c786b586 [file] [log] [blame]
Alex Savatieiev5118de02019-02-20 15:50:42 -06001import os
Alex9a4ad212020-10-01 18:04:25 -05002import json
3import pwd
Alexe9908f72020-05-19 16:04:53 -05004import sys
Alexc4f59622021-08-27 13:42:00 -05005import yaml
Alex Savatieiev5118de02019-02-20 15:50:42 -06006
Alex359e5752021-08-16 17:28:30 -05007from cfg_checker.common.const import ENV_TYPE_GLOB, ENV_TYPE_SALT
8from cfg_checker.common.const import ENV_TYPE_KUBE, ENV_TYPE_LINUX, ENV_LOCAL
9from cfg_checker.common.const import supported_envs
10
Alex3bc95f62020-03-05 17:00:04 -060011from cfg_checker.common.exception import ConfigException
Alex3bc95f62020-03-05 17:00:04 -060012from cfg_checker.common.log import logger_cli
Alex3ebc5632019-04-18 16:47:18 -050013
Alex9a4ad212020-10-01 18:04:25 -050014from cfg_checker.common.other import utils, shell
15from cfg_checker.common.ssh_utils import ssh_shell_p
16
17from cfg_checker.clients import get_kube_remote
Alex Savatieiev5118de02019-02-20 15:50:42 -060018
19pkg_dir = os.path.dirname(__file__)
20pkg_dir = os.path.join(pkg_dir, os.pardir, os.pardir)
21pkg_dir = os.path.normpath(pkg_dir)
22pkg_dir = os.path.abspath(pkg_dir)
23
24_default_work_folder = os.path.normpath(pkg_dir)
25
Alex9a4ad212020-10-01 18:04:25 -050026
27def _extract_salt_return(_raw):
28 if not isinstance(_raw, str):
29 _json = _raw
Alexc4f59622021-08-27 13:42:00 -050030 logger_cli.debug("... ambigious return detected")
Alex9a4ad212020-10-01 18:04:25 -050031 else:
32 try:
33 _json = json.loads(_raw)
34 except ValueError:
35 _json = _raw
36 logger_cli.debug(
Alexc4f59622021-08-27 13:42:00 -050037 "... return value is not a json: '{}'".format(_raw)
Alex9a4ad212020-10-01 18:04:25 -050038 )
39
40 return _json
41
Alex Savatieiev5118de02019-02-20 15:50:42 -060042
43class CheckerConfiguration(object):
Alexe9908f72020-05-19 16:04:53 -050044 @staticmethod
Alex Savatieiev9df93a92019-02-27 17:40:16 -060045 def load_nodes_list():
Alexe9908f72020-05-19 16:04:53 -050046 _file = os.environ.get('SALT_NODE_LIST_FILE', None)
47 if _file:
48 _v, _ = utils.get_nodes_list(
49 os.path.join(pkg_dir, _file),
50 env_sting=os.environ.get('CFG_ALL_NODES', None)
51 )
52 return _v
53 else:
54 return None
Alex Savatieiev9df93a92019-02-27 17:40:16 -060055
Alex9a4ad212020-10-01 18:04:25 -050056 def _detect(self, _type):
Alexc4f59622021-08-27 13:42:00 -050057 logger_cli.debug("... detecting '{}'".format(_type))
Alex9a4ad212020-10-01 18:04:25 -050058 if _type is None:
59 raise ConfigException("# Unexpected supported env type")
60 elif _type == ENV_TYPE_SALT:
61 # Detect salt env
62 _detect_cmd = ["curl", "-s"]
63 _detect_cmd.append(
64 "http://" + self.mcp_host + ':' + self.salt_port
65 )
66 # Try to call salt API on target host
67 _r = None
Alexc4f59622021-08-27 13:42:00 -050068 logger_cli.debug("... detecting env type '{}'".format(_type))
Alex9a4ad212020-10-01 18:04:25 -050069 if self.env_name == ENV_LOCAL:
70 _r = shell(" ".join(_detect_cmd))
71 else:
72 _r = ssh_shell_p(
73 " ".join(_detect_cmd),
74 self.ssh_host,
75 username=self.ssh_user,
76 keypath=self.ssh_key,
77 piped=False,
78 use_sudo=self.ssh_uses_sudo,
79 silent=True
80 )
81 # Parse return
82 _r = _extract_salt_return(_r)
83
84 if len(_r) < 1:
85 return False
86 elif _r["return"] == "Welcome":
87 return True
88 else:
89 return False
90 elif _type == ENV_TYPE_KUBE:
91 _kube = get_kube_remote(self)
Alex359e5752021-08-16 17:28:30 -050092 if not _kube.initialized:
93 logger_cli.debug(
Alexc4f59622021-08-27 13:42:00 -050094 "... failed to init kube using '{}'".format(
Alex359e5752021-08-16 17:28:30 -050095 _kube.kConfigPath
96 )
97 )
98 return False
99 else:
100 logger_cli.debug(
101 "... config loaded from '{}'".format(
102 _kube.kConfigPath
103 )
104 )
Alex9a4ad212020-10-01 18:04:25 -0500105 try:
106 _vApi = _kube.get_versions_api()
107 _v = _vApi.get_code()
108 if hasattr(_v, "platform") and \
109 hasattr(_v, "major") and \
110 hasattr(_v, "minor"):
Alex9a4ad212020-10-01 18:04:25 -0500111 logger_cli.info(
112 "# Kube server found: {}:{} on '{}'".format(
113 _v.major,
114 _v.minor,
Alexc4f59622021-08-27 13:42:00 -0500115 _kube.kConfigPath
Alex9a4ad212020-10-01 18:04:25 -0500116 )
117 )
118 return True
119 else:
120 return False
121 except Exception as e:
Alex359e5752021-08-16 17:28:30 -0500122 logger_cli.debug(
123 "... kube env error: '{}' ".format(
Alex9a4ad212020-10-01 18:04:25 -0500124 str(e)
125 )
126 )
127 return False
128 elif _type == ENV_TYPE_LINUX:
129 # Detect Linux env
130 from platform import system, release
131 _s = system()
132 _r = release()
Alexc4f59622021-08-27 13:42:00 -0500133 logger_cli.debug("... running on {} {}".format(_s, _r))
Alex9a4ad212020-10-01 18:04:25 -0500134 if _s in ['Linux', 'Darwin']:
135 return True
136 else:
137 return False
138 else:
139 raise ConfigException(
140 "# Env type of '{}' is not supported".format(
141 _type
142 )
143 )
144
145 def _detect_types(self):
146 """Try to detect env type based on the name
147 """
148 self.detected_envs = []
149 logger_cli.info('# Detecting env types')
150 for _env in supported_envs:
151 if self._detect(_env):
152 logger_cli.info("# '{}' found".format(_env))
153 self.detected_envs.append(_env)
154 else:
155 logger_cli.info("# '{}' not found".format(_env))
156
157 return
158
159 def _init_mcp_values(self):
Alex Savatieiev5118de02019-02-20 15:50:42 -0600160 """Load values from environment variables or put default ones
161 """
Alex9a4ad212020-10-01 18:04:25 -0500162 # filter vars and preload if needed
163 self.salt_vars = []
164 self.kube_vars = []
165 for _key, _value in self.vars:
Alexc4f59622021-08-27 13:42:00 -0500166 if _key in os.environ:
167 logger_cli.info(
168 "-> Using env var '{}={}'".format(_key, os.environ[_key])
169 )
Alex9a4ad212020-10-01 18:04:25 -0500170 if _key.startswith(ENV_TYPE_GLOB):
171 os.environ[_key] = _value
172 elif _key.startswith(ENV_TYPE_SALT):
173 self.salt_vars.append([_key, _value])
174 elif _key.startswith(ENV_TYPE_KUBE):
175 self.kube_vars.append([_key, _value])
176 else:
177 logger_cli.warn(
178 "Unsupported config variable: '{}={}'".format(
179 _key,
180 _value
181 )
182 )
Alex Savatieiev5118de02019-02-20 15:50:42 -0600183 self.name = "CheckerConfig"
184 self.working_folder = os.environ.get(
185 'CFG_TESTS_WORK_DIR',
186 _default_work_folder
187 )
188 self.date_format = "%Y-%m-%d %H:%M:%S.%f%z"
189 self.default_tz = "UTC"
190
Alex41485522019-04-12 17:26:18 -0500191 self.pkg_versions_map = 'versions_map.csv'
192
Alex359e5752021-08-16 17:28:30 -0500193 # self.ssh_uses_sudo = False
Alex9a4ad212020-10-01 18:04:25 -0500194 self.ssh_key = os.environ.get('MCP_SSH_KEY', None)
195 self.ssh_user = os.environ.get('MCP_SSH_USER', None)
196 self.ssh_host = os.environ.get('MCP_SSH_HOST', None)
Alex Savatieiev63576832019-02-27 15:46:26 -0600197
Alex9a4ad212020-10-01 18:04:25 -0500198 self.mcp_host = os.environ.get('MCP_ENV_HOST', None)
199 self.salt_port = os.environ.get('MCP_SALT_PORT', '6969')
200 self.threads = int(os.environ.get('MCP_THREADS', "5"))
Alex Savatieiev9df93a92019-02-27 17:40:16 -0600201
Alex Savatieiev5118de02019-02-20 15:50:42 -0600202 self.skip_nodes = utils.node_string_to_list(os.environ.get(
203 'CFG_SKIP_NODES',
204 None
205 ))
Alex9a4ad212020-10-01 18:04:25 -0500206 # prebuild user data and folder path
207 self.pw_user = pwd.getpwuid(os.getuid())
208 if self.env_name == "local":
209 pass
210 else:
211 if not self.ssh_key and not self.force_no_key:
212 raise ConfigException(
213 "Please, supply a key for the cluster's master node. "
214 "Use MCP_SSH_KEY, see 'etc/example.env'"
215 )
216
217 def _init_env_values(self):
218 if ENV_TYPE_SALT in self.detected_envs:
219 for _key, _value in self.salt_vars:
Alexc4f59622021-08-27 13:42:00 -0500220 if _key not in os.environ:
221 os.environ[_key] = _value
Alex9a4ad212020-10-01 18:04:25 -0500222
223 self.salt_user = os.environ.get('SALT_USER', 'salt')
224 self.salt_timeout = os.environ.get('SALT_TIMEOUT', 30)
Alexc4f59622021-08-27 13:42:00 -0500225 self.salt_file_root = os.environ.get(
226 'SALT_FILE_ROOT',
227 "/usr/share/salt-formulas/env/"
228 )
Alex9a4ad212020-10-01 18:04:25 -0500229 self.salt_scripts_folder = os.environ.get(
230 'SALT_SCRIPTS_FOLDER',
231 'cfg_checker_scripts'
232 )
233 elif ENV_TYPE_KUBE in self.detected_envs:
234 for _key, _value in self.kube_vars:
Alexc4f59622021-08-27 13:42:00 -0500235 if _key not in os.environ:
236 os.environ[_key] = _value
Alex9a4ad212020-10-01 18:04:25 -0500237
Alexc4f59622021-08-27 13:42:00 -0500238 self.kube_config_root = os.environ.get('KUBE_CONFIG_ROOT', "/root")
Alex9a4ad212020-10-01 18:04:25 -0500239 self.kube_scripts_folder = os.environ.get(
240 'KUBE_SCRIPTS_FOLDER',
Alexc4f59622021-08-27 13:42:00 -0500241 "cfg-checker-scripts"
Alex9a4ad212020-10-01 18:04:25 -0500242 )
243 self.kube_node_user = os.environ.get(
244 'KUBE_NODE_USER',
245 'ubuntu'
246 )
247 self.kube_node_keypath = os.environ.get(
248 'KUBE_NODE_KEYPATH',
249 None
250 )
251 # Warn user only if Kube env is detected locally
Alexccb72e02021-01-20 16:38:03 -0600252 if self.env_name == ENV_LOCAL:
Alex9a4ad212020-10-01 18:04:25 -0500253 if not os.path.exists(self.kube_config_path):
254 logger_cli.warn(
255 "Kube config path not found on local env: '{}'".format(
256 self.kube_config_path
257 )
258 )
259 # On local envs, KUBE_NODE_KEYPATH is mandatory and is
260 # provided to give cfg-checker access to kube nodes
261 if not self.kube_node_keypath and not self.force_no_key:
262 raise ConfigException(
263 "Please, supply a key for the cluster nodes. "
264 "Use KUBE_NODE_KEYPATH, see 'etc/example.env'. "
265 "Consider checking KUBE_NODE_USER as well"
266 )
Alex36308942020-11-09 17:13:38 -0600267 self.kube_node_homepath = os.path.join(
268 '/home',
269 self.kube_node_user
270 )
Alex9a4ad212020-10-01 18:04:25 -0500271 else:
Alex205546c2020-12-30 19:22:30 -0600272 # Init key for nodes
273 # KUBE_NODE_KEYPATH is provided in case of node keys would be
274 # different to master node key, which is supplied
Alex9a4ad212020-10-01 18:04:25 -0500275 # using MCP_SSH_KEY (mandatory) and, for the most cases,
276 # should be the same for remote envs
277 if not self.kube_node_keypath and not self.force_no_key:
278 logger_cli.debug(
279 "... using MCP_SSH_KEY as node keys. "
280 "Supply KUBE_NODE_KEYPATH to update."
281 )
282 self.kube_node_keypath = self.ssh_key
Alex36308942020-11-09 17:13:38 -0600283 self.kube_node_homepath = self.homepath
Alex Savatieiev5118de02019-02-20 15:50:42 -0600284
Alexccb72e02021-01-20 16:38:03 -0600285 def _init_env(self, config_path, env_name=None):
Alex Savatieiev63576832019-02-27 15:46:26 -0600286 """Inits the environment vars from the env file
287 Uses simple validation for the values and names
Alex Savatieiev5118de02019-02-20 15:50:42 -0600288
289 Keyword Arguments:
290 env_name {str} -- environment name to search configuration
291 files in etc/<env_name>.env (default: {None})
Alex9a4ad212020-10-01 18:04:25 -0500292 env_type {str} -- environment type to use: salt/kube
Alex Savatieiev5118de02019-02-20 15:50:42 -0600293
294 Raises:
295 ConfigException -- on IO error when loading env file
296 ConfigException -- on env file failed validation
297 """
Alexc4f59622021-08-27 13:42:00 -0500298 # detect kubeconfig placement
299 _env_kubeconf_path = os.environ.get('KUBECONFIG', None)
300 if not os.path.exists(self.kube_config_path):
301 logger_cli.debug(
302 "... kubeconfig not detected at '{}'".format(
303 self.kube_config_path
304 )
305 )
306 # not exists, get KUBECONFIG var
307 if _env_kubeconf_path:
308 # get the env var path
309 self.kube_config_path = _env_kubeconf_path
310 logger_cli.debug(
311 "... KUBECONFIG var points to '{}'".format(
312 self.kube_config_path
313 )
314 )
315 self.kube_config_detected = True
316 else:
317 logger_cli.debug("... KUBECONFIG env var not found")
318 self.kube_config_path = None
319 self.kube_config_detected = False
320 else:
321 logger_cli.debug(
322 "... kubeconfig detected at '{}'".format(
323 self.kube_config_path
324 )
325 )
326 self.kube_config_detected = True
327
328 # try to load values from KUBECONF
329 _kube_conf = None
330 if self.kube_config_path:
331 with open(self.kube_config_path) as kF:
332 _kube_conf = yaml.load(kF, Loader=yaml.SafeLoader)
333 # extract host ip
334 try:
335 _server = _kube_conf["clusters"][0]["cluster"]["server"]
336 except KeyError as e:
337 logger_cli.debug(
338 "... failed to extract server ip: "
339 "no '{}' key in 'clusters/[0]/cluster/server".format(e)
340 )
341 except IndexError:
342 logger_cli.debug(
343 "... failed to extract server ip: empty cluster list"
344 )
345 _ip = _server.split(':')
346 self.remote_ip = _ip[1].replace('/', '')
347 logger_cli.debug("... detected ip: '{}'".format(self.remote_ip))
348
Alex Savatieiev5118de02019-02-20 15:50:42 -0600349 # load env file as init os.environment with its values
Alexccb72e02021-01-20 16:38:03 -0600350 if os.path.isfile(config_path):
351 with open(config_path) as _f:
Alex Savatieiev5118de02019-02-20 15:50:42 -0600352 _list = _f.read().splitlines()
Alex3ebc5632019-04-18 16:47:18 -0500353 logger_cli.info(
354 "# Loading env vars from '{}'".format(
Alexccb72e02021-01-20 16:38:03 -0600355 config_path
Alex3ebc5632019-04-18 16:47:18 -0500356 )
357 )
Alex Savatieiev5118de02019-02-20 15:50:42 -0600358 else:
359 raise ConfigException(
Alex Savatieievf808cd22019-03-01 13:17:59 -0600360 "# Failed to load enviroment vars from '{}'".format(
Alexccb72e02021-01-20 16:38:03 -0600361 config_path
Alex Savatieiev5118de02019-02-20 15:50:42 -0600362 )
363 )
Alex9a4ad212020-10-01 18:04:25 -0500364 self.vars = []
Alex Savatieiev5118de02019-02-20 15:50:42 -0600365 for index in range(len(_list)):
366 _line = _list[index]
367 # skip comments
368 if _line.strip().startswith('#'):
369 continue
370 # validate
371 _errors = []
Alex9a4ad212020-10-01 18:04:25 -0500372 if len(_line) < 1:
373 _errors.append("Line {}: empty".format(index))
374 elif _line.find('=') < 0 or _line.count('=') > 1:
Alex Savatieiev5118de02019-02-20 15:50:42 -0600375 _errors.append("Line {}: {}".format(index, _line))
376 else:
377 # save values
378 _t = _line.split('=')
Alex9a4ad212020-10-01 18:04:25 -0500379 self.vars.append([_t[0], _t[1]])
Alex Savatieiev5118de02019-02-20 15:50:42 -0600380 # if there was errors, report them
381 if _errors:
382 raise ConfigException(
Alex Savatieievf808cd22019-03-01 13:17:59 -0600383 "# Environment file failed validation in lines: {}".format(
Alex Savatieiev5118de02019-02-20 15:50:42 -0600384 "\n".join(_errors)
385 )
386 )
387 else:
Alex3ebc5632019-04-18 16:47:18 -0500388 logger_cli.debug(
Alexc4f59622021-08-27 13:42:00 -0500389 "... loaded total of '{}' vars".format(
Alex3ebc5632019-04-18 16:47:18 -0500390 len(_list)
391 )
392 )
Alexccb72e02021-01-20 16:38:03 -0600393 self.env_name = env_name
Alex Savatieiev5118de02019-02-20 15:50:42 -0600394
Alex9a4ad212020-10-01 18:04:25 -0500395 def __init__(self, args):
Alex Savatieiev5118de02019-02-20 15:50:42 -0600396 """Base configuration class. Only values that are common for all scripts
397 """
Alex9a4ad212020-10-01 18:04:25 -0500398 self.ssh_uses_sudo = args.sudo
Alexeffa0682021-06-04 12:18:33 -0500399 self.ssh_direct = args.ssh_direct
Alexccb72e02021-01-20 16:38:03 -0600400 self.kube_config_path = args.kube_config
Alex9a4ad212020-10-01 18:04:25 -0500401 self.debug = args.debug
Alex33747812021-04-07 10:11:39 -0500402 self.insecure = args.insecure
Alex9a4ad212020-10-01 18:04:25 -0500403 self.force_no_key = args.force_no_key
Alexe9908f72020-05-19 16:04:53 -0500404 # Make sure we running on Python 3
405 if sys.version_info[0] < 3 and sys.version_info[1] < 5:
406 logger_cli.error("# ERROR: Python 3.5+ is required")
407 sys.exit(1)
408 else:
409 logger_cli.debug("### Python version is {}.{}".format(
410 sys.version_info[0],
411 sys.version_info[1]
412 ))
413
Alexccb72e02021-01-20 16:38:03 -0600414 # if env name is default, check var too
415 if args.env_name == ENV_LOCAL:
416 _env = os.getenv('MCP_ENV', None)
417 _env = _env if _env else args.env_name
Alex359e5752021-08-16 17:28:30 -0500418 _env_config_path = os.path.join(pkg_dir, 'etc', _env + '.env')
Alexccb72e02021-01-20 16:38:03 -0600419 else:
420 _env = args.env_name
Alex359e5752021-08-16 17:28:30 -0500421 _env_config_path = args.env_config
Alex9a4ad212020-10-01 18:04:25 -0500422
423 # Init environment variables from file, validate
Alex359e5752021-08-16 17:28:30 -0500424 self._init_env(_env_config_path, env_name=_env)
Alex9a4ad212020-10-01 18:04:25 -0500425 # Load Common vars for any type of the env
426 self._init_mcp_values()
427 # Detect env types present
428 self._detect_types()
429 # handle forced env type var
430 _forced_type = os.getenv('MCP_TYPE_FORCE', None)
431 if _forced_type in supported_envs:
432 self.detected_envs.append(_forced_type)
433 elif _forced_type is not None:
434 logger_cli.warn(
435 "Unsupported forced type of '{}'".format(
436 _forced_type
437 )
438 )
439 # Check if any of the envs detected
440 if len(self.detected_envs) < 1:
441 if _env is None:
442 raise ConfigException("No environment types detected locally")
443 else:
444 raise ConfigException(
445 "No environment types detected at '{}'".format(
446 self.mcp_host
447 )
448 )
Alex9a4ad212020-10-01 18:04:25 -0500449 # initialize path to folders
Alexccb72e02021-01-20 16:38:03 -0600450 if self.env_name == ENV_LOCAL:
Alex9a4ad212020-10-01 18:04:25 -0500451 # names and folders
452 self.user = self.pw_user.pw_name
453 self.homepath = self.pw_user.pw_dir
Alex9a4ad212020-10-01 18:04:25 -0500454 else:
455 # names and folders in case of remote env
456 self.user = self.ssh_user
457 self.homepath = os.path.join('/home', self.ssh_user)
Alexccb72e02021-01-20 16:38:03 -0600458
459 # Init vars that is specific to detected envs only
460 self._init_env_values()