blob: 9737d80817b315e22427da0518598c7e843884b5 [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")
22 with open(os.path.join(pkg_dir, 'etc', config.pkg_versions_map)) as f:
23 _reader = csv.reader(f, delimiter=',')
24 # load packages
25 for row in _reader:
26 # load release version labels
27 if _reader.line_num == 1:
28 self._labels = [v for v in row[5:]]
29 continue
30 # package_name,component,application_or_service,repo,openstack_release,2018.4.0,2018.11.0,2019.2.0,2019.2.1,2019.2.2
31 # reassign for code readability
32 _pkg = row[0]
33 _component = row[1]
34 _app = row[2]
35 _repo = row[3]
36 # if release cell empty - use keyword 'any'
Alex3ebc5632019-04-18 16:47:18 -050037 _os_release = row[4] if len(row[4]) > 0 else 'any'
Alex41485522019-04-12 17:26:18 -050038
39 # prepare versions dict
40 _l = self._labels
Alex3ebc5632019-04-18 16:47:18 -050041 _versions = {_l[i]: row[5+i] for i in range(0, len(row[5:]))}
42
Alex41485522019-04-12 17:26:18 -050043 if _pkg in self._list:
44 if _os_release in self._list[_pkg]["versions"]:
Alex3ebc5632019-04-18 16:47:18 -050045 # all pkg/os_releases should be uniq.
46 # If found, latest one used
Alex41485522019-04-12 17:26:18 -050047 logger_cli.info(
48 "-> WARNING: Duplicate package info found "
49 "'{}' (line {})".format(
50 _pkg,
51 _reader.line_num
52 )
53 )
54 else:
55 # update pkg data in list
56 self._list.update({
57 _pkg: {
58 "component": _component,
59 "app": _app,
60 "repo": _repo,
61 "versions": {}
62 }
63 })
Alex3ebc5632019-04-18 16:47:18 -050064
Alex41485522019-04-12 17:26:18 -050065 # and finally, update the versions for this release
66 self._list[_pkg]["versions"].update({
67 _os_release: _versions
68 })
Alex3ebc5632019-04-18 16:47:18 -050069
Alex41485522019-04-12 17:26:18 -050070 def __getitem__(self, pkg_name):
Alex3ebc5632019-04-18 16:47:18 -050071 if pkg_name in self._list:
Alex41485522019-04-12 17:26:18 -050072 return self._list[pkg_name]
73 else:
Alex3ebc5632019-04-18 16:47:18 -050074 # return self._dummy_desc
Alex41485522019-04-12 17:26:18 -050075 return None
76
77
78class DebianVersion(object):
79 epoch = None
80 epoch_status = const.VERSION_NA
81 upstream = None
82 upstream_rev = None
83 upstream_status = const.VERSION_NA
84 debian = None
85 debian_rev = None
86 debian_status = const.VERSION_NA
87
88 status = ""
89 version = ""
90
91 @staticmethod
92 def split_revision(version_fragment):
93 # The symbols are -, +, ~
94 _symbols = ['-', '+', '~']
95 # nums, coz it is faster then regex
96 _chars = [46, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57]
97 _ord_map = [ord(ch) not in _chars for ch in version_fragment]
98 # if there is nothing to extract, return at once
99 if not any([_s in version_fragment for _s in _symbols]) \
Alex3ebc5632019-04-18 16:47:18 -0500100 and not any(_ord_map):
Alex41485522019-04-12 17:26:18 -0500101 # no revisions
102 return version_fragment, ""
103 else:
104 _main = _rev = ""
105 # get indices
106 _indices = []
107 for _s in _symbols:
108 if _s in version_fragment:
109 _indices.append(version_fragment.index(_s))
110 for _s in version_fragment:
111 if ord(_s) not in _chars:
112 _indices.append(version_fragment.index(_s))
113 # sort indices
114 _indices.sort()
115 # extract starting from the lowest one
116 _main = version_fragment[:_indices[0]]
117 _rev = version_fragment[_indices[0]:]
118 return _main, _rev
Alex3ebc5632019-04-18 16:47:18 -0500119
Alex41485522019-04-12 17:26:18 -0500120 def __init__(self, version_string):
121 # save
122 if len(version_string) < 1:
123 self.epoch = None
124 self.upstream = None
125 self.debian = None
126 self.version = 'n/a'
127 return
128 else:
129 # do parse the main versions
130 _v = version_string
131 # colon presence, means epoch present
132 _e = _v.split(':', 1)[0] if ':' in _v else ''
133 # if epoch was there, upstream should be cut
134 _m = _v if ':' not in _v else _v.split(':', 1)[1]
135 # dash presence, means debian present
136 _d = _m.rsplit('-', 1)[1] if '-' in _m else ''
137 # if debian was there, upstream version should be cut
138 _m = _m if '-' not in _m else _m.rsplit('-', 1)[0]
139
140 self.epoch = _e
141 self.upstream, self.upstream_rev = self.split_revision(_m)
142 self.debian, self.debian_rev = self.split_revision(_d)
143 self.version = version_string
Alex3ebc5632019-04-18 16:47:18 -0500144
Alex41485522019-04-12 17:26:18 -0500145 # Following functions is a freestyle python mimic of apt's upstream, enjoy
146 # https://github.com/chaos/apt/blob/master/apt/apt-pkg/deb/debversion.cc#L42
147 # mimic produced in order not to pull any packages or call external code
148 @staticmethod
149 def _cmp_fragment(lhf, rhf):
150 # search for difference
151 # indices
152 _li = _ri = 0
153 # pre-calc len
154 _lL = len(lhf)
155 _rL = len(rhf)
156 # bool for compare found
157 _diff = False
158 while _li < _lL and _ri < _rL:
159 # iterate lists
160 _num = lhf[_li] - rhf[_ri]
161 if _num:
162 return _num
163 _li += 1
164 _ri += 1
Alex3ebc5632019-04-18 16:47:18 -0500165
Alex41485522019-04-12 17:26:18 -0500166 # diff found? lens equal?
167 if not _diff and _lL != _rL:
168 # lens not equal? Longer - later
169 return _lL - _rL
170 else:
171 # equal
172 return 0
Alex3ebc5632019-04-18 16:47:18 -0500173
Alex41485522019-04-12 17:26:18 -0500174 def _cmp_num(self, lf, rf):
175 # split fragments into lists
176 _lhf = lf.split('.') if '.' in lf else list(lf)
177 _rhf = rf.split('.') if '.' in rf else list(rf)
178 # cast them to ints, delete empty strs
179 _lhf = [int(n) for n in _lhf if len(n)]
180 _rhf = [int(n) for n in _rhf if len(n)]
181
182 return self._cmp_fragment(_lhf, _rhf)
Alex3ebc5632019-04-18 16:47:18 -0500183
Alex41485522019-04-12 17:26:18 -0500184 def _cmp_lex(self, lf, rf):
185 # cast each item into its ORD value
186 _lhf = [ord(n) for n in lf]
187 _rhf = [ord(n) for n in rf]
188
Alex3ebc5632019-04-18 16:47:18 -0500189 return self._cmp_fragment(_lhf, _rhf)
190 # end of cmps
Alex41485522019-04-12 17:26:18 -0500191
192 # main part compared using splitted numbers
193 # if equal, revision is compared using lexical comparizon
194 def __lt__(self, v):
195 if self._cmp_num(self.epoch, v.epoch) < 0:
196 return True
197 elif self._cmp_num(self.upstream, v.upstream) < 0:
198 return True
199 elif self._cmp_lex(self.upstream_rev, v.upstream_rev) < 0:
200 return True
201 else:
202 return False
203
204 def __eq__(self, v):
205 # compare all portions
206 _result = []
207 _result.append(self._cmp_num(self.epoch, v.epoch))
208 _result.append(self._cmp_num(self.upstream, v.upstream))
209 _result.append(self._cmp_lex(self.upstream_rev, v.upstream_rev))
210 # if there is any non-zero, its not equal
211 return not any(_result)
212
213 def __gt__(self, v):
214 if self._cmp_num(self.epoch, v.epoch) > 0:
215 return True
216 elif self._cmp_num(self.upstream, v.upstream) > 0:
217 return True
218 elif self._cmp_lex(self.upstream_rev, v.upstream_rev) > 0:
219 return True
220 else:
221 return False
Alex3ebc5632019-04-18 16:47:18 -0500222
Alex41485522019-04-12 17:26:18 -0500223 def update_parts(self, target, status):
224 # updating parts of version statuses
225 if self._cmp_num(self.epoch, target.epoch) != 0:
226 self.epoch_status = status
227 else:
228 self.epoch_status = const.VERSION_OK
229
230 if self._cmp_num(self.upstream, target.upstream) != 0 \
Alex3ebc5632019-04-18 16:47:18 -0500231 or self._cmp_lex(self.upstream_rev, target.upstream_rev) != 0:
Alex41485522019-04-12 17:26:18 -0500232 self.upstream_status = status
233 else:
234 self.upstream_status = const.VERSION_OK
235
236 if self._cmp_lex(self.debian, target.debian) != 0 \
Alex3ebc5632019-04-18 16:47:18 -0500237 or self._cmp_lex(self.debian_rev, target.debian_rev) != 0:
Alex41485522019-04-12 17:26:18 -0500238 self.debian_status = status
239 else:
240 self.debian_status = const.VERSION_OK
241
242
243class VersionCmpResult(object):
244 status = ""
245 action = ""
246
247 source = None
248 target = None
249
Alex41485522019-04-12 17:26:18 -0500250 def __init__(self, i, c, r):
251 # compare three versions and write a result
252 self.source = i
253 self.status = const.VERSION_NA
254 self.action = const.ACT_NA
Alex3ebc5632019-04-18 16:47:18 -0500255
Alex41485522019-04-12 17:26:18 -0500256 # Check if there is a release version present
257 if r and len(r.version) > 0 and r.version != 'n/a':
258 # I < C, installed version is older
259 if i < c:
260 self.target = c
261 if i == r:
262 # installed version is equal vs release version
263 self.status = const.VERSION_OK
264 self.action = const.ACT_UPGRADE
265 elif i > r:
266 # installed version is newer vs release version
267 self.status = const.VERSION_UP
268 self.action = const.ACT_UPGRADE
269 elif i < r and r < c:
270 # installed version is older vs release version
271 self.status = const.VERSION_ERR
272 self.action = const.ACT_NEED_UP
273 self.target = r
274 elif i < r and c == r:
275 # installed version is older vs release version
276 self.status = const.VERSION_ERR
277 self.action = const.ACT_NEED_UP
278 self.target = c
279 elif c < r:
280 # installed and repo versions older vs release version
281 self.status = const.VERSION_ERR
282 self.action = const.ACT_REPO
283 # I > C
284 # installed version is newer
285 elif i > c:
286 self.target = c
287 if c == r:
288 # some unknown version installed
289 self.status = const.VERSION_ERR
290 self.action = const.ACT_NEED_DOWN
291 elif c > r:
292 # installed and repo versions newer than release
293 self.status = const.VERSION_UP
294 self.action = const.ACT_NEED_DOWN
295 elif c < r and r < i:
296 # repo is older vs release and both older vs installed
297 self.status = const.VERSION_UP
298 self.action = const.ACT_REPO
299 elif c < r and r == i:
300 # repo is older vs release, but release version installed
301 self.status = const.VERSION_OK
302 self.action = const.ACT_REPO
303 elif i < r:
304 # both repo and installed older vs release, new target
305 self.status = const.VERSION_DOWN
306 self.action = const.ACT_REPO
307 self.target = r
308 # I = C
309 # installed and linked repo is inline,
310 elif i == c:
311 self.target = c
312 if i < r:
313 # both are old, new target
314 self.status = const.VERSION_ERR
315 self.action = const.ACT_REPO
316 self.target = r
317 elif i > r:
318 # both are newer, same target
319 self.status = const.VERSION_UP
320 self.action = const.ACT_NA
321 elif i == r:
322 # all is ok
323 self.status = const.VERSION_OK
324 self.action = const.ACT_NA
325 else:
326 # no release version present
327 self.target = c
328 if i < c:
329 self.status = const.VERSION_OK
330 self.action = const.ACT_UPGRADE
331 elif i > c:
332 self.status = const.VERSION_UP
333 self.action = const.ACT_NEED_DOWN
334 elif i == c:
335 self.status = const.VERSION_OK
336 self.action = const.ACT_NA
Alex3ebc5632019-04-18 16:47:18 -0500337
Alex41485522019-04-12 17:26:18 -0500338 # and we need to update per-part status
339 self.source.update_parts(self.target, self.status)
340
341 @staticmethod
342 def deb_lower(_s, _t):
343 if _t.debian and _t.debian > _s.debian:
344 return True
345 else:
Alex3ebc5632019-04-18 16:47:18 -0500346 return False