blob: c6549706334f12a1b463c3c9ecbd0fe4e582ed72 [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
Alex Savatieiev5118de02019-02-20 15:50:42 -06005
Alex359e5752021-08-16 17:28:30 -05006from cfg_checker.common.const import ENV_TYPE_GLOB, ENV_TYPE_SALT
7from cfg_checker.common.const import ENV_TYPE_KUBE, ENV_TYPE_LINUX, ENV_LOCAL
8from cfg_checker.common.const import supported_envs
9
Alex3bc95f62020-03-05 17:00:04 -060010from cfg_checker.common.exception import ConfigException
Alex3bc95f62020-03-05 17:00:04 -060011from cfg_checker.common.log import logger_cli
Alex3ebc5632019-04-18 16:47:18 -050012
Alex9a4ad212020-10-01 18:04:25 -050013from cfg_checker.common.other import utils, shell
14from cfg_checker.common.ssh_utils import ssh_shell_p
15
16from cfg_checker.clients import get_kube_remote
Alex Savatieiev5118de02019-02-20 15:50:42 -060017
18pkg_dir = os.path.dirname(__file__)
19pkg_dir = os.path.join(pkg_dir, os.pardir, os.pardir)
20pkg_dir = os.path.normpath(pkg_dir)
21pkg_dir = os.path.abspath(pkg_dir)
22
23_default_work_folder = os.path.normpath(pkg_dir)
24
Alex9a4ad212020-10-01 18:04:25 -050025
26def _extract_salt_return(_raw):
27 if not isinstance(_raw, str):
28 _json = _raw
29 logger_cli.debug("...ambigious return detected")
30 else:
31 try:
32 _json = json.loads(_raw)
33 except ValueError:
34 _json = _raw
35 logger_cli.debug(
36 "...return value is not a json: '{}'".format(_raw)
37 )
38
39 return _json
40
Alex Savatieiev5118de02019-02-20 15:50:42 -060041
42class CheckerConfiguration(object):
Alexe9908f72020-05-19 16:04:53 -050043 @staticmethod
Alex Savatieiev9df93a92019-02-27 17:40:16 -060044 def load_nodes_list():
Alexe9908f72020-05-19 16:04:53 -050045 _file = os.environ.get('SALT_NODE_LIST_FILE', None)
46 if _file:
47 _v, _ = utils.get_nodes_list(
48 os.path.join(pkg_dir, _file),
49 env_sting=os.environ.get('CFG_ALL_NODES', None)
50 )
51 return _v
52 else:
53 return None
Alex Savatieiev9df93a92019-02-27 17:40:16 -060054
Alex9a4ad212020-10-01 18:04:25 -050055 def _detect(self, _type):
56 logger_cli.debug("...detecting '{}'".format(_type))
57 if _type is None:
58 raise ConfigException("# Unexpected supported env type")
59 elif _type == ENV_TYPE_SALT:
60 # Detect salt env
61 _detect_cmd = ["curl", "-s"]
62 _detect_cmd.append(
63 "http://" + self.mcp_host + ':' + self.salt_port
64 )
65 # Try to call salt API on target host
66 _r = None
67 logger_cli.debug("...trying to detect env type '{}'".format(_type))
68 if self.env_name == ENV_LOCAL:
69 _r = shell(" ".join(_detect_cmd))
70 else:
71 _r = ssh_shell_p(
72 " ".join(_detect_cmd),
73 self.ssh_host,
74 username=self.ssh_user,
75 keypath=self.ssh_key,
76 piped=False,
77 use_sudo=self.ssh_uses_sudo,
78 silent=True
79 )
80 # Parse return
81 _r = _extract_salt_return(_r)
82
83 if len(_r) < 1:
84 return False
85 elif _r["return"] == "Welcome":
86 return True
87 else:
88 return False
89 elif _type == ENV_TYPE_KUBE:
90 _kube = get_kube_remote(self)
Alex359e5752021-08-16 17:28:30 -050091 if not _kube.initialized:
92 logger_cli.debug(
93 "... failed to load config from '{}'".format(
94 _kube.kConfigPath
95 )
96 )
97 return False
98 else:
99 logger_cli.debug(
100 "... config loaded from '{}'".format(
101 _kube.kConfigPath
102 )
103 )
Alex9a4ad212020-10-01 18:04:25 -0500104 try:
105 _vApi = _kube.get_versions_api()
106 _v = _vApi.get_code()
107 if hasattr(_v, "platform") and \
108 hasattr(_v, "major") and \
109 hasattr(_v, "minor"):
110 _host = "localhost" if _kube.is_local else _kube.kConf.host
111 logger_cli.info(
112 "# Kube server found: {}:{} on '{}'".format(
113 _v.major,
114 _v.minor,
115 _host
116 )
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()
133 logger_cli.debug("...running on {} {}".format(_s, _r))
134 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:
166 if _key.startswith(ENV_TYPE_GLOB):
167 os.environ[_key] = _value
168 elif _key.startswith(ENV_TYPE_SALT):
169 self.salt_vars.append([_key, _value])
170 elif _key.startswith(ENV_TYPE_KUBE):
171 self.kube_vars.append([_key, _value])
172 else:
173 logger_cli.warn(
174 "Unsupported config variable: '{}={}'".format(
175 _key,
176 _value
177 )
178 )
Alex Savatieiev5118de02019-02-20 15:50:42 -0600179 self.name = "CheckerConfig"
180 self.working_folder = os.environ.get(
181 'CFG_TESTS_WORK_DIR',
182 _default_work_folder
183 )
184 self.date_format = "%Y-%m-%d %H:%M:%S.%f%z"
185 self.default_tz = "UTC"
186
Alex41485522019-04-12 17:26:18 -0500187 self.pkg_versions_map = 'versions_map.csv'
188
Alex359e5752021-08-16 17:28:30 -0500189 # self.ssh_uses_sudo = False
Alex9a4ad212020-10-01 18:04:25 -0500190 self.ssh_key = os.environ.get('MCP_SSH_KEY', None)
191 self.ssh_user = os.environ.get('MCP_SSH_USER', None)
192 self.ssh_host = os.environ.get('MCP_SSH_HOST', None)
Alex Savatieiev63576832019-02-27 15:46:26 -0600193
Alex9a4ad212020-10-01 18:04:25 -0500194 self.mcp_host = os.environ.get('MCP_ENV_HOST', None)
195 self.salt_port = os.environ.get('MCP_SALT_PORT', '6969')
196 self.threads = int(os.environ.get('MCP_THREADS', "5"))
Alex Savatieiev9df93a92019-02-27 17:40:16 -0600197
Alex Savatieiev5118de02019-02-20 15:50:42 -0600198 self.skip_nodes = utils.node_string_to_list(os.environ.get(
199 'CFG_SKIP_NODES',
200 None
201 ))
Alex9a4ad212020-10-01 18:04:25 -0500202 # prebuild user data and folder path
203 self.pw_user = pwd.getpwuid(os.getuid())
204 if self.env_name == "local":
205 pass
206 else:
207 if not self.ssh_key and not self.force_no_key:
208 raise ConfigException(
209 "Please, supply a key for the cluster's master node. "
210 "Use MCP_SSH_KEY, see 'etc/example.env'"
211 )
212
213 def _init_env_values(self):
214 if ENV_TYPE_SALT in self.detected_envs:
215 for _key, _value in self.salt_vars:
216 os.environ[_key] = _value
217
218 self.salt_user = os.environ.get('SALT_USER', 'salt')
219 self.salt_timeout = os.environ.get('SALT_TIMEOUT', 30)
220 self.salt_file_root = os.environ.get('SALT_FILE_ROOT', None)
221 self.salt_scripts_folder = os.environ.get(
222 'SALT_SCRIPTS_FOLDER',
223 'cfg_checker_scripts'
224 )
225 elif ENV_TYPE_KUBE in self.detected_envs:
226 for _key, _value in self.kube_vars:
227 os.environ[_key] = _value
228
229 self.kube_config_root = os.environ.get('KUBE_CONFIG_ROOT', None)
230 self.kube_scripts_folder = os.environ.get(
231 'KUBE_SCRIPTS_FOLDER',
232 None
233 )
234 self.kube_node_user = os.environ.get(
235 'KUBE_NODE_USER',
236 'ubuntu'
237 )
238 self.kube_node_keypath = os.environ.get(
239 'KUBE_NODE_KEYPATH',
240 None
241 )
242 # Warn user only if Kube env is detected locally
Alexccb72e02021-01-20 16:38:03 -0600243 if self.env_name == ENV_LOCAL:
Alex9a4ad212020-10-01 18:04:25 -0500244 if not os.path.exists(self.kube_config_path):
245 logger_cli.warn(
246 "Kube config path not found on local env: '{}'".format(
247 self.kube_config_path
248 )
249 )
250 # On local envs, KUBE_NODE_KEYPATH is mandatory and is
251 # provided to give cfg-checker access to kube nodes
252 if not self.kube_node_keypath and not self.force_no_key:
253 raise ConfigException(
254 "Please, supply a key for the cluster nodes. "
255 "Use KUBE_NODE_KEYPATH, see 'etc/example.env'. "
256 "Consider checking KUBE_NODE_USER as well"
257 )
Alex36308942020-11-09 17:13:38 -0600258 self.kube_node_homepath = os.path.join(
259 '/home',
260 self.kube_node_user
261 )
Alex9a4ad212020-10-01 18:04:25 -0500262 else:
Alex205546c2020-12-30 19:22:30 -0600263 # Init key for nodes
264 # KUBE_NODE_KEYPATH is provided in case of node keys would be
265 # different to master node key, which is supplied
Alex9a4ad212020-10-01 18:04:25 -0500266 # using MCP_SSH_KEY (mandatory) and, for the most cases,
267 # should be the same for remote envs
268 if not self.kube_node_keypath and not self.force_no_key:
269 logger_cli.debug(
270 "... using MCP_SSH_KEY as node keys. "
271 "Supply KUBE_NODE_KEYPATH to update."
272 )
273 self.kube_node_keypath = self.ssh_key
Alex36308942020-11-09 17:13:38 -0600274 self.kube_node_homepath = self.homepath
Alex Savatieiev5118de02019-02-20 15:50:42 -0600275
Alexccb72e02021-01-20 16:38:03 -0600276 def _init_env(self, config_path, env_name=None):
Alex Savatieiev63576832019-02-27 15:46:26 -0600277 """Inits the environment vars from the env file
278 Uses simple validation for the values and names
Alex Savatieiev5118de02019-02-20 15:50:42 -0600279
280 Keyword Arguments:
281 env_name {str} -- environment name to search configuration
282 files in etc/<env_name>.env (default: {None})
Alex9a4ad212020-10-01 18:04:25 -0500283 env_type {str} -- environment type to use: salt/kube
Alex Savatieiev5118de02019-02-20 15:50:42 -0600284
285 Raises:
286 ConfigException -- on IO error when loading env file
287 ConfigException -- on env file failed validation
288 """
289 # load env file as init os.environment with its values
Alexccb72e02021-01-20 16:38:03 -0600290 if os.path.isfile(config_path):
291 with open(config_path) as _f:
Alex Savatieiev5118de02019-02-20 15:50:42 -0600292 _list = _f.read().splitlines()
Alex3ebc5632019-04-18 16:47:18 -0500293 logger_cli.info(
294 "# Loading env vars from '{}'".format(
Alexccb72e02021-01-20 16:38:03 -0600295 config_path
Alex3ebc5632019-04-18 16:47:18 -0500296 )
297 )
Alex Savatieiev5118de02019-02-20 15:50:42 -0600298 else:
299 raise ConfigException(
Alex Savatieievf808cd22019-03-01 13:17:59 -0600300 "# Failed to load enviroment vars from '{}'".format(
Alexccb72e02021-01-20 16:38:03 -0600301 config_path
Alex Savatieiev5118de02019-02-20 15:50:42 -0600302 )
303 )
Alex9a4ad212020-10-01 18:04:25 -0500304 self.vars = []
Alex Savatieiev5118de02019-02-20 15:50:42 -0600305 for index in range(len(_list)):
306 _line = _list[index]
307 # skip comments
308 if _line.strip().startswith('#'):
309 continue
310 # validate
311 _errors = []
Alex9a4ad212020-10-01 18:04:25 -0500312 if len(_line) < 1:
313 _errors.append("Line {}: empty".format(index))
314 elif _line.find('=') < 0 or _line.count('=') > 1:
Alex Savatieiev5118de02019-02-20 15:50:42 -0600315 _errors.append("Line {}: {}".format(index, _line))
316 else:
317 # save values
318 _t = _line.split('=')
Alex9a4ad212020-10-01 18:04:25 -0500319 self.vars.append([_t[0], _t[1]])
Alex Savatieiev5118de02019-02-20 15:50:42 -0600320 # if there was errors, report them
321 if _errors:
322 raise ConfigException(
Alex Savatieievf808cd22019-03-01 13:17:59 -0600323 "# Environment file failed validation in lines: {}".format(
Alex Savatieiev5118de02019-02-20 15:50:42 -0600324 "\n".join(_errors)
325 )
326 )
327 else:
Alex3ebc5632019-04-18 16:47:18 -0500328 logger_cli.debug(
329 "-> ...loaded total of '{}' vars".format(
330 len(_list)
331 )
332 )
Alexccb72e02021-01-20 16:38:03 -0600333 self.env_name = env_name
Alex Savatieiev5118de02019-02-20 15:50:42 -0600334
Alex9a4ad212020-10-01 18:04:25 -0500335 def __init__(self, args):
Alex Savatieiev5118de02019-02-20 15:50:42 -0600336 """Base configuration class. Only values that are common for all scripts
337 """
Alex9a4ad212020-10-01 18:04:25 -0500338 self.ssh_uses_sudo = args.sudo
Alexeffa0682021-06-04 12:18:33 -0500339 self.ssh_direct = args.ssh_direct
Alexccb72e02021-01-20 16:38:03 -0600340 self.kube_config_path = args.kube_config
Alex9a4ad212020-10-01 18:04:25 -0500341 self.debug = args.debug
Alex33747812021-04-07 10:11:39 -0500342 self.insecure = args.insecure
Alex9a4ad212020-10-01 18:04:25 -0500343 self.force_no_key = args.force_no_key
Alexe9908f72020-05-19 16:04:53 -0500344 # Make sure we running on Python 3
345 if sys.version_info[0] < 3 and sys.version_info[1] < 5:
346 logger_cli.error("# ERROR: Python 3.5+ is required")
347 sys.exit(1)
348 else:
349 logger_cli.debug("### Python version is {}.{}".format(
350 sys.version_info[0],
351 sys.version_info[1]
352 ))
353
Alexccb72e02021-01-20 16:38:03 -0600354 # if env name is default, check var too
355 if args.env_name == ENV_LOCAL:
356 _env = os.getenv('MCP_ENV', None)
357 _env = _env if _env else args.env_name
Alex359e5752021-08-16 17:28:30 -0500358 _env_config_path = os.path.join(pkg_dir, 'etc', _env + '.env')
Alexccb72e02021-01-20 16:38:03 -0600359 else:
360 _env = args.env_name
Alex359e5752021-08-16 17:28:30 -0500361 _env_config_path = args.env_config
Alex9a4ad212020-10-01 18:04:25 -0500362
363 # Init environment variables from file, validate
Alex359e5752021-08-16 17:28:30 -0500364 self._init_env(_env_config_path, env_name=_env)
Alex9a4ad212020-10-01 18:04:25 -0500365 # Load Common vars for any type of the env
366 self._init_mcp_values()
367 # Detect env types present
368 self._detect_types()
369 # handle forced env type var
370 _forced_type = os.getenv('MCP_TYPE_FORCE', None)
371 if _forced_type in supported_envs:
372 self.detected_envs.append(_forced_type)
373 elif _forced_type is not None:
374 logger_cli.warn(
375 "Unsupported forced type of '{}'".format(
376 _forced_type
377 )
378 )
379 # Check if any of the envs detected
380 if len(self.detected_envs) < 1:
381 if _env is None:
382 raise ConfigException("No environment types detected locally")
383 else:
384 raise ConfigException(
385 "No environment types detected at '{}'".format(
386 self.mcp_host
387 )
388 )
Alex9a4ad212020-10-01 18:04:25 -0500389 # initialize path to folders
Alexccb72e02021-01-20 16:38:03 -0600390 if self.env_name == ENV_LOCAL:
Alex9a4ad212020-10-01 18:04:25 -0500391 # names and folders
392 self.user = self.pw_user.pw_name
393 self.homepath = self.pw_user.pw_dir
Alex9a4ad212020-10-01 18:04:25 -0500394 else:
395 # names and folders in case of remote env
396 self.user = self.ssh_user
397 self.homepath = os.path.join('/home', self.ssh_user)
Alexccb72e02021-01-20 16:38:03 -0600398
399 # Init vars that is specific to detected envs only
400 self._init_env_values()