blob: 05eaad868898bf7afc513591c397d7fd20af409c [file] [log] [blame]
Alex41485522019-04-12 17:26:18 -05001import csv
2import os
Alexd0391d42019-05-21 18:48:55 -05003import re
Alex41485522019-04-12 17:26:18 -05004
Alex9a4ad212020-10-01 18:04:25 -05005from cfg_checker.common import const, logger_cli
Alex265f45e2019-04-23 18:51:23 -05006from cfg_checker.common.settings import pkg_dir
Alex41485522019-04-12 17:26:18 -05007
8
9class PkgVersions(object):
10 _labels = []
11 _list = {}
12
13 dummy_desc = {
Alexd0391d42019-05-21 18:48:55 -050014 "section": "unlisted",
Alex41485522019-04-12 17:26:18 -050015 "app": "-",
16 "repo": "-",
17 "versions": {}
18 }
19
Alex9a4ad212020-10-01 18:04:25 -050020 def __init__(self, config):
Alex41485522019-04-12 17:26:18 -050021 # preload csv file
Alexe9547d82019-06-03 15:22:50 -050022 logger_cli.info("# Preloading specific MCP release versions")
Alexd9fd85e2019-05-16 16:58:24 -050023 with open(os.path.join(
24 pkg_dir,
25 'versions',
26 config.pkg_versions_map)
27 ) as f:
Alex41485522019-04-12 17:26:18 -050028 _reader = csv.reader(f, delimiter=',')
29 # load packages
30 for row in _reader:
31 # load release version labels
32 if _reader.line_num == 1:
33 self._labels = [v for v in row[5:]]
34 continue
35 # package_name,component,application_or_service,repo,openstack_release,2018.4.0,2018.11.0,2019.2.0,2019.2.1,2019.2.2
36 # reassign for code readability
37 _pkg = row[0]
Alexd0391d42019-05-21 18:48:55 -050038 _section = row[1]
Alex41485522019-04-12 17:26:18 -050039 _app = row[2]
40 _repo = row[3]
41 # if release cell empty - use keyword 'any'
Alex3ebc5632019-04-18 16:47:18 -050042 _os_release = row[4] if len(row[4]) > 0 else 'any'
Alex41485522019-04-12 17:26:18 -050043
44 # prepare versions dict
45 _l = self._labels
Alex3ebc5632019-04-18 16:47:18 -050046 _versions = {_l[i]: row[5+i] for i in range(0, len(row[5:]))}
47
Alex41485522019-04-12 17:26:18 -050048 if _pkg in self._list:
49 if _os_release in self._list[_pkg]["versions"]:
Alex3ebc5632019-04-18 16:47:18 -050050 # all pkg/os_releases should be uniq.
51 # If found, latest one used
Alex41485522019-04-12 17:26:18 -050052 logger_cli.info(
53 "-> WARNING: Duplicate package info found "
54 "'{}' (line {})".format(
55 _pkg,
56 _reader.line_num
57 )
58 )
59 else:
60 # update pkg data in list
61 self._list.update({
62 _pkg: {
Alexd0391d42019-05-21 18:48:55 -050063 "section": _section,
Alex41485522019-04-12 17:26:18 -050064 "app": _app,
65 "repo": _repo,
66 "versions": {}
67 }
68 })
Alex3ebc5632019-04-18 16:47:18 -050069
Alex41485522019-04-12 17:26:18 -050070 # and finally, update the versions for this release
71 self._list[_pkg]["versions"].update({
72 _os_release: _versions
73 })
Alex3ebc5632019-04-18 16:47:18 -050074
Alex41485522019-04-12 17:26:18 -050075 def __getitem__(self, pkg_name):
Alex3ebc5632019-04-18 16:47:18 -050076 if pkg_name in self._list:
Alex41485522019-04-12 17:26:18 -050077 return self._list[pkg_name]
78 else:
Alex3ebc5632019-04-18 16:47:18 -050079 # return self._dummy_desc
Alex41485522019-04-12 17:26:18 -050080 return None
81
82
83class DebianVersion(object):
84 epoch = None
85 epoch_status = const.VERSION_NA
86 upstream = None
87 upstream_rev = None
88 upstream_status = const.VERSION_NA
89 debian = None
90 debian_rev = None
91 debian_status = const.VERSION_NA
92
93 status = ""
94 version = ""
95
96 @staticmethod
97 def split_revision(version_fragment):
98 # The symbols are -, +, ~
99 _symbols = ['-', '+', '~']
100 # nums, coz it is faster then regex
101 _chars = [46, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57]
102 _ord_map = [ord(ch) not in _chars for ch in version_fragment]
103 # if there is nothing to extract, return at once
104 if not any([_s in version_fragment for _s in _symbols]) \
Alex3ebc5632019-04-18 16:47:18 -0500105 and not any(_ord_map):
Alex41485522019-04-12 17:26:18 -0500106 # no revisions
107 return version_fragment, ""
108 else:
109 _main = _rev = ""
110 # get indices
111 _indices = []
112 for _s in _symbols:
113 if _s in version_fragment:
114 _indices.append(version_fragment.index(_s))
115 for _s in version_fragment:
116 if ord(_s) not in _chars:
117 _indices.append(version_fragment.index(_s))
118 # sort indices
119 _indices.sort()
120 # extract starting from the lowest one
121 _main = version_fragment[:_indices[0]]
122 _rev = version_fragment[_indices[0]:]
123 return _main, _rev
Alex3ebc5632019-04-18 16:47:18 -0500124
Alex41485522019-04-12 17:26:18 -0500125 def __init__(self, version_string):
126 # save
127 if len(version_string) < 1:
Alexd0391d42019-05-21 18:48:55 -0500128 self.epoch = "0"
129 self.upstream = "0"
130 self.debian = ''
Alex41485522019-04-12 17:26:18 -0500131 self.version = 'n/a'
132 return
133 else:
134 # do parse the main versions
135 _v = version_string
136 # colon presence, means epoch present
Alexd0391d42019-05-21 18:48:55 -0500137 _e = _v.split(':', 1)[0] if ':' in _v else "0"
Alex41485522019-04-12 17:26:18 -0500138 # if epoch was there, upstream should be cut
139 _m = _v if ':' not in _v else _v.split(':', 1)[1]
140 # dash presence, means debian present
141 _d = _m.rsplit('-', 1)[1] if '-' in _m else ''
142 # if debian was there, upstream version should be cut
143 _m = _m if '-' not in _m else _m.rsplit('-', 1)[0]
144
145 self.epoch = _e
146 self.upstream, self.upstream_rev = self.split_revision(_m)
147 self.debian, self.debian_rev = self.split_revision(_d)
148 self.version = version_string
Alex3ebc5632019-04-18 16:47:18 -0500149
Alex41485522019-04-12 17:26:18 -0500150 # Following functions is a freestyle python mimic of apt's upstream, enjoy
151 # https://github.com/chaos/apt/blob/master/apt/apt-pkg/deb/debversion.cc#L42
152 # mimic produced in order not to pull any packages or call external code
153 @staticmethod
154 def _cmp_fragment(lhf, rhf):
155 # search for difference
156 # indices
157 _li = _ri = 0
158 # pre-calc len
159 _lL = len(lhf)
160 _rL = len(rhf)
161 # bool for compare found
162 _diff = False
163 while _li < _lL and _ri < _rL:
164 # iterate lists
165 _num = lhf[_li] - rhf[_ri]
166 if _num:
167 return _num
168 _li += 1
169 _ri += 1
Alex3ebc5632019-04-18 16:47:18 -0500170
Alex41485522019-04-12 17:26:18 -0500171 # diff found? lens equal?
172 if not _diff and _lL != _rL:
173 # lens not equal? Longer - later
174 return _lL - _rL
175 else:
176 # equal
177 return 0
Alex3ebc5632019-04-18 16:47:18 -0500178
Alex41485522019-04-12 17:26:18 -0500179 def _cmp_num(self, lf, rf):
180 # split fragments into lists
181 _lhf = lf.split('.') if '.' in lf else list(lf)
182 _rhf = rf.split('.') if '.' in rf else list(rf)
183 # cast them to ints, delete empty strs
184 _lhf = [int(n) for n in _lhf if len(n)]
185 _rhf = [int(n) for n in _rhf if len(n)]
186
187 return self._cmp_fragment(_lhf, _rhf)
Alex3ebc5632019-04-18 16:47:18 -0500188
Alex41485522019-04-12 17:26:18 -0500189 def _cmp_lex(self, lf, rf):
Alexd0391d42019-05-21 18:48:55 -0500190 def split_rev(_s):
191 _out = []
192 _list = re.split(r'(\d+)', _s)
193 # iterate and cast into num of possible
194 for idx in range(0, len(_list)):
195 try:
196 # try to convert it to number
197 _out.append(int(_list[idx]))
198 except ValueError:
199 # not a number
200 _ords = [ord(n) for n in _list[idx]]
201 _out += _ords
202 return _out
203 # split string into letters and numbers
204 # and cast each item into its ORD value
205 _lhf = split_rev(lf)
206 _rhf = split_rev(rf)
207 # _lhf = [ord(n) for n in lf]
208 # _rhf = [ord(n) for n in rf]
Alex41485522019-04-12 17:26:18 -0500209
Alex3ebc5632019-04-18 16:47:18 -0500210 return self._cmp_fragment(_lhf, _rhf)
211 # end of cmps
Alex41485522019-04-12 17:26:18 -0500212
213 # main part compared using splitted numbers
214 # if equal, revision is compared using lexical comparizon
215 def __lt__(self, v):
Alexd0391d42019-05-21 18:48:55 -0500216 _e = self._cmp_num(self.epoch, v.epoch)
217 _u = self._cmp_num(self.upstream, v.upstream)
218 _ul = self._cmp_lex(self.upstream_rev, v.upstream_rev)
219 _d = self._cmp_num(self.debian, v.debian)
220 _dl = self._cmp_lex(self.debian_rev, v.debian_rev)
221 for n in [_e, _u, _ul, _d, _dl]:
222 if n == 0:
223 continue
224 elif n < 0:
225 return True
226 elif n > 0:
227 return False
228 # if all is equal, it is still false
229 return False
Alex41485522019-04-12 17:26:18 -0500230
231 def __eq__(self, v):
232 # compare all portions
233 _result = []
234 _result.append(self._cmp_num(self.epoch, v.epoch))
235 _result.append(self._cmp_num(self.upstream, v.upstream))
236 _result.append(self._cmp_lex(self.upstream_rev, v.upstream_rev))
Alexd0391d42019-05-21 18:48:55 -0500237 _result.append(self._cmp_num(self.debian, v.debian))
238 _result.append(self._cmp_lex(self.debian_rev, v.debian_rev))
Alex41485522019-04-12 17:26:18 -0500239 # if there is any non-zero, its not equal
240 return not any(_result)
241
242 def __gt__(self, v):
Alexd0391d42019-05-21 18:48:55 -0500243 _e = self._cmp_num(self.epoch, v.epoch)
244 _u = self._cmp_num(self.upstream, v.upstream)
245 _ul = self._cmp_lex(self.upstream_rev, v.upstream_rev)
246 _d = self._cmp_num(self.debian, v.debian)
247 _dl = self._cmp_lex(self.debian_rev, v.debian_rev)
248 for n in [_e, _u, _ul, _d, _dl]:
249 if n == 0:
250 continue
251 elif n > 0:
252 return True
253 elif n < 0:
254 return False
255 # if all is equal, it is still false
256 return False
Alex3ebc5632019-04-18 16:47:18 -0500257
Alex41485522019-04-12 17:26:18 -0500258 def update_parts(self, target, status):
259 # updating parts of version statuses
260 if self._cmp_num(self.epoch, target.epoch) != 0:
261 self.epoch_status = status
262 else:
263 self.epoch_status = const.VERSION_OK
264
265 if self._cmp_num(self.upstream, target.upstream) != 0 \
Alex3ebc5632019-04-18 16:47:18 -0500266 or self._cmp_lex(self.upstream_rev, target.upstream_rev) != 0:
Alex41485522019-04-12 17:26:18 -0500267 self.upstream_status = status
268 else:
269 self.upstream_status = const.VERSION_OK
270
271 if self._cmp_lex(self.debian, target.debian) != 0 \
Alex3ebc5632019-04-18 16:47:18 -0500272 or self._cmp_lex(self.debian_rev, target.debian_rev) != 0:
Alex41485522019-04-12 17:26:18 -0500273 self.debian_status = status
274 else:
275 self.debian_status = const.VERSION_OK
276
277
278class VersionCmpResult(object):
279 status = ""
280 action = ""
281
282 source = None
283 target = None
284
Alex41485522019-04-12 17:26:18 -0500285 def __init__(self, i, c, r):
286 # compare three versions and write a result
287 self.source = i
288 self.status = const.VERSION_NA
289 self.action = const.ACT_NA
Alex3ebc5632019-04-18 16:47:18 -0500290
Alex41485522019-04-12 17:26:18 -0500291 # Check if there is a release version present
292 if r and len(r.version) > 0 and r.version != 'n/a':
293 # I < C, installed version is older
294 if i < c:
295 self.target = c
296 if i == r:
297 # installed version is equal vs release version
298 self.status = const.VERSION_OK
299 self.action = const.ACT_UPGRADE
300 elif i > r:
301 # installed version is newer vs release version
302 self.status = const.VERSION_UP
303 self.action = const.ACT_UPGRADE
304 elif i < r and r < c:
305 # installed version is older vs release version
Alex26b8a8c2019-10-09 17:09:07 -0500306 self.status = const.VERSION_WARN
Alex41485522019-04-12 17:26:18 -0500307 self.action = const.ACT_NEED_UP
308 self.target = r
309 elif i < r and c == r:
310 # installed version is older vs release version
Alex26b8a8c2019-10-09 17:09:07 -0500311 self.status = const.VERSION_WARN
Alex41485522019-04-12 17:26:18 -0500312 self.action = const.ACT_NEED_UP
313 self.target = c
314 elif c < r:
315 # installed and repo versions older vs release version
Alex26b8a8c2019-10-09 17:09:07 -0500316 self.status = const.VERSION_WARN
Alex41485522019-04-12 17:26:18 -0500317 self.action = const.ACT_REPO
318 # I > C
319 # installed version is newer
320 elif i > c:
321 self.target = c
322 if c == r:
323 # some unknown version installed
Alex26b8a8c2019-10-09 17:09:07 -0500324 self.status = const.VERSION_WARN
Alex41485522019-04-12 17:26:18 -0500325 self.action = const.ACT_NEED_DOWN
326 elif c > r:
327 # installed and repo versions newer than release
328 self.status = const.VERSION_UP
329 self.action = const.ACT_NEED_DOWN
330 elif c < r and r < i:
331 # repo is older vs release and both older vs installed
332 self.status = const.VERSION_UP
333 self.action = const.ACT_REPO
334 elif c < r and r == i:
335 # repo is older vs release, but release version installed
336 self.status = const.VERSION_OK
337 self.action = const.ACT_REPO
338 elif i < r:
339 # both repo and installed older vs release, new target
340 self.status = const.VERSION_DOWN
341 self.action = const.ACT_REPO
342 self.target = r
343 # I = C
344 # installed and linked repo is inline,
345 elif i == c:
346 self.target = c
347 if i < r:
Alex9e4bfaf2019-06-11 15:21:59 -0500348 # both are intact, new target possible
349 self.status = const.VERSION_OK
Alex41485522019-04-12 17:26:18 -0500350 self.action = const.ACT_REPO
351 self.target = r
352 elif i > r:
353 # both are newer, same target
Alex3bc95f62020-03-05 17:00:04 -0600354 self.status = const.VERSION_WARN
355 self.action = const.ACT_REPO
Alex41485522019-04-12 17:26:18 -0500356 elif i == r:
357 # all is ok
358 self.status = const.VERSION_OK
359 self.action = const.ACT_NA
360 else:
361 # no release version present
362 self.target = c
363 if i < c:
364 self.status = const.VERSION_OK
365 self.action = const.ACT_UPGRADE
366 elif i > c:
367 self.status = const.VERSION_UP
368 self.action = const.ACT_NEED_DOWN
369 elif i == c:
370 self.status = const.VERSION_OK
371 self.action = const.ACT_NA
Alex3ebc5632019-04-18 16:47:18 -0500372
Alex41485522019-04-12 17:26:18 -0500373 # and we need to update per-part status
374 self.source.update_parts(self.target, self.status)