blob: 3d0cc1329a47087c4802bcaf4c8d01f01c3f6268 [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
18def piped_shell(command):
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))
23 return _out
24
25
26# 'Proper way to execute shell
Alex Savatieiev63576832019-02-27 15:46:26 -060027def shell(command):
Alexc4f59622021-08-27 13:42:00 -050028 logger_cli.debug("... cmd:'{}'".format(command))
Alex Savatieiev63576832019-02-27 15:46:26 -060029 _ps = subprocess.Popen(
30 command.split(),
Alex9a4ad212020-10-01 18:04:25 -050031 stdout=subprocess.PIPE,
32 universal_newlines=False
Alex Savatieiev63576832019-02-27 15:46:26 -060033 ).communicate()[0].decode()
34
35 return _ps
36
37
Alex26b8a8c2019-10-09 17:09:07 -050038def merge_dict(source, destination):
39 """
40 Dict merger, thanks to vincent
41 http://stackoverflow.com/questions/20656135/python-deep-merge-dictionary-data
42
43 >>> a = { 'first' : { 'all_rows' : { 'pass' : 'dog', 'number' : '1' } } }
44 >>> b = { 'first' : { 'all_rows' : { 'fail' : 'cat', 'number' : '5' } } }
45 >>> merge(b, a) == {
46 'first': {
47 'all_rows': {
48 'pass': 'dog',
49 'fail': 'cat',
50 'number': '5'
51 }
52 }
53 }
54 True
55 """
56 for key, value in source.items():
57 if isinstance(value, dict):
58 # get node or create one
59 node = destination.setdefault(key, {})
60 merge_dict(value, node)
61 else:
62 destination[key] = value
63
64 return destination
65
66
savex4448e132018-04-25 15:51:14 +020067class Utils(object):
68 @staticmethod
69 def validate_name(fqdn, message=False):
70 """
71 Function that tries to validate node name.
72 Checks if code contains letters, has '.' in it,
73 roles map contains code's role
74
75 :param fqdn: node FQDN name to supply for the check
76 :param message: True if validate should return error check message
77 :return: False if checks failed, True if all checks passed
78 """
Alex Savatieievf808cd22019-03-01 13:17:59 -060079 _message = "# Validation passed"
savex4448e132018-04-25 15:51:14 +020080
81 def _result():
82 return (True, _message) if message else True
83
84 # node role code checks
Alexd0391d42019-05-21 18:48:55 -050085 _code = re.findall(r"[a-zA-Z]+", fqdn.split('.')[0])
savex4448e132018-04-25 15:51:14 +020086 if len(_code) > 0:
Alex9a4ad212020-10-01 18:04:25 -050087 if _code[0] in all_salt_roles_map:
savex4448e132018-04-25 15:51:14 +020088 return _result()
89 else:
90 # log warning here
Alex Savatieievf808cd22019-03-01 13:17:59 -060091 _message = "# Node code is unknown, '{}'. " \
92 "# Please, update map".format(_code)
savex4448e132018-04-25 15:51:14 +020093 else:
94 # log warning here
Alex Savatieievf808cd22019-03-01 13:17:59 -060095 _message = "# Node name is invalid, '{}'".format(fqdn)
savex4448e132018-04-25 15:51:14 +020096
97 # put other checks here
98
99 # output result
100 return _result()
101
102 @staticmethod
103 def node_string_to_list(node_string):
104 # supplied var should contain node list
105 # if there is no ',' -> use space as a delimiter
106 if node_string is not None:
107 if node_string.find(',') < 0:
108 return node_string.split(' ')
109 else:
110 return node_string.split(',')
111 else:
112 return []
113
114 def get_node_code(self, fqdn):
115 # validate
116 _isvalid, _message = self.validate_name(fqdn, message=True)
Alex2e213b22019-12-05 10:40:29 -0600117 _code = re.findall(
118 r"[a-zA-Z]+?(?=(?:[0-9]+$)|(?:\-+?)|(?:\_+?)|$)",
119 fqdn.split('.')[0]
120 )
savex4448e132018-04-25 15:51:14 +0200121 # check if it is valid and raise if not
122 if _isvalid:
Alexe0c5b9e2019-04-23 18:51:23 -0500123 # try to match it with ones in map
124 _c = _code[0]
Alex9a4ad212020-10-01 18:04:25 -0500125 match = any([r in _c for r in all_salt_roles_map.keys()])
Alexe0c5b9e2019-04-23 18:51:23 -0500126 if match:
127 # no match, try to find it
128 match = False
Alex9a4ad212020-10-01 18:04:25 -0500129 for r in all_salt_roles_map.keys():
Alexe0c5b9e2019-04-23 18:51:23 -0500130 _idx = _c.find(r)
131 if _idx > -1:
132 _c = _c[_idx:]
133 match = True
134 break
135 if match:
136 return _c
137 else:
138 return uknown_code
139 else:
140 return uknown_code
savex4448e132018-04-25 15:51:14 +0200141 else:
142 raise ConfigException(_message)
143
Alexe9908f72020-05-19 16:04:53 -0500144 def get_nodes_list(self, nodes_list, env_sting=None):
savex4448e132018-04-25 15:51:14 +0200145 _list = []
Alexe9908f72020-05-19 16:04:53 -0500146 if env_sting is None:
savex4448e132018-04-25 15:51:14 +0200147 # nothing supplied, use the one in repo
148 try:
Alex Savatieievd48994d2018-12-13 12:13:00 +0100149 if not nodes_list:
150 return []
Alexe9908f72020-05-19 16:04:53 -0500151 with open(nodes_list) as _f:
savex4448e132018-04-25 15:51:14 +0200152 _list.extend(_f.read().splitlines())
153 except IOError as e:
Alex Savatieievf808cd22019-03-01 13:17:59 -0600154 raise ConfigException("# Error while loading file, '{}': "
savex4448e132018-04-25 15:51:14 +0200155 "{}".format(e.filename, e.strerror))
156 else:
Alexe9908f72020-05-19 16:04:53 -0500157 _list.extend(self.node_string_to_list(env_sting))
savex4448e132018-04-25 15:51:14 +0200158
159 # validate names
160 _invalid = []
161 _valid = []
162 for idx in range(len(_list)):
163 _name = _list[idx]
164 if not self.validate_name(_name):
165 _invalid.append(_name)
166 else:
167 _valid.append(_name)
168
Alexe9908f72020-05-19 16:04:53 -0500169 return _valid, _invalid
savex4448e132018-04-25 15:51:14 +0200170
Alex9a4ad212020-10-01 18:04:25 -0500171 @staticmethod
172 def to_bool(value):
173 if value.lower() in truth:
174 return True
175 else:
176 return False
177
178 # helper functions to get nested attrs
179 # https://stackoverflow.com/questions/31174295/getattr-and-setattr-on-nested-subobjects-chained-properties
180 # using wonder's beautiful simplification:
181 # https://stackoverflow.com/questions/31174295/getattr-and-setattr-on-nested-objects/31174427?noredirect=1#comment86638618_31174427
182 def rsetattr(self, obj, attr, val):
183 pre, _, post = attr.rpartition('.')
184 return setattr(self.rgetattr(obj, pre) if pre else obj, post, val)
185
186 def rgetattr(self, obj, attr, *args):
187 def _getattr(obj, attr):
188 return getattr(obj, attr, *args)
189 return functools.reduce(_getattr, [obj] + attr.split('.'))
190
savex4448e132018-04-25 15:51:14 +0200191
192utils = Utils()