blob: 10f65dc012536566226a5c4c6c13b4d30a6af61f [file] [log] [blame]
Alex41485522019-04-12 17:26:18 -05001import csv
2import os
3
4from cfg_checker.common import config, logger, logger_cli, pkg_dir, const
5
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'
36 _os_release = row[4] if len(row[4]) > 0 else 'any'
37
38 # prepare versions dict
39 _l = self._labels
40 _versions = {_l[i]:row[5+i] for i in range(0, len(row[5:]))}
41
42 if _pkg in self._list:
43 if _os_release in self._list[_pkg]["versions"]:
44 # all pkg/os_releases should be uniq. If found, latest one used
45 logger_cli.info(
46 "-> WARNING: Duplicate package info found "
47 "'{}' (line {})".format(
48 _pkg,
49 _reader.line_num
50 )
51 )
52 else:
53 # update pkg data in list
54 self._list.update({
55 _pkg: {
56 "component": _component,
57 "app": _app,
58 "repo": _repo,
59 "versions": {}
60 }
61 })
62
63 # and finally, update the versions for this release
64 self._list[_pkg]["versions"].update({
65 _os_release: _versions
66 })
67
68 def __getitem__(self, pkg_name):
69 if pkg_name in self._list:
70 return self._list[pkg_name]
71 else:
72 #return self._dummy_desc
73 return None
74
75
76class DebianVersion(object):
77 epoch = None
78 epoch_status = const.VERSION_NA
79 upstream = None
80 upstream_rev = None
81 upstream_status = const.VERSION_NA
82 debian = None
83 debian_rev = None
84 debian_status = const.VERSION_NA
85
86 status = ""
87 version = ""
88
89 @staticmethod
90 def split_revision(version_fragment):
91 # The symbols are -, +, ~
92 _symbols = ['-', '+', '~']
93 # nums, coz it is faster then regex
94 _chars = [46, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57]
95 _ord_map = [ord(ch) not in _chars for ch in version_fragment]
96 # if there is nothing to extract, return at once
97 if not any([_s in version_fragment for _s in _symbols]) \
98 and not any(_ord_map):
99 # no revisions
100 return version_fragment, ""
101 else:
102 _main = _rev = ""
103 # get indices
104 _indices = []
105 for _s in _symbols:
106 if _s in version_fragment:
107 _indices.append(version_fragment.index(_s))
108 for _s in version_fragment:
109 if ord(_s) not in _chars:
110 _indices.append(version_fragment.index(_s))
111 # sort indices
112 _indices.sort()
113 # extract starting from the lowest one
114 _main = version_fragment[:_indices[0]]
115 _rev = version_fragment[_indices[0]:]
116 return _main, _rev
117
118 def __init__(self, version_string):
119 # save
120 if len(version_string) < 1:
121 self.epoch = None
122 self.upstream = None
123 self.debian = None
124 self.version = 'n/a'
125 return
126 else:
127 # do parse the main versions
128 _v = version_string
129 # colon presence, means epoch present
130 _e = _v.split(':', 1)[0] if ':' in _v else ''
131 # if epoch was there, upstream should be cut
132 _m = _v if ':' not in _v else _v.split(':', 1)[1]
133 # dash presence, means debian present
134 _d = _m.rsplit('-', 1)[1] if '-' in _m else ''
135 # if debian was there, upstream version should be cut
136 _m = _m if '-' not in _m else _m.rsplit('-', 1)[0]
137
138 self.epoch = _e
139 self.upstream, self.upstream_rev = self.split_revision(_m)
140 self.debian, self.debian_rev = self.split_revision(_d)
141 self.version = version_string
142
143 # Following functions is a freestyle python mimic of apt's upstream, enjoy
144 # https://github.com/chaos/apt/blob/master/apt/apt-pkg/deb/debversion.cc#L42
145 # mimic produced in order not to pull any packages or call external code
146 @staticmethod
147 def _cmp_fragment(lhf, rhf):
148 # search for difference
149 # indices
150 _li = _ri = 0
151 # pre-calc len
152 _lL = len(lhf)
153 _rL = len(rhf)
154 # bool for compare found
155 _diff = False
156 while _li < _lL and _ri < _rL:
157 # iterate lists
158 _num = lhf[_li] - rhf[_ri]
159 if _num:
160 return _num
161 _li += 1
162 _ri += 1
163
164 # diff found? lens equal?
165 if not _diff and _lL != _rL:
166 # lens not equal? Longer - later
167 return _lL - _rL
168 else:
169 # equal
170 return 0
171
172 def _cmp_num(self, lf, rf):
173 # split fragments into lists
174 _lhf = lf.split('.') if '.' in lf else list(lf)
175 _rhf = rf.split('.') if '.' in rf else list(rf)
176 # cast them to ints, delete empty strs
177 _lhf = [int(n) for n in _lhf if len(n)]
178 _rhf = [int(n) for n in _rhf if len(n)]
179
180 return self._cmp_fragment(_lhf, _rhf)
181
182 def _cmp_lex(self, lf, rf):
183 # cast each item into its ORD value
184 _lhf = [ord(n) for n in lf]
185 _rhf = [ord(n) for n in rf]
186
187 return self._cmp_fragment(_lhf, _rhf)
188 # end of cmps
189
190 # main part compared using splitted numbers
191 # if equal, revision is compared using lexical comparizon
192 def __lt__(self, v):
193 if self._cmp_num(self.epoch, v.epoch) < 0:
194 return True
195 elif self._cmp_num(self.upstream, v.upstream) < 0:
196 return True
197 elif self._cmp_lex(self.upstream_rev, v.upstream_rev) < 0:
198 return True
199 else:
200 return False
201
202 def __eq__(self, v):
203 # compare all portions
204 _result = []
205 _result.append(self._cmp_num(self.epoch, v.epoch))
206 _result.append(self._cmp_num(self.upstream, v.upstream))
207 _result.append(self._cmp_lex(self.upstream_rev, v.upstream_rev))
208 # if there is any non-zero, its not equal
209 return not any(_result)
210
211 def __gt__(self, v):
212 if self._cmp_num(self.epoch, v.epoch) > 0:
213 return True
214 elif self._cmp_num(self.upstream, v.upstream) > 0:
215 return True
216 elif self._cmp_lex(self.upstream_rev, v.upstream_rev) > 0:
217 return True
218 else:
219 return False
220
221 def update_parts(self, target, status):
222 # updating parts of version statuses
223 if self._cmp_num(self.epoch, target.epoch) != 0:
224 self.epoch_status = status
225 else:
226 self.epoch_status = const.VERSION_OK
227
228 if self._cmp_num(self.upstream, target.upstream) != 0 \
229 or self._cmp_lex(self.upstream_rev, target.upstream_rev) != 0:
230 self.upstream_status = status
231 else:
232 self.upstream_status = const.VERSION_OK
233
234 if self._cmp_lex(self.debian, target.debian) != 0 \
235 or self._cmp_lex(self.debian_rev, target.debian_rev) != 0:
236 self.debian_status = status
237 else:
238 self.debian_status = const.VERSION_OK
239
240
241class VersionCmpResult(object):
242 status = ""
243 action = ""
244
245 source = None
246 target = None
247
248
249 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
254
255 # 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
336
337 # 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:
345 return false