blob: b5a0406d73a240aafac485f00d9499c9826c46d0 [file] [log] [blame]
Alex9a4ad212020-10-01 18:04:25 -05001import functools
savex4448e132018-04-25 15:51:14 +02002import os
3import re
Alex Savatieiev63576832019-02-27 15:46:26 -06004import subprocess
savex4448e132018-04-25 15:51:14 +02005
Alex9a4ad212020-10-01 18:04:25 -05006from cfg_checker.common import logger_cli
7from cfg_checker.common.const import all_salt_roles_map, uknown_code, \
8 truth
Alex Savatieiev5118de02019-02-20 15:50:42 -06009from cfg_checker.common.exception import ConfigException
savex4448e132018-04-25 15:51:14 +020010
Alex Savatieiev5118de02019-02-20 15:50:42 -060011pkg_dir = os.path.dirname(__file__)
12pkg_dir = os.path.join(pkg_dir, os.pardir, os.pardir)
13pkg_dir = os.path.normpath(pkg_dir)
14pkg_dir = os.path.abspath(pkg_dir)
savex4448e132018-04-25 15:51:14 +020015
16
Alex9a4ad212020-10-01 18:04:25 -050017# 'Dirty' and simple way to execute piped cmds
Alexb78191f2021-11-02 16:35:46 -050018def piped_shell(command, code=False):
Alexc4f59622021-08-27 13:42:00 -050019 logger_cli.debug("... cmd:'{}'".format(command))
Alex9a4ad212020-10-01 18:04:25 -050020 _code, _out = subprocess.getstatusoutput(command)
21 if _code:
22 logger_cli.error("Non-zero return code: {}, '{}'".format(_code, _out))
Alexb78191f2021-11-02 16:35:46 -050023 if code:
24 return _code, _out
25 else:
26 return _out
Alex9a4ad212020-10-01 18:04:25 -050027
28
29# 'Proper way to execute shell
Alex Savatieiev63576832019-02-27 15:46:26 -060030def shell(command):
Alexc4f59622021-08-27 13:42:00 -050031 logger_cli.debug("... cmd:'{}'".format(command))
Alex Savatieiev63576832019-02-27 15:46:26 -060032 _ps = subprocess.Popen(
33 command.split(),
Alex9a4ad212020-10-01 18:04:25 -050034 stdout=subprocess.PIPE,
35 universal_newlines=False
Alex Savatieiev63576832019-02-27 15:46:26 -060036 ).communicate()[0].decode()
37
38 return _ps
39
40
Alex26b8a8c2019-10-09 17:09:07 -050041def merge_dict(source, destination):
42 """
43 Dict merger, thanks to vincent
44 http://stackoverflow.com/questions/20656135/python-deep-merge-dictionary-data
45
46 >>> a = { 'first' : { 'all_rows' : { 'pass' : 'dog', 'number' : '1' } } }
47 >>> b = { 'first' : { 'all_rows' : { 'fail' : 'cat', 'number' : '5' } } }
48 >>> merge(b, a) == {
49 'first': {
50 'all_rows': {
51 'pass': 'dog',
52 'fail': 'cat',
53 'number': '5'
54 }
55 }
56 }
57 True
58 """
59 for key, value in source.items():
60 if isinstance(value, dict):
61 # get node or create one
62 node = destination.setdefault(key, {})
63 merge_dict(value, node)
64 else:
65 destination[key] = value
66
67 return destination
68
69
savex4448e132018-04-25 15:51:14 +020070class Utils(object):
71 @staticmethod
72 def validate_name(fqdn, message=False):
73 """
74 Function that tries to validate node name.
75 Checks if code contains letters, has '.' in it,
76 roles map contains code's role
77
78 :param fqdn: node FQDN name to supply for the check
79 :param message: True if validate should return error check message
80 :return: False if checks failed, True if all checks passed
81 """
Alex Savatieievf808cd22019-03-01 13:17:59 -060082 _message = "# Validation passed"
savex4448e132018-04-25 15:51:14 +020083
84 def _result():
85 return (True, _message) if message else True
86
87 # node role code checks
Alexd0391d42019-05-21 18:48:55 -050088 _code = re.findall(r"[a-zA-Z]+", fqdn.split('.')[0])
savex4448e132018-04-25 15:51:14 +020089 if len(_code) > 0:
Alex9a4ad212020-10-01 18:04:25 -050090 if _code[0] in all_salt_roles_map:
savex4448e132018-04-25 15:51:14 +020091 return _result()
92 else:
93 # log warning here
Alex Savatieievf808cd22019-03-01 13:17:59 -060094 _message = "# Node code is unknown, '{}'. " \
95 "# Please, update map".format(_code)
savex4448e132018-04-25 15:51:14 +020096 else:
97 # log warning here
Alex Savatieievf808cd22019-03-01 13:17:59 -060098 _message = "# Node name is invalid, '{}'".format(fqdn)
savex4448e132018-04-25 15:51:14 +020099
100 # put other checks here
101
102 # output result
103 return _result()
104
105 @staticmethod
106 def node_string_to_list(node_string):
107 # supplied var should contain node list
108 # if there is no ',' -> use space as a delimiter
109 if node_string is not None:
110 if node_string.find(',') < 0:
111 return node_string.split(' ')
112 else:
113 return node_string.split(',')
114 else:
115 return []
116
117 def get_node_code(self, fqdn):
118 # validate
119 _isvalid, _message = self.validate_name(fqdn, message=True)
Alex2e213b22019-12-05 10:40:29 -0600120 _code = re.findall(
121 r"[a-zA-Z]+?(?=(?:[0-9]+$)|(?:\-+?)|(?:\_+?)|$)",
122 fqdn.split('.')[0]
123 )
savex4448e132018-04-25 15:51:14 +0200124 # check if it is valid and raise if not
125 if _isvalid:
Alexe0c5b9e2019-04-23 18:51:23 -0500126 # try to match it with ones in map
127 _c = _code[0]
Alex9a4ad212020-10-01 18:04:25 -0500128 match = any([r in _c for r in all_salt_roles_map.keys()])
Alexe0c5b9e2019-04-23 18:51:23 -0500129 if match:
130 # no match, try to find it
131 match = False
Alex9a4ad212020-10-01 18:04:25 -0500132 for r in all_salt_roles_map.keys():
Alexe0c5b9e2019-04-23 18:51:23 -0500133 _idx = _c.find(r)
134 if _idx > -1:
135 _c = _c[_idx:]
136 match = True
137 break
138 if match:
139 return _c
140 else:
141 return uknown_code
142 else:
143 return uknown_code
savex4448e132018-04-25 15:51:14 +0200144 else:
145 raise ConfigException(_message)
146
Alexe9908f72020-05-19 16:04:53 -0500147 def get_nodes_list(self, nodes_list, env_sting=None):
savex4448e132018-04-25 15:51:14 +0200148 _list = []
Alexe9908f72020-05-19 16:04:53 -0500149 if env_sting is None:
savex4448e132018-04-25 15:51:14 +0200150 # nothing supplied, use the one in repo
151 try:
Alex Savatieievd48994d2018-12-13 12:13:00 +0100152 if not nodes_list:
153 return []
Alexe9908f72020-05-19 16:04:53 -0500154 with open(nodes_list) as _f:
savex4448e132018-04-25 15:51:14 +0200155 _list.extend(_f.read().splitlines())
156 except IOError as e:
Alex Savatieievf808cd22019-03-01 13:17:59 -0600157 raise ConfigException("# Error while loading file, '{}': "
savex4448e132018-04-25 15:51:14 +0200158 "{}".format(e.filename, e.strerror))
159 else:
Alexe9908f72020-05-19 16:04:53 -0500160 _list.extend(self.node_string_to_list(env_sting))
savex4448e132018-04-25 15:51:14 +0200161
162 # validate names
163 _invalid = []
164 _valid = []
165 for idx in range(len(_list)):
166 _name = _list[idx]
167 if not self.validate_name(_name):
168 _invalid.append(_name)
169 else:
170 _valid.append(_name)
171
Alexe9908f72020-05-19 16:04:53 -0500172 return _valid, _invalid
savex4448e132018-04-25 15:51:14 +0200173
Alex9a4ad212020-10-01 18:04:25 -0500174 @staticmethod
175 def to_bool(value):
176 if value.lower() in truth:
177 return True
178 else:
179 return False
180
181 # helper functions to get nested attrs
182 # https://stackoverflow.com/questions/31174295/getattr-and-setattr-on-nested-subobjects-chained-properties
183 # using wonder's beautiful simplification:
184 # https://stackoverflow.com/questions/31174295/getattr-and-setattr-on-nested-objects/31174427?noredirect=1#comment86638618_31174427
185 def rsetattr(self, obj, attr, val):
186 pre, _, post = attr.rpartition('.')
187 return setattr(self.rgetattr(obj, pre) if pre else obj, post, val)
188
189 def rgetattr(self, obj, attr, *args):
190 def _getattr(obj, attr):
191 return getattr(obj, attr, *args)
192 return functools.reduce(_getattr, [obj] + attr.split('.'))
193
savex4448e132018-04-25 15:51:14 +0200194
195utils = Utils()