blob: 4b7b3f5d4dcc5adec11274d006073bee4f4d93ba [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
Alex3bc95f62020-03-05 17:00:04 -06006from cfg_checker.common.exception import ConfigException
Alex3bc95f62020-03-05 17:00:04 -06007from cfg_checker.common.log import logger_cli
Alex3ebc5632019-04-18 16:47:18 -05008
Alex9a4ad212020-10-01 18:04:25 -05009from cfg_checker.common.other import utils, shell
10from cfg_checker.common.ssh_utils import ssh_shell_p
11
12from cfg_checker.clients import get_kube_remote
Alex Savatieiev5118de02019-02-20 15:50:42 -060013
14pkg_dir = os.path.dirname(__file__)
15pkg_dir = os.path.join(pkg_dir, os.pardir, os.pardir)
16pkg_dir = os.path.normpath(pkg_dir)
17pkg_dir = os.path.abspath(pkg_dir)
18
19_default_work_folder = os.path.normpath(pkg_dir)
20
Alex9a4ad212020-10-01 18:04:25 -050021ENV_TYPE_GLOB = "MCP"
22ENV_TYPE_SALT = "SALT"
23ENV_TYPE_KUBE = "KUBE"
24ENV_TYPE_LINUX = "LINUX"
25
26ENV_LOCAL = "local"
27
28supported_envs = [ENV_TYPE_LINUX, ENV_TYPE_SALT, ENV_TYPE_KUBE]
29
30
31def _extract_salt_return(_raw):
32 if not isinstance(_raw, str):
33 _json = _raw
34 logger_cli.debug("...ambigious return detected")
35 else:
36 try:
37 _json = json.loads(_raw)
38 except ValueError:
39 _json = _raw
40 logger_cli.debug(
41 "...return value is not a json: '{}'".format(_raw)
42 )
43
44 return _json
45
Alex Savatieiev5118de02019-02-20 15:50:42 -060046
47class CheckerConfiguration(object):
Alexe9908f72020-05-19 16:04:53 -050048 @staticmethod
Alex Savatieiev9df93a92019-02-27 17:40:16 -060049 def load_nodes_list():
Alexe9908f72020-05-19 16:04:53 -050050 _file = os.environ.get('SALT_NODE_LIST_FILE', None)
51 if _file:
52 _v, _ = utils.get_nodes_list(
53 os.path.join(pkg_dir, _file),
54 env_sting=os.environ.get('CFG_ALL_NODES', None)
55 )
56 return _v
57 else:
58 return None
Alex Savatieiev9df93a92019-02-27 17:40:16 -060059
Alex9a4ad212020-10-01 18:04:25 -050060 def _detect(self, _type):
61 logger_cli.debug("...detecting '{}'".format(_type))
62 if _type is None:
63 raise ConfigException("# Unexpected supported env type")
64 elif _type == ENV_TYPE_SALT:
65 # Detect salt env
66 _detect_cmd = ["curl", "-s"]
67 _detect_cmd.append(
68 "http://" + self.mcp_host + ':' + self.salt_port
69 )
70 # Try to call salt API on target host
71 _r = None
72 logger_cli.debug("...trying to detect env type '{}'".format(_type))
73 if self.env_name == ENV_LOCAL:
74 _r = shell(" ".join(_detect_cmd))
75 else:
76 _r = ssh_shell_p(
77 " ".join(_detect_cmd),
78 self.ssh_host,
79 username=self.ssh_user,
80 keypath=self.ssh_key,
81 piped=False,
82 use_sudo=self.ssh_uses_sudo,
83 silent=True
84 )
85 # Parse return
86 _r = _extract_salt_return(_r)
87
88 if len(_r) < 1:
89 return False
90 elif _r["return"] == "Welcome":
91 return True
92 else:
93 return False
94 elif _type == ENV_TYPE_KUBE:
95 _kube = get_kube_remote(self)
96 try:
97 _vApi = _kube.get_versions_api()
98 _v = _vApi.get_code()
99 if hasattr(_v, "platform") and \
100 hasattr(_v, "major") and \
101 hasattr(_v, "minor"):
102 _host = "localhost" if _kube.is_local else _kube.kConf.host
103 logger_cli.info(
104 "# Kube server found: {}:{} on '{}'".format(
105 _v.major,
106 _v.minor,
107 _host
108 )
109 )
110 return True
111 else:
112 return False
113 except Exception as e:
114 logger_cli.warn(
115 "# Unexpected error finding Kube env: '{}' ".format(
116 str(e)
117 )
118 )
119 return False
120 elif _type == ENV_TYPE_LINUX:
121 # Detect Linux env
122 from platform import system, release
123 _s = system()
124 _r = release()
125 logger_cli.debug("...running on {} {}".format(_s, _r))
126 if _s in ['Linux', 'Darwin']:
127 return True
128 else:
129 return False
130 else:
131 raise ConfigException(
132 "# Env type of '{}' is not supported".format(
133 _type
134 )
135 )
136
137 def _detect_types(self):
138 """Try to detect env type based on the name
139 """
140 self.detected_envs = []
141 logger_cli.info('# Detecting env types')
142 for _env in supported_envs:
143 if self._detect(_env):
144 logger_cli.info("# '{}' found".format(_env))
145 self.detected_envs.append(_env)
146 else:
147 logger_cli.info("# '{}' not found".format(_env))
148
149 return
150
151 def _init_mcp_values(self):
Alex Savatieiev5118de02019-02-20 15:50:42 -0600152 """Load values from environment variables or put default ones
153 """
Alex9a4ad212020-10-01 18:04:25 -0500154 # filter vars and preload if needed
155 self.salt_vars = []
156 self.kube_vars = []
157 for _key, _value in self.vars:
158 if _key.startswith(ENV_TYPE_GLOB):
159 os.environ[_key] = _value
160 elif _key.startswith(ENV_TYPE_SALT):
161 self.salt_vars.append([_key, _value])
162 elif _key.startswith(ENV_TYPE_KUBE):
163 self.kube_vars.append([_key, _value])
164 else:
165 logger_cli.warn(
166 "Unsupported config variable: '{}={}'".format(
167 _key,
168 _value
169 )
170 )
Alex Savatieiev5118de02019-02-20 15:50:42 -0600171 self.name = "CheckerConfig"
172 self.working_folder = os.environ.get(
173 'CFG_TESTS_WORK_DIR',
174 _default_work_folder
175 )
176 self.date_format = "%Y-%m-%d %H:%M:%S.%f%z"
177 self.default_tz = "UTC"
178
Alex41485522019-04-12 17:26:18 -0500179 self.pkg_versions_map = 'versions_map.csv'
180
Alex Savatieiev63576832019-02-27 15:46:26 -0600181 self.ssh_uses_sudo = False
Alex9a4ad212020-10-01 18:04:25 -0500182 self.ssh_key = os.environ.get('MCP_SSH_KEY', None)
183 self.ssh_user = os.environ.get('MCP_SSH_USER', None)
184 self.ssh_host = os.environ.get('MCP_SSH_HOST', None)
Alex Savatieiev63576832019-02-27 15:46:26 -0600185
Alex9a4ad212020-10-01 18:04:25 -0500186 self.mcp_host = os.environ.get('MCP_ENV_HOST', None)
187 self.salt_port = os.environ.get('MCP_SALT_PORT', '6969')
188 self.threads = int(os.environ.get('MCP_THREADS', "5"))
Alex Savatieiev9df93a92019-02-27 17:40:16 -0600189
Alex Savatieiev5118de02019-02-20 15:50:42 -0600190 self.skip_nodes = utils.node_string_to_list(os.environ.get(
191 'CFG_SKIP_NODES',
192 None
193 ))
Alex9a4ad212020-10-01 18:04:25 -0500194 # prebuild user data and folder path
195 self.pw_user = pwd.getpwuid(os.getuid())
196 if self.env_name == "local":
197 pass
198 else:
199 if not self.ssh_key and not self.force_no_key:
200 raise ConfigException(
201 "Please, supply a key for the cluster's master node. "
202 "Use MCP_SSH_KEY, see 'etc/example.env'"
203 )
204
205 def _init_env_values(self):
206 if ENV_TYPE_SALT in self.detected_envs:
207 for _key, _value in self.salt_vars:
208 os.environ[_key] = _value
209
210 self.salt_user = os.environ.get('SALT_USER', 'salt')
211 self.salt_timeout = os.environ.get('SALT_TIMEOUT', 30)
212 self.salt_file_root = os.environ.get('SALT_FILE_ROOT', None)
213 self.salt_scripts_folder = os.environ.get(
214 'SALT_SCRIPTS_FOLDER',
215 'cfg_checker_scripts'
216 )
217 elif ENV_TYPE_KUBE in self.detected_envs:
218 for _key, _value in self.kube_vars:
219 os.environ[_key] = _value
220
221 self.kube_config_root = os.environ.get('KUBE_CONFIG_ROOT', None)
222 self.kube_scripts_folder = os.environ.get(
223 'KUBE_SCRIPTS_FOLDER',
224 None
225 )
226 self.kube_node_user = os.environ.get(
227 'KUBE_NODE_USER',
228 'ubuntu'
229 )
230 self.kube_node_keypath = os.environ.get(
231 'KUBE_NODE_KEYPATH',
232 None
233 )
234 # Warn user only if Kube env is detected locally
Alexccb72e02021-01-20 16:38:03 -0600235 if self.env_name == ENV_LOCAL:
Alex9a4ad212020-10-01 18:04:25 -0500236 if not os.path.exists(self.kube_config_path):
237 logger_cli.warn(
238 "Kube config path not found on local env: '{}'".format(
239 self.kube_config_path
240 )
241 )
242 # On local envs, KUBE_NODE_KEYPATH is mandatory and is
243 # provided to give cfg-checker access to kube nodes
244 if not self.kube_node_keypath and not self.force_no_key:
245 raise ConfigException(
246 "Please, supply a key for the cluster nodes. "
247 "Use KUBE_NODE_KEYPATH, see 'etc/example.env'. "
248 "Consider checking KUBE_NODE_USER as well"
249 )
Alex36308942020-11-09 17:13:38 -0600250 self.kube_node_homepath = os.path.join(
251 '/home',
252 self.kube_node_user
253 )
Alex9a4ad212020-10-01 18:04:25 -0500254 else:
Alex205546c2020-12-30 19:22:30 -0600255 # Init key for nodes
256 # KUBE_NODE_KEYPATH is provided in case of node keys would be
257 # different to master node key, which is supplied
Alex9a4ad212020-10-01 18:04:25 -0500258 # using MCP_SSH_KEY (mandatory) and, for the most cases,
259 # should be the same for remote envs
260 if not self.kube_node_keypath and not self.force_no_key:
261 logger_cli.debug(
262 "... using MCP_SSH_KEY as node keys. "
263 "Supply KUBE_NODE_KEYPATH to update."
264 )
265 self.kube_node_keypath = self.ssh_key
Alex36308942020-11-09 17:13:38 -0600266 self.kube_node_homepath = self.homepath
Alex Savatieiev5118de02019-02-20 15:50:42 -0600267
Alexccb72e02021-01-20 16:38:03 -0600268 def _init_env(self, config_path, env_name=None):
Alex Savatieiev63576832019-02-27 15:46:26 -0600269 """Inits the environment vars from the env file
270 Uses simple validation for the values and names
Alex Savatieiev5118de02019-02-20 15:50:42 -0600271
272 Keyword Arguments:
273 env_name {str} -- environment name to search configuration
274 files in etc/<env_name>.env (default: {None})
Alex9a4ad212020-10-01 18:04:25 -0500275 env_type {str} -- environment type to use: salt/kube
Alex Savatieiev5118de02019-02-20 15:50:42 -0600276
277 Raises:
278 ConfigException -- on IO error when loading env file
279 ConfigException -- on env file failed validation
280 """
281 # load env file as init os.environment with its values
Alexccb72e02021-01-20 16:38:03 -0600282 if os.path.isfile(config_path):
283 with open(config_path) as _f:
Alex Savatieiev5118de02019-02-20 15:50:42 -0600284 _list = _f.read().splitlines()
Alex3ebc5632019-04-18 16:47:18 -0500285 logger_cli.info(
286 "# Loading env vars from '{}'".format(
Alexccb72e02021-01-20 16:38:03 -0600287 config_path
Alex3ebc5632019-04-18 16:47:18 -0500288 )
289 )
Alex Savatieiev5118de02019-02-20 15:50:42 -0600290 else:
291 raise ConfigException(
Alex Savatieievf808cd22019-03-01 13:17:59 -0600292 "# Failed to load enviroment vars from '{}'".format(
Alexccb72e02021-01-20 16:38:03 -0600293 config_path
Alex Savatieiev5118de02019-02-20 15:50:42 -0600294 )
295 )
Alex9a4ad212020-10-01 18:04:25 -0500296 self.vars = []
Alex Savatieiev5118de02019-02-20 15:50:42 -0600297 for index in range(len(_list)):
298 _line = _list[index]
299 # skip comments
300 if _line.strip().startswith('#'):
301 continue
302 # validate
303 _errors = []
Alex9a4ad212020-10-01 18:04:25 -0500304 if len(_line) < 1:
305 _errors.append("Line {}: empty".format(index))
306 elif _line.find('=') < 0 or _line.count('=') > 1:
Alex Savatieiev5118de02019-02-20 15:50:42 -0600307 _errors.append("Line {}: {}".format(index, _line))
308 else:
309 # save values
310 _t = _line.split('=')
Alex9a4ad212020-10-01 18:04:25 -0500311 self.vars.append([_t[0], _t[1]])
Alex Savatieiev5118de02019-02-20 15:50:42 -0600312 # if there was errors, report them
313 if _errors:
314 raise ConfigException(
Alex Savatieievf808cd22019-03-01 13:17:59 -0600315 "# Environment file failed validation in lines: {}".format(
Alex Savatieiev5118de02019-02-20 15:50:42 -0600316 "\n".join(_errors)
317 )
318 )
319 else:
Alex3ebc5632019-04-18 16:47:18 -0500320 logger_cli.debug(
321 "-> ...loaded total of '{}' vars".format(
322 len(_list)
323 )
324 )
Alexccb72e02021-01-20 16:38:03 -0600325 self.env_name = env_name
Alex Savatieiev5118de02019-02-20 15:50:42 -0600326
Alex9a4ad212020-10-01 18:04:25 -0500327 def __init__(self, args):
Alex Savatieiev5118de02019-02-20 15:50:42 -0600328 """Base configuration class. Only values that are common for all scripts
329 """
Alex9a4ad212020-10-01 18:04:25 -0500330 self.ssh_uses_sudo = args.sudo
Alexccb72e02021-01-20 16:38:03 -0600331 self.kube_config_path = args.kube_config
Alex9a4ad212020-10-01 18:04:25 -0500332 self.debug = args.debug
Alex33747812021-04-07 10:11:39 -0500333 self.insecure = args.insecure
Alex9a4ad212020-10-01 18:04:25 -0500334 self.force_no_key = args.force_no_key
Alexe9908f72020-05-19 16:04:53 -0500335 # Make sure we running on Python 3
336 if sys.version_info[0] < 3 and sys.version_info[1] < 5:
337 logger_cli.error("# ERROR: Python 3.5+ is required")
338 sys.exit(1)
339 else:
340 logger_cli.debug("### Python version is {}.{}".format(
341 sys.version_info[0],
342 sys.version_info[1]
343 ))
344
Alexccb72e02021-01-20 16:38:03 -0600345 # if env name is default, check var too
346 if args.env_name == ENV_LOCAL:
347 _env = os.getenv('MCP_ENV', None)
348 _env = _env if _env else args.env_name
349 else:
350 _env = args.env_name
Alex9a4ad212020-10-01 18:04:25 -0500351
352 # Init environment variables from file, validate
Alexccb72e02021-01-20 16:38:03 -0600353 self._init_env(args.env_config, env_name=_env)
Alex9a4ad212020-10-01 18:04:25 -0500354 # Load Common vars for any type of the env
355 self._init_mcp_values()
356 # Detect env types present
357 self._detect_types()
358 # handle forced env type var
359 _forced_type = os.getenv('MCP_TYPE_FORCE', None)
360 if _forced_type in supported_envs:
361 self.detected_envs.append(_forced_type)
362 elif _forced_type is not None:
363 logger_cli.warn(
364 "Unsupported forced type of '{}'".format(
365 _forced_type
366 )
367 )
368 # Check if any of the envs detected
369 if len(self.detected_envs) < 1:
370 if _env is None:
371 raise ConfigException("No environment types detected locally")
372 else:
373 raise ConfigException(
374 "No environment types detected at '{}'".format(
375 self.mcp_host
376 )
377 )
Alex9a4ad212020-10-01 18:04:25 -0500378 # initialize path to folders
Alexccb72e02021-01-20 16:38:03 -0600379 if self.env_name == ENV_LOCAL:
Alex9a4ad212020-10-01 18:04:25 -0500380 # names and folders
381 self.user = self.pw_user.pw_name
382 self.homepath = self.pw_user.pw_dir
Alex9a4ad212020-10-01 18:04:25 -0500383 else:
384 # names and folders in case of remote env
385 self.user = self.ssh_user
386 self.homepath = os.path.join('/home', self.ssh_user)
Alexccb72e02021-01-20 16:38:03 -0600387
388 # Init vars that is specific to detected envs only
389 self._init_env_values()