blob: a2bd083acd3959c68a51403ed399d349c39af99b [file] [log] [blame]
Alex41485522019-04-12 17:26:18 -05001import csv
2import os
3
Alex265f45e2019-04-23 18:51:23 -05004from cfg_checker.common import config, const, logger_cli
5from cfg_checker.common.settings import pkg_dir
Alex41485522019-04-12 17:26:18 -05006
7
8class PkgVersions(object):
9 _labels = []
10 _list = {}
11
12 dummy_desc = {
13 "component": "unlisted",
14 "app": "-",
15 "repo": "-",
16 "versions": {}
17 }
18
19 def __init__(self):
20 # preload csv file
21 logger_cli.info("# Preloading MCP release versions")
Alexd9fd85e2019-05-16 16:58:24 -050022 with open(os.path.join(
23 pkg_dir,
24 'versions',
25 config.pkg_versions_map)
26 ) as f:
Alex41485522019-04-12 17:26:18 -050027 _reader = csv.reader(f, delimiter=',')
28 # load packages
29 for row in _reader:
30 # load release version labels
31 if _reader.line_num == 1:
32 self._labels = [v for v in row[5:]]
33 continue
34 # package_name,component,application_or_service,repo,openstack_release,2018.4.0,2018.11.0,2019.2.0,2019.2.1,2019.2.2
35 # reassign for code readability
36 _pkg = row[0]
37 _component = row[1]
38 _app = row[2]
39 _repo = row[3]
40 # if release cell empty - use keyword 'any'
Alex3ebc5632019-04-18 16:47:18 -050041 _os_release = row[4] if len(row[4]) > 0 else 'any'
Alex41485522019-04-12 17:26:18 -050042
43 # prepare versions dict
44 _l = self._labels
Alex3ebc5632019-04-18 16:47:18 -050045 _versions = {_l[i]: row[5+i] for i in range(0, len(row[5:]))}
46
Alex41485522019-04-12 17:26:18 -050047 if _pkg in self._list:
48 if _os_release in self._list[_pkg]["versions"]:
Alex3ebc5632019-04-18 16:47:18 -050049 # all pkg/os_releases should be uniq.
50 # If found, latest one used
Alex41485522019-04-12 17:26:18 -050051 logger_cli.info(
52 "-> WARNING: Duplicate package info found "
53 "'{}' (line {})".format(
54 _pkg,
55 _reader.line_num
56 )
57 )
58 else:
59 # update pkg data in list
60 self._list.update({
61 _pkg: {
62 "component": _component,
63 "app": _app,
64 "repo": _repo,
65 "versions": {}
66 }
67 })
Alex3ebc5632019-04-18 16:47:18 -050068
Alex41485522019-04-12 17:26:18 -050069 # and finally, update the versions for this release
70 self._list[_pkg]["versions"].update({
71 _os_release: _versions
72 })
Alex3ebc5632019-04-18 16:47:18 -050073
Alex41485522019-04-12 17:26:18 -050074 def __getitem__(self, pkg_name):
Alex3ebc5632019-04-18 16:47:18 -050075 if pkg_name in self._list:
Alex41485522019-04-12 17:26:18 -050076 return self._list[pkg_name]
77 else:
Alex3ebc5632019-04-18 16:47:18 -050078 # return self._dummy_desc
Alex41485522019-04-12 17:26:18 -050079 return None
80
81
82class DebianVersion(object):
83 epoch = None
84 epoch_status = const.VERSION_NA
85 upstream = None
86 upstream_rev = None
87 upstream_status = const.VERSION_NA
88 debian = None
89 debian_rev = None
90 debian_status = const.VERSION_NA
91
92 status = ""
93 version = ""
94
95 @staticmethod
96 def split_revision(version_fragment):
97 # The symbols are -, +, ~
98 _symbols = ['-', '+', '~']
99 # nums, coz it is faster then regex
100 _chars = [46, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57]
101 _ord_map = [ord(ch) not in _chars for ch in version_fragment]
102 # if there is nothing to extract, return at once
103 if not any([_s in version_fragment for _s in _symbols]) \
Alex3ebc5632019-04-18 16:47:18 -0500104 and not any(_ord_map):
Alex41485522019-04-12 17:26:18 -0500105 # no revisions
106 return version_fragment, ""
107 else:
108 _main = _rev = ""
109 # get indices
110 _indices = []
111 for _s in _symbols:
112 if _s in version_fragment:
113 _indices.append(version_fragment.index(_s))
114 for _s in version_fragment:
115 if ord(_s) not in _chars:
116 _indices.append(version_fragment.index(_s))
117 # sort indices
118 _indices.sort()
119 # extract starting from the lowest one
120 _main = version_fragment[:_indices[0]]
121 _rev = version_fragment[_indices[0]:]
122 return _main, _rev
Alex3ebc5632019-04-18 16:47:18 -0500123
Alex41485522019-04-12 17:26:18 -0500124 def __init__(self, version_string):
125 # save
126 if len(version_string) < 1:
127 self.epoch = None
128 self.upstream = None
129 self.debian = None
130 self.version = 'n/a'
131 return
132 else:
133 # do parse the main versions
134 _v = version_string
135 # colon presence, means epoch present
136 _e = _v.split(':', 1)[0] if ':' in _v else ''
137 # if epoch was there, upstream should be cut
138 _m = _v if ':' not in _v else _v.split(':', 1)[1]
139 # dash presence, means debian present
140 _d = _m.rsplit('-', 1)[1] if '-' in _m else ''
141 # if debian was there, upstream version should be cut
142 _m = _m if '-' not in _m else _m.rsplit('-', 1)[0]
143
144 self.epoch = _e
145 self.upstream, self.upstream_rev = self.split_revision(_m)
146 self.debian, self.debian_rev = self.split_revision(_d)
147 self.version = version_string
Alex3ebc5632019-04-18 16:47:18 -0500148
Alex41485522019-04-12 17:26:18 -0500149 # Following functions is a freestyle python mimic of apt's upstream, enjoy
150 # https://github.com/chaos/apt/blob/master/apt/apt-pkg/deb/debversion.cc#L42
151 # mimic produced in order not to pull any packages or call external code
152 @staticmethod
153 def _cmp_fragment(lhf, rhf):
154 # search for difference
155 # indices
156 _li = _ri = 0
157 # pre-calc len
158 _lL = len(lhf)
159 _rL = len(rhf)
160 # bool for compare found
161 _diff = False
162 while _li < _lL and _ri < _rL:
163 # iterate lists
164 _num = lhf[_li] - rhf[_ri]
165 if _num:
166 return _num
167 _li += 1
168 _ri += 1
Alex3ebc5632019-04-18 16:47:18 -0500169
Alex41485522019-04-12 17:26:18 -0500170 # diff found? lens equal?
171 if not _diff and _lL != _rL:
172 # lens not equal? Longer - later
173 return _lL - _rL
174 else:
175 # equal
176 return 0
Alex3ebc5632019-04-18 16:47:18 -0500177
Alex41485522019-04-12 17:26:18 -0500178 def _cmp_num(self, lf, rf):
179 # split fragments into lists
180 _lhf = lf.split('.') if '.' in lf else list(lf)
181 _rhf = rf.split('.') if '.' in rf else list(rf)
182 # cast them to ints, delete empty strs
183 _lhf = [int(n) for n in _lhf if len(n)]
184 _rhf = [int(n) for n in _rhf if len(n)]
185
186 return self._cmp_fragment(_lhf, _rhf)
Alex3ebc5632019-04-18 16:47:18 -0500187
Alex41485522019-04-12 17:26:18 -0500188 def _cmp_lex(self, lf, rf):
189 # cast each item into its ORD value
190 _lhf = [ord(n) for n in lf]
191 _rhf = [ord(n) for n in rf]
192
Alex3ebc5632019-04-18 16:47:18 -0500193 return self._cmp_fragment(_lhf, _rhf)
194 # end of cmps
Alex41485522019-04-12 17:26:18 -0500195
196 # main part compared using splitted numbers
197 # if equal, revision is compared using lexical comparizon
198 def __lt__(self, v):
199 if self._cmp_num(self.epoch, v.epoch) < 0:
200 return True
201 elif self._cmp_num(self.upstream, v.upstream) < 0:
202 return True
203 elif self._cmp_lex(self.upstream_rev, v.upstream_rev) < 0:
204 return True
205 else:
206 return False
207
208 def __eq__(self, v):
209 # compare all portions
210 _result = []
211 _result.append(self._cmp_num(self.epoch, v.epoch))
212 _result.append(self._cmp_num(self.upstream, v.upstream))
213 _result.append(self._cmp_lex(self.upstream_rev, v.upstream_rev))
214 # if there is any non-zero, its not equal
215 return not any(_result)
216
217 def __gt__(self, v):
218 if self._cmp_num(self.epoch, v.epoch) > 0:
219 return True
220 elif self._cmp_num(self.upstream, v.upstream) > 0:
221 return True
222 elif self._cmp_lex(self.upstream_rev, v.upstream_rev) > 0:
223 return True
224 else:
225 return False
Alex3ebc5632019-04-18 16:47:18 -0500226
Alex41485522019-04-12 17:26:18 -0500227 def update_parts(self, target, status):
228 # updating parts of version statuses
229 if self._cmp_num(self.epoch, target.epoch) != 0:
230 self.epoch_status = status
231 else:
232 self.epoch_status = const.VERSION_OK
233
234 if self._cmp_num(self.upstream, target.upstream) != 0 \
Alex3ebc5632019-04-18 16:47:18 -0500235 or self._cmp_lex(self.upstream_rev, target.upstream_rev) != 0:
Alex41485522019-04-12 17:26:18 -0500236 self.upstream_status = status
237 else:
238 self.upstream_status = const.VERSION_OK
239
240 if self._cmp_lex(self.debian, target.debian) != 0 \
Alex3ebc5632019-04-18 16:47:18 -0500241 or self._cmp_lex(self.debian_rev, target.debian_rev) != 0:
Alex41485522019-04-12 17:26:18 -0500242 self.debian_status = status
243 else:
244 self.debian_status = const.VERSION_OK
245
246
247class VersionCmpResult(object):
248 status = ""
249 action = ""
250
251 source = None
252 target = None
253
Alex41485522019-04-12 17:26:18 -0500254 def __init__(self, i, c, r):
255 # compare three versions and write a result
256 self.source = i
257 self.status = const.VERSION_NA
258 self.action = const.ACT_NA
Alex3ebc5632019-04-18 16:47:18 -0500259
Alex41485522019-04-12 17:26:18 -0500260 # Check if there is a release version present
261 if r and len(r.version) > 0 and r.version != 'n/a':
262 # I < C, installed version is older
263 if i < c:
264 self.target = c
265 if i == r:
266 # installed version is equal vs release version
267 self.status = const.VERSION_OK
268 self.action = const.ACT_UPGRADE
269 elif i > r:
270 # installed version is newer vs release version
271 self.status = const.VERSION_UP
272 self.action = const.ACT_UPGRADE
273 elif i < r and r < c:
274 # installed version is older vs release version
275 self.status = const.VERSION_ERR
276 self.action = const.ACT_NEED_UP
277 self.target = r
278 elif i < r and c == r:
279 # installed version is older vs release version
280 self.status = const.VERSION_ERR
281 self.action = const.ACT_NEED_UP
282 self.target = c
283 elif c < r:
284 # installed and repo versions older vs release version
285 self.status = const.VERSION_ERR
286 self.action = const.ACT_REPO
287 # I > C
288 # installed version is newer
289 elif i > c:
290 self.target = c
291 if c == r:
292 # some unknown version installed
293 self.status = const.VERSION_ERR
294 self.action = const.ACT_NEED_DOWN
295 elif c > r:
296 # installed and repo versions newer than release
297 self.status = const.VERSION_UP
298 self.action = const.ACT_NEED_DOWN
299 elif c < r and r < i:
300 # repo is older vs release and both older vs installed
301 self.status = const.VERSION_UP
302 self.action = const.ACT_REPO
303 elif c < r and r == i:
304 # repo is older vs release, but release version installed
305 self.status = const.VERSION_OK
306 self.action = const.ACT_REPO
307 elif i < r:
308 # both repo and installed older vs release, new target
309 self.status = const.VERSION_DOWN
310 self.action = const.ACT_REPO
311 self.target = r
312 # I = C
313 # installed and linked repo is inline,
314 elif i == c:
315 self.target = c
316 if i < r:
317 # both are old, new target
318 self.status = const.VERSION_ERR
319 self.action = const.ACT_REPO
320 self.target = r
321 elif i > r:
322 # both are newer, same target
323 self.status = const.VERSION_UP
324 self.action = const.ACT_NA
325 elif i == r:
326 # all is ok
327 self.status = const.VERSION_OK
328 self.action = const.ACT_NA
329 else:
330 # no release version present
331 self.target = c
332 if i < c:
333 self.status = const.VERSION_OK
334 self.action = const.ACT_UPGRADE
335 elif i > c:
336 self.status = const.VERSION_UP
337 self.action = const.ACT_NEED_DOWN
338 elif i == c:
339 self.status = const.VERSION_OK
340 self.action = const.ACT_NA
Alex3ebc5632019-04-18 16:47:18 -0500341
Alex41485522019-04-12 17:26:18 -0500342 # and we need to update per-part status
343 self.source.update_parts(self.target, self.status)
344
345 @staticmethod
346 def deb_lower(_s, _t):
347 if _t.debian and _t.debian > _s.debian:
348 return True
349 else:
Alex3ebc5632019-04-18 16:47:18 -0500350 return False