blob: 3f9dac2311781d06f5b705c923c6112cf210680a [file] [log] [blame]
Alex0989ecf2022-03-29 13:43:21 -05001# Author: Alex Savatieiev (osavatieiev@mirantis.com; a.savex@gmail.com)
2# Copyright 2019-2022 Mirantis, Inc.
Alex41485522019-04-12 17:26:18 -05003import csv
4import os
Alexd0391d42019-05-21 18:48:55 -05005import re
Alex41485522019-04-12 17:26:18 -05006
Alex9a4ad212020-10-01 18:04:25 -05007from cfg_checker.common import const, logger_cli
Alex265f45e2019-04-23 18:51:23 -05008from cfg_checker.common.settings import pkg_dir
Alex41485522019-04-12 17:26:18 -05009
10
11class PkgVersions(object):
12 _labels = []
13 _list = {}
14
15 dummy_desc = {
Alexd0391d42019-05-21 18:48:55 -050016 "section": "unlisted",
Alex41485522019-04-12 17:26:18 -050017 "app": "-",
18 "repo": "-",
19 "versions": {}
20 }
21
Alex9a4ad212020-10-01 18:04:25 -050022 def __init__(self, config):
Alex41485522019-04-12 17:26:18 -050023 # preload csv file
Alexe9547d82019-06-03 15:22:50 -050024 logger_cli.info("# Preloading specific MCP release versions")
Alexd9fd85e2019-05-16 16:58:24 -050025 with open(os.path.join(
26 pkg_dir,
27 'versions',
28 config.pkg_versions_map)
29 ) as f:
Alex41485522019-04-12 17:26:18 -050030 _reader = csv.reader(f, delimiter=',')
31 # load packages
32 for row in _reader:
33 # load release version labels
34 if _reader.line_num == 1:
35 self._labels = [v for v in row[5:]]
36 continue
37 # package_name,component,application_or_service,repo,openstack_release,2018.4.0,2018.11.0,2019.2.0,2019.2.1,2019.2.2
38 # reassign for code readability
39 _pkg = row[0]
Alexd0391d42019-05-21 18:48:55 -050040 _section = row[1]
Alex41485522019-04-12 17:26:18 -050041 _app = row[2]
42 _repo = row[3]
43 # if release cell empty - use keyword 'any'
Alex3ebc5632019-04-18 16:47:18 -050044 _os_release = row[4] if len(row[4]) > 0 else 'any'
Alex41485522019-04-12 17:26:18 -050045
46 # prepare versions dict
47 _l = self._labels
Alex3ebc5632019-04-18 16:47:18 -050048 _versions = {_l[i]: row[5+i] for i in range(0, len(row[5:]))}
49
Alex41485522019-04-12 17:26:18 -050050 if _pkg in self._list:
51 if _os_release in self._list[_pkg]["versions"]:
Alex3ebc5632019-04-18 16:47:18 -050052 # all pkg/os_releases should be uniq.
53 # If found, latest one used
Alex41485522019-04-12 17:26:18 -050054 logger_cli.info(
55 "-> WARNING: Duplicate package info found "
56 "'{}' (line {})".format(
57 _pkg,
58 _reader.line_num
59 )
60 )
61 else:
62 # update pkg data in list
63 self._list.update({
64 _pkg: {
Alexd0391d42019-05-21 18:48:55 -050065 "section": _section,
Alex41485522019-04-12 17:26:18 -050066 "app": _app,
67 "repo": _repo,
68 "versions": {}
69 }
70 })
Alex3ebc5632019-04-18 16:47:18 -050071
Alex41485522019-04-12 17:26:18 -050072 # and finally, update the versions for this release
73 self._list[_pkg]["versions"].update({
74 _os_release: _versions
75 })
Alex3ebc5632019-04-18 16:47:18 -050076
Alex41485522019-04-12 17:26:18 -050077 def __getitem__(self, pkg_name):
Alex3ebc5632019-04-18 16:47:18 -050078 if pkg_name in self._list:
Alex41485522019-04-12 17:26:18 -050079 return self._list[pkg_name]
80 else:
Alex3ebc5632019-04-18 16:47:18 -050081 # return self._dummy_desc
Alex41485522019-04-12 17:26:18 -050082 return None
83
84
85class DebianVersion(object):
86 epoch = None
87 epoch_status = const.VERSION_NA
88 upstream = None
89 upstream_rev = None
90 upstream_status = const.VERSION_NA
91 debian = None
92 debian_rev = None
93 debian_status = const.VERSION_NA
94
95 status = ""
96 version = ""
97
98 @staticmethod
99 def split_revision(version_fragment):
100 # The symbols are -, +, ~
101 _symbols = ['-', '+', '~']
102 # nums, coz it is faster then regex
103 _chars = [46, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57]
104 _ord_map = [ord(ch) not in _chars for ch in version_fragment]
105 # if there is nothing to extract, return at once
106 if not any([_s in version_fragment for _s in _symbols]) \
Alex3ebc5632019-04-18 16:47:18 -0500107 and not any(_ord_map):
Alex41485522019-04-12 17:26:18 -0500108 # no revisions
109 return version_fragment, ""
110 else:
111 _main = _rev = ""
112 # get indices
113 _indices = []
114 for _s in _symbols:
115 if _s in version_fragment:
116 _indices.append(version_fragment.index(_s))
117 for _s in version_fragment:
118 if ord(_s) not in _chars:
119 _indices.append(version_fragment.index(_s))
120 # sort indices
121 _indices.sort()
122 # extract starting from the lowest one
123 _main = version_fragment[:_indices[0]]
124 _rev = version_fragment[_indices[0]:]
125 return _main, _rev
Alex3ebc5632019-04-18 16:47:18 -0500126
Alex41485522019-04-12 17:26:18 -0500127 def __init__(self, version_string):
128 # save
129 if len(version_string) < 1:
Alexd0391d42019-05-21 18:48:55 -0500130 self.epoch = "0"
131 self.upstream = "0"
132 self.debian = ''
Alex41485522019-04-12 17:26:18 -0500133 self.version = 'n/a'
134 return
135 else:
136 # do parse the main versions
137 _v = version_string
138 # colon presence, means epoch present
Alexd0391d42019-05-21 18:48:55 -0500139 _e = _v.split(':', 1)[0] if ':' in _v else "0"
Alex41485522019-04-12 17:26:18 -0500140 # if epoch was there, upstream should be cut
141 _m = _v if ':' not in _v else _v.split(':', 1)[1]
142 # dash presence, means debian present
143 _d = _m.rsplit('-', 1)[1] if '-' in _m else ''
144 # if debian was there, upstream version should be cut
145 _m = _m if '-' not in _m else _m.rsplit('-', 1)[0]
146
147 self.epoch = _e
148 self.upstream, self.upstream_rev = self.split_revision(_m)
149 self.debian, self.debian_rev = self.split_revision(_d)
150 self.version = version_string
Alex3ebc5632019-04-18 16:47:18 -0500151
Alex41485522019-04-12 17:26:18 -0500152 # Following functions is a freestyle python mimic of apt's upstream, enjoy
153 # https://github.com/chaos/apt/blob/master/apt/apt-pkg/deb/debversion.cc#L42
154 # mimic produced in order not to pull any packages or call external code
155 @staticmethod
156 def _cmp_fragment(lhf, rhf):
157 # search for difference
158 # indices
159 _li = _ri = 0
160 # pre-calc len
161 _lL = len(lhf)
162 _rL = len(rhf)
163 # bool for compare found
164 _diff = False
165 while _li < _lL and _ri < _rL:
166 # iterate lists
167 _num = lhf[_li] - rhf[_ri]
168 if _num:
169 return _num
170 _li += 1
171 _ri += 1
Alex3ebc5632019-04-18 16:47:18 -0500172
Alex41485522019-04-12 17:26:18 -0500173 # diff found? lens equal?
174 if not _diff and _lL != _rL:
175 # lens not equal? Longer - later
176 return _lL - _rL
177 else:
178 # equal
179 return 0
Alex3ebc5632019-04-18 16:47:18 -0500180
Alex41485522019-04-12 17:26:18 -0500181 def _cmp_num(self, lf, rf):
182 # split fragments into lists
183 _lhf = lf.split('.') if '.' in lf else list(lf)
184 _rhf = rf.split('.') if '.' in rf else list(rf)
185 # cast them to ints, delete empty strs
186 _lhf = [int(n) for n in _lhf if len(n)]
187 _rhf = [int(n) for n in _rhf if len(n)]
188
189 return self._cmp_fragment(_lhf, _rhf)
Alex3ebc5632019-04-18 16:47:18 -0500190
Alex41485522019-04-12 17:26:18 -0500191 def _cmp_lex(self, lf, rf):
Alexd0391d42019-05-21 18:48:55 -0500192 def split_rev(_s):
193 _out = []
194 _list = re.split(r'(\d+)', _s)
195 # iterate and cast into num of possible
196 for idx in range(0, len(_list)):
197 try:
198 # try to convert it to number
199 _out.append(int(_list[idx]))
200 except ValueError:
201 # not a number
202 _ords = [ord(n) for n in _list[idx]]
203 _out += _ords
204 return _out
205 # split string into letters and numbers
206 # and cast each item into its ORD value
207 _lhf = split_rev(lf)
208 _rhf = split_rev(rf)
209 # _lhf = [ord(n) for n in lf]
210 # _rhf = [ord(n) for n in rf]
Alex41485522019-04-12 17:26:18 -0500211
Alex3ebc5632019-04-18 16:47:18 -0500212 return self._cmp_fragment(_lhf, _rhf)
213 # end of cmps
Alex41485522019-04-12 17:26:18 -0500214
215 # main part compared using splitted numbers
216 # if equal, revision is compared using lexical comparizon
217 def __lt__(self, v):
Alexd0391d42019-05-21 18:48:55 -0500218 _e = self._cmp_num(self.epoch, v.epoch)
219 _u = self._cmp_num(self.upstream, v.upstream)
220 _ul = self._cmp_lex(self.upstream_rev, v.upstream_rev)
221 _d = self._cmp_num(self.debian, v.debian)
222 _dl = self._cmp_lex(self.debian_rev, v.debian_rev)
223 for n in [_e, _u, _ul, _d, _dl]:
224 if n == 0:
225 continue
226 elif n < 0:
227 return True
228 elif n > 0:
229 return False
230 # if all is equal, it is still false
231 return False
Alex41485522019-04-12 17:26:18 -0500232
233 def __eq__(self, v):
234 # compare all portions
235 _result = []
236 _result.append(self._cmp_num(self.epoch, v.epoch))
237 _result.append(self._cmp_num(self.upstream, v.upstream))
238 _result.append(self._cmp_lex(self.upstream_rev, v.upstream_rev))
Alexd0391d42019-05-21 18:48:55 -0500239 _result.append(self._cmp_num(self.debian, v.debian))
240 _result.append(self._cmp_lex(self.debian_rev, v.debian_rev))
Alex41485522019-04-12 17:26:18 -0500241 # if there is any non-zero, its not equal
242 return not any(_result)
243
244 def __gt__(self, v):
Alexd0391d42019-05-21 18:48:55 -0500245 _e = self._cmp_num(self.epoch, v.epoch)
246 _u = self._cmp_num(self.upstream, v.upstream)
247 _ul = self._cmp_lex(self.upstream_rev, v.upstream_rev)
248 _d = self._cmp_num(self.debian, v.debian)
249 _dl = self._cmp_lex(self.debian_rev, v.debian_rev)
250 for n in [_e, _u, _ul, _d, _dl]:
251 if n == 0:
252 continue
253 elif n > 0:
254 return True
255 elif n < 0:
256 return False
257 # if all is equal, it is still false
258 return False
Alex3ebc5632019-04-18 16:47:18 -0500259
Alex41485522019-04-12 17:26:18 -0500260 def update_parts(self, target, status):
261 # updating parts of version statuses
262 if self._cmp_num(self.epoch, target.epoch) != 0:
263 self.epoch_status = status
264 else:
265 self.epoch_status = const.VERSION_OK
266
267 if self._cmp_num(self.upstream, target.upstream) != 0 \
Alex3ebc5632019-04-18 16:47:18 -0500268 or self._cmp_lex(self.upstream_rev, target.upstream_rev) != 0:
Alex41485522019-04-12 17:26:18 -0500269 self.upstream_status = status
270 else:
271 self.upstream_status = const.VERSION_OK
272
273 if self._cmp_lex(self.debian, target.debian) != 0 \
Alex3ebc5632019-04-18 16:47:18 -0500274 or self._cmp_lex(self.debian_rev, target.debian_rev) != 0:
Alex41485522019-04-12 17:26:18 -0500275 self.debian_status = status
276 else:
277 self.debian_status = const.VERSION_OK
278
279
280class VersionCmpResult(object):
281 status = ""
282 action = ""
283
284 source = None
285 target = None
286
Alex41485522019-04-12 17:26:18 -0500287 def __init__(self, i, c, r):
288 # compare three versions and write a result
289 self.source = i
290 self.status = const.VERSION_NA
291 self.action = const.ACT_NA
Alex3ebc5632019-04-18 16:47:18 -0500292
Alex41485522019-04-12 17:26:18 -0500293 # Check if there is a release version present
294 if r and len(r.version) > 0 and r.version != 'n/a':
295 # I < C, installed version is older
296 if i < c:
297 self.target = c
298 if i == r:
299 # installed version is equal vs release version
300 self.status = const.VERSION_OK
301 self.action = const.ACT_UPGRADE
302 elif i > r:
303 # installed version is newer vs release version
304 self.status = const.VERSION_UP
305 self.action = const.ACT_UPGRADE
306 elif i < r and r < c:
307 # installed version is older vs release version
Alex26b8a8c2019-10-09 17:09:07 -0500308 self.status = const.VERSION_WARN
Alex41485522019-04-12 17:26:18 -0500309 self.action = const.ACT_NEED_UP
310 self.target = r
311 elif i < r and c == r:
312 # installed version is older vs release version
Alex26b8a8c2019-10-09 17:09:07 -0500313 self.status = const.VERSION_WARN
Alex41485522019-04-12 17:26:18 -0500314 self.action = const.ACT_NEED_UP
315 self.target = c
316 elif c < r:
317 # installed and repo versions older vs release version
Alex26b8a8c2019-10-09 17:09:07 -0500318 self.status = const.VERSION_WARN
Alex41485522019-04-12 17:26:18 -0500319 self.action = const.ACT_REPO
320 # I > C
321 # installed version is newer
322 elif i > c:
323 self.target = c
324 if c == r:
325 # some unknown version installed
Alex26b8a8c2019-10-09 17:09:07 -0500326 self.status = const.VERSION_WARN
Alex41485522019-04-12 17:26:18 -0500327 self.action = const.ACT_NEED_DOWN
328 elif c > r:
329 # installed and repo versions newer than release
330 self.status = const.VERSION_UP
331 self.action = const.ACT_NEED_DOWN
332 elif c < r and r < i:
333 # repo is older vs release and both older vs installed
334 self.status = const.VERSION_UP
335 self.action = const.ACT_REPO
336 elif c < r and r == i:
337 # repo is older vs release, but release version installed
338 self.status = const.VERSION_OK
339 self.action = const.ACT_REPO
340 elif i < r:
341 # both repo and installed older vs release, new target
342 self.status = const.VERSION_DOWN
343 self.action = const.ACT_REPO
344 self.target = r
345 # I = C
346 # installed and linked repo is inline,
347 elif i == c:
348 self.target = c
349 if i < r:
Alex9e4bfaf2019-06-11 15:21:59 -0500350 # both are intact, new target possible
351 self.status = const.VERSION_OK
Alex41485522019-04-12 17:26:18 -0500352 self.action = const.ACT_REPO
353 self.target = r
354 elif i > r:
355 # both are newer, same target
Alex3bc95f62020-03-05 17:00:04 -0600356 self.status = const.VERSION_WARN
357 self.action = const.ACT_REPO
Alex41485522019-04-12 17:26:18 -0500358 elif i == r:
359 # all is ok
360 self.status = const.VERSION_OK
361 self.action = const.ACT_NA
362 else:
363 # no release version present
364 self.target = c
365 if i < c:
366 self.status = const.VERSION_OK
367 self.action = const.ACT_UPGRADE
368 elif i > c:
369 self.status = const.VERSION_UP
370 self.action = const.ACT_NEED_DOWN
371 elif i == c:
372 self.status = const.VERSION_OK
373 self.action = const.ACT_NA
Alex3ebc5632019-04-18 16:47:18 -0500374
Alex41485522019-04-12 17:26:18 -0500375 # and we need to update per-part status
376 self.source.update_parts(self.target, self.status)