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