blob: 025703e212880f89fe2db3942544d4f1553cea29 [file] [log] [blame]
Alexd9fd85e2019-05-16 16:58:24 -05001import json
2import os
Alexd0391d42019-05-21 18:48:55 -05003import re
Alexd9fd85e2019-05-16 16:58:24 -05004from copy import deepcopy
5
Alex74dc1352019-05-17 13:18:24 -05006from cfg_checker.common import logger, logger_cli, nested_set
Alex0ed4f762019-05-17 17:55:33 -05007from cfg_checker.common.const import _mainteiners_index_filename
8from cfg_checker.common.const import _mirantis_versions_filename
9from cfg_checker.common.const import _other_versions_filename
Alexd9fd85e2019-05-16 16:58:24 -050010from cfg_checker.common.const import _pkg_desc_archive
11from cfg_checker.common.const import _repos_index_filename
12from cfg_checker.common.const import _repos_info_archive
13from cfg_checker.common.const import _repos_versions_archive
Alexd9fd85e2019-05-16 16:58:24 -050014from cfg_checker.common.const import ubuntu_releases
15from cfg_checker.common.file_utils import get_gzipped_file
16from cfg_checker.common.settings import pkg_dir
17from cfg_checker.helpers.console_utils import Progress
18from cfg_checker.helpers.tgz import TGZFile
19
20import requests
21from requests.exceptions import ConnectionError
22
23ext = ".json"
24
25
Alex0ed4f762019-05-17 17:55:33 -050026def get_tag_label(_tag, parsed=False):
Alex74dc1352019-05-17 13:18:24 -050027 # prettify the tag for printing
Alex0ed4f762019-05-17 17:55:33 -050028 if parsed:
29 _label = "+ "
30 else:
31 _label = " "
32
Alex74dc1352019-05-17 13:18:24 -050033 if _tag.endswith(".update"):
34 _label += "[updates] " + _tag.rsplit('.', 1)[0]
35 elif _tag.endswith(".hotfix"):
36 _label += " [hotfix] " + _tag.rsplit('.', 1)[0]
37 else:
38 _label += " "*10 + _tag
Alex0ed4f762019-05-17 17:55:33 -050039
Alex74dc1352019-05-17 13:18:24 -050040 return _label
41
42
Alex0ed4f762019-05-17 17:55:33 -050043def _get_value_index(_di, value, header=None):
Alex29ee76f2019-05-17 18:52:29 -050044 # Mainteiner names often uses specific chars
45 # so make sure that value saved is unicode not str
46 _val = unicode(value, 'utf-8') if isinstance(value, str) else value
Alex0ed4f762019-05-17 17:55:33 -050047 if header:
48 if not filter(lambda i: _di[i]["header"] == header, _di):
Alex29ee76f2019-05-17 18:52:29 -050049 _index = unicode(len(_di.keys()) + 1)
Alex0ed4f762019-05-17 17:55:33 -050050 _di[_index] = {
51 "header": header,
Alex29ee76f2019-05-17 18:52:29 -050052 "props": _val
Alex0ed4f762019-05-17 17:55:33 -050053 }
54 else:
55 for _k, _v in _di.iteritems():
56 if _v["header"] == header:
57 _index = _k
58
59 return _index
60 else:
Alex29ee76f2019-05-17 18:52:29 -050061 if not filter(lambda i: _di[i] == _val, _di):
62 _index = unicode(len(_di.keys()) + 1)
63 # on save, cast it as unicode
64 _di[_index] = _val
Alex0ed4f762019-05-17 17:55:33 -050065 else:
66 for _k, _v in _di.iteritems():
Alex29ee76f2019-05-17 18:52:29 -050067 if _v == _val:
Alex0ed4f762019-05-17 17:55:33 -050068 _index = _k
69
70 return _index
71
72
73def _safe_load(_f, _a):
74 if _f in _a.list_files():
Alexd0391d42019-05-21 18:48:55 -050075 logger_cli.debug(
76 "... loading '{}':'{}'".format(
Alex0ed4f762019-05-17 17:55:33 -050077 _a.basefile,
78 _f
79 )
80 )
81 return json.loads(_a.get_file(_f))
82 else:
83 return {}
84
85
Alexd9fd85e2019-05-16 16:58:24 -050086def _n_url(url):
87 if url[-1] == '/':
88 return url
89 else:
90 return url + '/'
91
92
93class ReposInfo(object):
94 repos = []
95 _repofile = os.path.join(pkg_dir, "versions", _repos_info_archive)
96
97 @staticmethod
98 def _ls_repo_page(url):
99 # Yes, this is ugly. But it works ok for small HTMLs.
100 _a = "<a"
101 _s = "href="
102 _e = "\">"
103 try:
104 page = requests.get(url, timeout=60)
105 except ConnectionError as e:
106 logger_cli.error("# ERROR: {}".format(e.message))
107 return [], []
108 a = page.text.splitlines()
109 # Comprehension for dirs. Anchors for ends with '-'
110 _dirs = [l[l.index(_s)+6:l.index(_e)-1]
111 for l in a if l.startswith(_a) and l.endswith('-')]
112 # Comprehension for files. Anchors ends with size
113 _files = [l[l.index(_s)+6:l.index(_e)]
114 for l in a if l.startswith(_a) and not l.endswith('-')]
115
116 return _dirs, _files
117
118 def search_pkg(self, url, _list):
119 # recoursive method to walk dists tree
120 _dirs, _files = self._ls_repo_page(url)
121
122 for _d in _dirs:
123 # Search only in dists, ignore the rest
124 if "dists" not in url and _d != "dists":
125 continue
126 _u = _n_url(url + _d)
127 self.search_pkg(_u, _list)
128
129 for _f in _files:
130 if _f == "Packages.gz":
131 _list.append(url + _f)
132 logger.debug("... [F] '{}'".format(url + _f))
133
134 return _list
135
136 @staticmethod
137 def _map_repo(_path_list, _r):
138 for _pkg_path in _path_list:
139 _l = _pkg_path.split('/')
140 _kw = _l[_l.index('dists')+1:]
141 _kw.reverse()
142 _repo_item = {
143 "arch": _kw[1][7:] if "binary" in _kw[1] else _kw[1],
144 "type": _kw[2],
145 "ubuntu-release": _kw[3],
146 "filepath": _pkg_path
147 }
148 _r.append(_repo_item)
149
150 def _find_tag(self, _t, _u, label=""):
151 if label:
152 _url = _n_url(_u + label)
153 _label = _t + '.' + label
154 else:
155 _url = _u
156 _label = _t
157 _ts, _ = self._ls_repo_page(_url)
158 if _t in _ts:
159 logger.debug(
160 "... found tag '{}' at '{}'".format(
161 _t,
162 _url
163 )
164 )
165 return {
166 _label: {
167 "baseurl": _n_url(_url + _t),
168 "all": {}
169 }
170 }
171 else:
172 return {}
173
174 def fetch_repos(self, url, tag=None):
175 base_url = _n_url(url)
176 logger_cli.info("# Using '{}' as a repos source".format(base_url))
177
178 logger_cli.info("# Gathering repos info (i.e. links to 'packages.gz')")
179 # init repoinfo archive
180 _repotgz = TGZFile(self._repofile)
181 # prepare repo links
182 _repos = {}
183 if tag:
184 # only one tag to process
185 _repos.update(self._find_tag(tag, base_url))
186 _repos.update(self._find_tag(tag, base_url, label="hotfix"))
187 _repos.update(self._find_tag(tag, base_url, label="update"))
188 else:
189 # gather all of them
190 _tags, _ = self._ls_repo_page(base_url)
191 _tags.remove('hotfix')
192 _tags.remove('update')
193 # search tags in subfolders
194 _h_tags, _ = self._ls_repo_page(base_url + 'hotfix')
195 _u_tags, _ = self._ls_repo_page(base_url + 'update')
196 _tags.extend([t for t in _h_tags if t not in _tags])
197 _tags.extend([t for t in _u_tags if t not in _tags])
198 _progress = Progress(len(_tags))
199 _index = 0
200 for _tag in _tags:
201 _repos.update(self._find_tag(_tag, base_url))
202 _repos.update(self._find_tag(_tag, base_url, label="hotfix"))
203 _repos.update(self._find_tag(_tag, base_url, label="update"))
204 _index += 1
205 _progress.write_progress(_index)
206 _progress.end()
207
208 # parse subtags
209 for _label in _repos.keys():
210 logger_cli.info("-> processing tag '{}'".format(_label))
211 _name = _label + ".json"
212 if _repotgz.has_file(_name):
213 logger_cli.info(
214 "-> skipping, '{}' already has '{}'".format(
215 _repos_info_archive,
216 _name
217 )
218 )
219 continue
220 # process the tag
221 _repo = _repos[_label]
222 _baseurl = _repos[_label]["baseurl"]
223 # get the subtags
224 _sub_tags, _ = self._ls_repo_page(_baseurl)
225 _total_index = len(_sub_tags)
226 _index = 0
227 _progress = Progress(_total_index)
228 logger.debug(
229 "... found {} subtags for '{}'".format(
230 len(_sub_tags),
231 _label
232 )
233 )
234 # save the url and start search
235 for _stag in _sub_tags:
236 _u = _baseurl + _stag
237 _index += 1
238 logger.debug(
239 "... searching repos in '{}/{}'".format(
240 _label,
241 _stag
242 )
243 )
244
245 # Searching Package collections
246 if _stag in ubuntu_releases:
247 # if stag is the release, this is all packages
248 _repo["all"][_stag] = []
249 _repo["all"]["url"] = _n_url(_u)
250 _path_list = self.search_pkg(_n_url(_u), [])
251 self._map_repo(_path_list, _repo["all"][_stag])
252 logger.info(
253 "-> found {} dists".format(
254 len(_repo["all"][_stag])
255 )
256 )
257
258 else:
259 # each subtag might have any ubuntu release
260 # so iterate them
261 _repo[_stag] = {
262 "url": _n_url(_u)
263 }
264 _releases, _ = self._ls_repo_page(_n_url(_u))
265 for _rel in _releases:
266 if _rel not in ubuntu_releases:
267 logger.debug(
268 "... skipped unknown ubuntu release: "
269 "'{}' in '{}'".format(
270 _rel,
271 _u
272 )
273 )
274 else:
275 _rel_u = _n_url(_u) + _rel
276 _repo[_stag][_rel] = []
277 _path_list = self.search_pkg(_n_url(_rel_u), [])
278 self._map_repo(
279 _path_list,
280 _repo[_stag][_rel]
281 )
282 logger.info(
283 "-> found {} dists for '{}'".format(
284 len(_repo[_stag][_rel]),
285 _rel
286 )
287 )
288 _progress.write_progress(_index)
289
290 _progress.end()
291 _name = _label + ext
292 _repotgz.add_file(_name, buf=json.dumps(_repo, indent=2))
293 logger_cli.info(
294 "-> archive '{}' updated with '{}'".format(
295 self._repofile,
296 _name
297 )
298 )
299
300 return
301
Alex74dc1352019-05-17 13:18:24 -0500302 def list_tags(self, splitted=False):
Alexd9fd85e2019-05-16 16:58:24 -0500303 _files = TGZFile(self._repofile).list_files()
304 # all files in archive with no '.json' part
305 _all = set([f.rsplit('.', 1)[0] for f in _files])
Alex74dc1352019-05-17 13:18:24 -0500306 if splitted:
307 # files that ends with '.update'
308 _updates = set([f for f in _all if f.find('update') >= 0])
309 # files that ends with '.hotfix'
310 _hotfix = set([f for f in _all if f.find('hotfix') >= 0])
311 # remove updates and hotfix tags from all. The true magic of SETs
312 _all = _all - _updates - _hotfix
313 # cut updates and hotfix endings
314 _updates = [f.rsplit('.', 1)[0] for f in _updates]
315 _hotfix = [f.rsplit('.', 1)[0] for f in _hotfix]
Alexd9fd85e2019-05-16 16:58:24 -0500316
Alex74dc1352019-05-17 13:18:24 -0500317 return _all, _updates, _hotfix
318 else:
319 # dynamic import
320 import re
321 _all = list(_all)
322 # lexical tags
323 _lex = [s for s in _all if not s[0].isdigit()]
324 _lex.sort()
325 # tags with digits
326 _dig = [s for s in _all if s[0].isdigit()]
327 _dig = sorted(
328 _dig,
Alexd0391d42019-05-21 18:48:55 -0500329 key=lambda x: tuple(int(i) for i in re.findall(r"\d+", x)[:3])
Alex74dc1352019-05-17 13:18:24 -0500330 )
331
332 return _dig + _lex
Alexd9fd85e2019-05-16 16:58:24 -0500333
334 def get_repoinfo(self, tag):
335 _tgz = TGZFile(self._repofile)
336 _buf = _tgz.get_file(tag + ext)
337 return json.loads(_buf)
338
339
340class RepoManager(object):
Alexd9fd85e2019-05-16 16:58:24 -0500341 # archives
342 _versions_arch = os.path.join(pkg_dir, "versions", _repos_versions_archive)
343 _desc_arch = os.path.join(pkg_dir, "versions", _pkg_desc_archive)
Alexd0391d42019-05-21 18:48:55 -0500344 _apps_filename = "apps.json"
Alexd9fd85e2019-05-16 16:58:24 -0500345
346 # repository index
347 _repo_index = {}
Alex0ed4f762019-05-17 17:55:33 -0500348 _mainteiners_index = {}
Alexd9fd85e2019-05-16 16:58:24 -0500349
Alexd0391d42019-05-21 18:48:55 -0500350 _apps = {}
351
Alexd9fd85e2019-05-16 16:58:24 -0500352 # init package versions storage
Alex0ed4f762019-05-17 17:55:33 -0500353 _versions_mirantis = {}
354 _versions_other = {}
Alexd9fd85e2019-05-16 16:58:24 -0500355
356 def __init__(self):
357 # Init version files
358 self.versionstgz = TGZFile(
359 self._versions_arch,
360 label="MCP Configuration Checker: Package versions archive"
361 )
362 self.desctgz = TGZFile(
363 self._desc_arch,
364 label="MCP Configuration Checker: Package descriptions archive"
365 )
Alexd0391d42019-05-21 18:48:55 -0500366
367 # section / app
368 self._apps = _safe_load(
369 self._apps_filename,
370 self.desctgz
371 )
372
Alex0ed4f762019-05-17 17:55:33 -0500373 # indices
374 self._repo_index = _safe_load(
375 _repos_index_filename,
376 self.versionstgz
377 )
378 self._mainteiners_index = _safe_load(
379 _mainteiners_index_filename,
380 self.versionstgz
381 )
Alexd9fd85e2019-05-16 16:58:24 -0500382
Alex0ed4f762019-05-17 17:55:33 -0500383 # versions
384 self._versions_mirantis = _safe_load(
385 _mirantis_versions_filename,
386 self.versionstgz
387 )
388 self._versions_other = _safe_load(
389 _other_versions_filename,
390 self.versionstgz
391 )
Alexd9fd85e2019-05-16 16:58:24 -0500392
393 def _create_repo_header(self, p):
394 _header = "_".join([
395 p['tag'],
396 p['subset'],
397 p['release'],
398 p['ubuntu-release'],
399 p['type'],
400 p['arch']
401 ])
Alex0ed4f762019-05-17 17:55:33 -0500402 return _get_value_index(self._repo_index, p, header=_header)
Alexd9fd85e2019-05-16 16:58:24 -0500403
Alex0ed4f762019-05-17 17:55:33 -0500404 def _get_indexed_values(self, pair):
405 _h, _m = pair.split('-')
406 return self._repo_index[_h], self._mainteiners_index[_m]
Alexd9fd85e2019-05-16 16:58:24 -0500407
Alexd0391d42019-05-21 18:48:55 -0500408 def _update_pkg_version(self, _d, n, v, md5, s, a, h_index, m_index):
Alexd9fd85e2019-05-16 16:58:24 -0500409 """Method updates package version record in global dict
410 """
411 # 'if'*4 operation is pretty expensive when using it 100k in a row
412 # so try/except is a better way to go, even faster than 'reduce'
Alex0ed4f762019-05-17 17:55:33 -0500413 _pair = "-".join([h_index, m_index])
Alexd0391d42019-05-21 18:48:55 -0500414 _info = {
415 'repo': [_pair],
416 'section': s,
417 'app': a
418 }
Alexd9fd85e2019-05-16 16:58:24 -0500419 try:
420 # try to load list
Alexd0391d42019-05-21 18:48:55 -0500421 _list = _d[n][v][md5]['repo']
Alexd9fd85e2019-05-16 16:58:24 -0500422 # cast it as set() and union()
Alex0ed4f762019-05-17 17:55:33 -0500423 _list = set(_list).union([_pair])
Alexd9fd85e2019-05-16 16:58:24 -0500424 # cast back as set() is not serializeable
Alexd0391d42019-05-21 18:48:55 -0500425 _d[n][v][md5]['repo'] = list(_list)
Alexd9fd85e2019-05-16 16:58:24 -0500426 return False
427 except KeyError:
428 # ok, this is fresh pkg. Do it slow way.
Alex0ed4f762019-05-17 17:55:33 -0500429 if n in _d:
Alexd9fd85e2019-05-16 16:58:24 -0500430 # there is such pkg already
Alex0ed4f762019-05-17 17:55:33 -0500431 if v in _d[n]:
Alexd9fd85e2019-05-16 16:58:24 -0500432 # there is such version, check md5
Alex0ed4f762019-05-17 17:55:33 -0500433 if md5 in _d[n][v]:
Alexd9fd85e2019-05-16 16:58:24 -0500434 # just add new repo header
Alexd0391d42019-05-21 18:48:55 -0500435 if _pair not in _d[n][v][md5]['repo']:
436 _d[n][v][md5]['repo'].append(_pair)
Alexd9fd85e2019-05-16 16:58:24 -0500437 else:
438 # check if such index is here...
439 _existing = filter(
Alexd0391d42019-05-21 18:48:55 -0500440 lambda i: _pair in _d[n][v][i]['repo'],
Alex0ed4f762019-05-17 17:55:33 -0500441 _d[n][v]
Alexd9fd85e2019-05-16 16:58:24 -0500442 )
443 if _existing:
444 # Yuck! Same version had different MD5
Alex0ed4f762019-05-17 17:55:33 -0500445 _r, _m = self._get_indexed_values(_pair)
Alexd9fd85e2019-05-16 16:58:24 -0500446 logger_cli.error(
447 "# ERROR: Package version has multiple MD5s "
448 "in '{}': {}:{}:{}".format(
Alex0ed4f762019-05-17 17:55:33 -0500449 _r,
Alexd9fd85e2019-05-16 16:58:24 -0500450 n,
451 v,
452 md5
453 )
454 )
Alexd0391d42019-05-21 18:48:55 -0500455 _d[n][v][md5] = _info
Alexd9fd85e2019-05-16 16:58:24 -0500456 else:
457 # this is new version for existing package
Alex0ed4f762019-05-17 17:55:33 -0500458 _d[n][v] = {
Alexd0391d42019-05-21 18:48:55 -0500459 md5: _info
Alexd9fd85e2019-05-16 16:58:24 -0500460 }
461 return False
462 else:
463 # this is new pakcage
Alex0ed4f762019-05-17 17:55:33 -0500464 _d[n] = {
Alexd9fd85e2019-05-16 16:58:24 -0500465 v: {
Alexd0391d42019-05-21 18:48:55 -0500466 md5: _info
Alexd9fd85e2019-05-16 16:58:24 -0500467 }
468 }
469 return True
470
471 def _save_repo_descriptions(self, repo_props, desc):
472 # form the filename for the repo and save it
473 self.desctgz.add_file(
474 self._create_repo_header(repo_props),
475 json.dumps(desc)
476 )
477
478 # def get_description(self, repo_props, name, md5=None):
479 # """Gets target description
480 # """
481 # _filename = self._create_repo_header(repo_props)
482 # # check if it is present in cache
483 # if _filename in self._desc_cache:
484 # _descs = self._desc_cache[_filename]
485 # else:
486 # # load data
487 # _descs = self.desctgz.get_file(_filename)
488 # # Serialize it
489 # _descs = json.loads(_descs)
490 # self._desc_cache[_filename] = _descs
491 # # return target desc
492 # if name in _descs and md5 in _descs[name]:
493 # return _descs[name][md5]
494 # else:
495 # return None
496
Alexd0391d42019-05-21 18:48:55 -0500497 def parse_tag(self, tag, descriptions=False, apps=False):
Alexd9fd85e2019-05-16 16:58:24 -0500498 """Download and parse Package.gz files for specific tag
499 By default, descriptions not saved
500 due to huge resulting file size and slow processing
501 """
502 # init gzip and downloader
503 _info = ReposInfo().get_repoinfo(tag)
504 # calculate Packages.gz files to process
505 _baseurl = _info.pop("baseurl")
506 _total_components = len(_info.keys()) - 1
507 _ubuntu_package_repos = 0
508 _other_repos = 0
509 for _c, _d in _info.iteritems():
510 for _ur, _l in _d.iteritems():
511 if _ur in ubuntu_releases:
512 _ubuntu_package_repos += len(_l)
513 elif _ur != 'url':
514 _other_repos += len(_l)
515 logger_cli.info(
516 "-> loaded repository info for '{}'.\n"
517 " '{}', {} components, {} ubuntu repos, {} other/uknown".format(
518 _baseurl,
519 tag,
520 _total_components,
521 _ubuntu_package_repos,
522 _other_repos
523 )
524 )
525 # init progress bar
526 _progress = Progress(_ubuntu_package_repos)
527 _index = 0
528 _processed = 0
529 _new = 0
530 for _c, _d in _info.iteritems():
531 # we do not need url here, just get rid of it
532 if 'url' in _d:
533 _d.pop('url')
534 # _url = if 'url' in _d else _baseurl + _c
535 for _ur, _l in _d.iteritems():
536 # iterate package collections
537 for _p in _l:
538 # descriptions
539 if descriptions:
540 _descriptions = {}
541 # download and unzip
Alexd0391d42019-05-21 18:48:55 -0500542 _index += 1
543 _progress.write_progress(
544 _index,
545 note="/ {} {} {} {} {}, GET 'Packages.gz'".format(
546 _c,
547 _ur,
548 _p['ubuntu-release'],
549 _p['type'],
550 _p['arch']
551 )
552 )
553 _raw = get_gzipped_file(_p['filepath'])
554 if not _raw:
555 # empty repo...
556 _progress.clearline()
557 logger_cli.warning(
558 "# WARNING: Empty file: '{}'".format(
559 _p['filepath']
560 )
561 )
562 continue
Alexd9fd85e2019-05-16 16:58:24 -0500563 _progress.write_progress(
564 _index,
565 note="/ {} {} {} {} {}, {}/{}".format(
566 _c,
567 _ur,
568 _p['ubuntu-release'],
569 _p['type'],
570 _p['arch'],
571 _processed,
572 _new
573 )
574 )
Alexd9fd85e2019-05-16 16:58:24 -0500575 _lines = _raw.splitlines()
Alexd9fd85e2019-05-16 16:58:24 -0500576 # break lines collection into isolated pkg data
577 _pkg = {
578 "tag": tag,
579 "subset": _c,
580 "release": _ur
581 }
582 _pkg.update(_p)
583 _desc = {}
584 _key = _value = ""
Alexd0391d42019-05-21 18:48:55 -0500585 # if there is no empty line at end, add it
586 if _lines[-1] != '':
587 _lines.append('')
588 # Process lines
Alexd9fd85e2019-05-16 16:58:24 -0500589 for _line in _lines:
590 if not _line:
591 # if the line is empty, process pkg data gathered
592 _name = _desc['package']
593 _md5 = _desc['md5sum']
594 _version = _desc['version']
Alex0ed4f762019-05-17 17:55:33 -0500595 _mainteiner = _desc['maintainer']
596
Alexd0391d42019-05-21 18:48:55 -0500597 if 'source' in _desc:
598 _ap = _desc['source'].lower()
599 else:
600 _ap = "-"
601
602 if apps:
603 # insert app
604 _sc = _desc['section'].lower()
605 if 'source' in _desc:
606 _ap = _desc['source'].lower()
607 else:
608 _ap = "-"
609
610 try:
611 _tmp = set(self._apps[_sc][_ap][_name])
612 _tmp.add(_desc['architecture'])
613 self._apps[_sc][_ap][_name] = list(_tmp)
614 except KeyError:
615 nested_set(
616 self._apps,
617 [_sc, _ap, _name],
618 [_desc['architecture']]
619 )
620
Alex0ed4f762019-05-17 17:55:33 -0500621 # Check is mainteiner is Mirantis
622 if _mainteiner.endswith("@mirantis.com>"):
623 # update mirantis versions
624 if self._update_pkg_version(
625 self._versions_mirantis,
626 _name,
627 _version,
628 _md5,
Alexd0391d42019-05-21 18:48:55 -0500629 _desc['section'].lower(),
630 _ap,
Alex0ed4f762019-05-17 17:55:33 -0500631 self._create_repo_header(_pkg),
632 _get_value_index(
633 self._mainteiners_index,
634 _mainteiner
635 )
636 ):
637 _new += 1
638 else:
639 # update other versions
640 if self._update_pkg_version(
641 self._versions_other,
642 _name,
643 _version,
644 _md5,
Alexd0391d42019-05-21 18:48:55 -0500645 _desc['section'].lower(),
646 _ap,
Alex0ed4f762019-05-17 17:55:33 -0500647 self._create_repo_header(_pkg),
648 _get_value_index(
649 self._mainteiners_index,
650 _mainteiner
651 )
652 ):
653 _new += 1
Alexd9fd85e2019-05-16 16:58:24 -0500654
655 if descriptions:
656 _d_new = {
657 _md5: deepcopy(_desc)
658 }
659 try:
660 _descriptions[_name].update(_d_new)
661 except KeyError:
662 _descriptions[_name] = _d_new
663 # clear the data for next pkg
664 _processed += 1
665 _desc = {}
666 _key = ""
667 _value = ""
668 elif _line.startswith(' '):
669 _desc[_key] += "\n{}".format(_line)
670 else:
671 _key, _value = _line.split(': ', 1)
672 _key = _key.lower()
673
674 _desc[_key] = _value
675 # save descriptions if needed
676 if descriptions:
677 _progress.clearline()
678 self._save_repo_descriptions(_pkg, _descriptions)
679
680 _progress.end()
681 # backup headers to disk
682 self.versionstgz.add_file(
Alex0ed4f762019-05-17 17:55:33 -0500683 _repos_index_filename,
Alexd9fd85e2019-05-16 16:58:24 -0500684 json.dumps(self._repo_index),
685 replace=True
686 )
Alex0ed4f762019-05-17 17:55:33 -0500687 self.versionstgz.add_file(
688 _mainteiners_index_filename,
689 json.dumps(self._mainteiners_index),
690 replace=True
691 )
Alexd0391d42019-05-21 18:48:55 -0500692 if apps:
693 self.desctgz.add_file(
694 self._apps_filename,
695 json.dumps(self._apps),
696 replace=True
697 )
698
Alexd9fd85e2019-05-16 16:58:24 -0500699 return
700
Alexd0391d42019-05-21 18:48:55 -0500701 def fetch_versions(self, tag, descriptions=False, apps=False):
Alexd9fd85e2019-05-16 16:58:24 -0500702 """Executes parsing for specific tag
703 """
704 if descriptions:
705 logger_cli.warning(
706 "\n\n# !!! WARNING: Saving repo descriptions "
707 "consumes huge amount of disk space\n\n"
708 )
709 # if there is no such tag, parse it from repoinfo
Alexd9fd85e2019-05-16 16:58:24 -0500710 logger_cli.info("# Fetching versions for {}".format(tag))
Alexd0391d42019-05-21 18:48:55 -0500711 self.parse_tag(tag, descriptions=descriptions, apps=apps)
Alex0ed4f762019-05-17 17:55:33 -0500712 logger_cli.info("-> saving updated versions")
713 self.versionstgz.add_file(
714 _mirantis_versions_filename,
715 json.dumps(self._versions_mirantis),
716 replace=True
717 )
718 self.versionstgz.add_file(
719 _other_versions_filename,
720 json.dumps(self._versions_other),
721 replace=True
722 )
Alexd9fd85e2019-05-16 16:58:24 -0500723
724 def build_repos(self, url, tag=None):
725 """Builds versions data for selected tag, or for all of them
726 """
727 # Init the ReposInfo class and check if all files are present
728 _repos = ReposInfo()
729 # recoursively walk the mirrors
730 # and gather all of the repos for 'tag' or all of the tags
731 _repos.fetch_repos(url, tag=tag)
732
Alex74dc1352019-05-17 13:18:24 -0500733 def _build_action(self, url, tags):
734 for t in tags:
735 logger_cli.info(
736 "# Building repo info for '{}/{}'".format(
737 url,
738 t
739 )
740 )
741 self.build_repos(url, tag=t)
742
Alexd0391d42019-05-21 18:48:55 -0500743 def get_available_tags(self, tag=None):
744 # Populate action tags
745 major, updates, hotfix = ReposInfo().list_tags(splitted=True)
746
747 _tags = []
748 if tag in major:
749 _tags.append(tag)
750 if tag in updates:
751 _tags.append(tag + ".update")
752 if tag in hotfix:
753 _tags.append(tag + ".hotfix")
754
755 return _tags
756
Alexd9fd85e2019-05-16 16:58:24 -0500757 def action_for_tag(
758 self,
759 url,
760 tag,
761 action=None,
Alexd0391d42019-05-21 18:48:55 -0500762 descriptions=None,
763 apps=None
Alexd9fd85e2019-05-16 16:58:24 -0500764 ):
765 """Executes action for every tag from all collections
766 """
767 if not action:
768 logger_cli.info("# No action set, nothing to do")
Alex74dc1352019-05-17 13:18:24 -0500769 # See if this is a list action
Alexd9fd85e2019-05-16 16:58:24 -0500770 if action == "list":
Alex74dc1352019-05-17 13:18:24 -0500771 _all = ReposInfo().list_tags()
772 # Print pretty list and exit
Alexd9fd85e2019-05-16 16:58:24 -0500773 logger_cli.info("# Tags available at '{}':".format(url))
Alex74dc1352019-05-17 13:18:24 -0500774 for t in _all:
Alex0ed4f762019-05-17 17:55:33 -0500775 _ri = self._repo_index
776 _isparsed = any(
777 [k for k, v in _ri.iteritems() if v['props']['tag'] == t]
778 )
779 if _isparsed:
780 logger_cli.info(get_tag_label(t, parsed=True))
781 else:
782 logger_cli.info(get_tag_label(t))
Alex74dc1352019-05-17 13:18:24 -0500783 # exit
Alexd9fd85e2019-05-16 16:58:24 -0500784 return
Alex74dc1352019-05-17 13:18:24 -0500785
Alexd0391d42019-05-21 18:48:55 -0500786 # Populate action tags
787 _action_tags = self.get_available_tags(tag)
788
Alexd9fd85e2019-05-16 16:58:24 -0500789 if not _action_tags:
790 logger_cli.info(
791 "# Tag of '{}' not found. "
792 "Consider rebuilding repos info.".format(tag)
793 )
Alex74dc1352019-05-17 13:18:24 -0500794 else:
Alexd9fd85e2019-05-16 16:58:24 -0500795 logger_cli.info(
Alex74dc1352019-05-17 13:18:24 -0500796 "-> tags to process: {}".format(
Alexd9fd85e2019-05-16 16:58:24 -0500797 ", ".join(_action_tags)
798 )
799 )
Alex74dc1352019-05-17 13:18:24 -0500800 # Execute actions
801 if action == "build":
802 self._build_action(url, _action_tags)
803 elif action == "fetch":
Alexd9fd85e2019-05-16 16:58:24 -0500804 for t in _action_tags:
Alexd0391d42019-05-21 18:48:55 -0500805 self.fetch_versions(t, descriptions=descriptions, apps=apps)
Alexd9fd85e2019-05-16 16:58:24 -0500806
807 logger_cli.info("# Done.")
808
Alex74dc1352019-05-17 13:18:24 -0500809 def show_package(self, name):
810 # get the package data
811 _p = self.get_package_versions(name)
812 if not _p:
813 logger_cli.warning(
814 "# WARNING: Package '{}' not found".format(name)
815 )
816 else:
817 # print package info using sorted tags from headers
818 # Package: name
819 # [u/h] tag \t <version>
820 # \t <version>
821 # <10symbols> \t <md5> \t sorted headers with no tag
822 # ...
Alexd0391d42019-05-21 18:48:55 -0500823 # section
Alex92e07ce2019-05-31 16:00:03 -0500824 for _s in sorted(_p):
Alexd0391d42019-05-21 18:48:55 -0500825 # app
Alex92e07ce2019-05-31 16:00:03 -0500826 for _a in sorted(_p[_s]):
Alexcf91b182019-05-31 11:57:07 -0500827 _o = ""
828 _mm = []
Alexd0391d42019-05-21 18:48:55 -0500829 # get and sort tags
Alex92e07ce2019-05-31 16:00:03 -0500830 for _v in sorted(_p[_s][_a]):
Alexd0391d42019-05-21 18:48:55 -0500831 _o += "\n" + " "*8 + _v + ':\n'
832 # get and sort tags
Alex92e07ce2019-05-31 16:00:03 -0500833 for _md5 in sorted(_p[_s][_a][_v]):
Alexd0391d42019-05-21 18:48:55 -0500834 _o += " "*16 + _md5 + "\n"
835 # get and sort repo headers
Alex92e07ce2019-05-31 16:00:03 -0500836 for _r in sorted(_p[_s][_a][_v][_md5]):
Alexcf91b182019-05-31 11:57:07 -0500837 _o += " "*24 + _r.replace('_', ' ') + '\n'
838 _m = _p[_s][_a][_v][_md5][_r]["maintainer"]
839 if _m not in _mm:
840 _mm.append(_m)
Alex74dc1352019-05-17 13:18:24 -0500841
Alexcf91b182019-05-31 11:57:07 -0500842 logger_cli.info(
843 "\n# Package: {}/{}/{}\nMaintainers: {}".format(
844 _s,
845 _a,
846 name,
847 ", ".join(_mm)
848 )
849 )
850
851 logger_cli.info(_o)
Alex74dc1352019-05-17 13:18:24 -0500852
Alexd0391d42019-05-21 18:48:55 -0500853 @staticmethod
854 def get_apps(versions, name):
855 _all = True if name == '*' else False
Alexcf91b182019-05-31 11:57:07 -0500856 _s_max = _a_max = _p_max = _v_max = 0
Alexd0391d42019-05-21 18:48:55 -0500857 _rows = []
858 for _p in versions.keys():
859 _vs = versions[_p]
860 for _v, _d1 in _vs.iteritems():
861 for _md5, _info in _d1.iteritems():
862 if _all or name == _info['app']:
863 _s_max = max(len(_info['section']), _s_max)
864 _a_max = max(len(_info['app']), _a_max)
Alexcf91b182019-05-31 11:57:07 -0500865 _p_max = max(len(_p), _p_max)
866 _v_max = max(len(_v), _v_max)
Alexd0391d42019-05-21 18:48:55 -0500867 _rows.append([
868 _info['section'],
869 _info['app'],
Alexcf91b182019-05-31 11:57:07 -0500870 _p,
871 _v,
872 _md5,
873 len(_info['repo'])
Alexd0391d42019-05-21 18:48:55 -0500874 ])
Alexcf91b182019-05-31 11:57:07 -0500875 # format columns
876 # section
877 _fmt = "{:"+str(_s_max)+"} "
878 # app
879 _fmt += "{:"+str(_a_max)+"} "
880 # package name
881 _fmt += "{:"+str(_p_max)+"} "
882 # version
883 _fmt += "{:"+str(_v_max)+"} "
884 # md5 and number of repos is fixed
885 _fmt += "{} in {} repos"
886
887 # fill rows
888 _rows = [_fmt.format(s, a, p, v, m, l) for s, a, p, v, m, l in _rows]
Alexd0391d42019-05-21 18:48:55 -0500889 _rows.sort()
890 return _rows
891
892 def show_app(self, name):
893 c = 0
894 rows = self.get_apps(self._versions_mirantis, name)
895 if rows:
Alexcf91b182019-05-31 11:57:07 -0500896 logger_cli.info("\n# Mirantis packages for '{}'".format(name))
Alexd0391d42019-05-21 18:48:55 -0500897 logger_cli.info("\n".join(rows))
898 c += 1
899 rows = self.get_apps(self._versions_other, name)
900 if rows:
Alexcf91b182019-05-31 11:57:07 -0500901 logger_cli.info("\n# Other packages for '{}'".format(name))
Alexd0391d42019-05-21 18:48:55 -0500902 logger_cli.info("\n".join(rows))
903 c += 1
904 if c == 0:
905 logger_cli.info("\n# No app found for '{}'".format(name))
906
907 def get_mirantis_pkg_names(self):
908 # Mirantis maintainers only
909 return set(
910 self._versions_mirantis.keys()
911 ) - set(
912 self._versions_other.keys()
913 )
914
915 def get_other_pkg_names(self):
916 # Non-mirantis Maintainers
917 return set(
918 self._versions_other.keys()
919 ) - set(
920 self._versions_mirantis.keys()
921 )
922
923 def get_mixed_pkg_names(self):
924 # Mixed maintainers
925 return set(
926 self._versions_mirantis.keys()
927 ).intersection(set(
928 self._versions_other.keys()
929 ))
930
931 def is_mirantis(self, name, tag=None):
932 """Method checks if this package is mainteined
933 by mirantis in target tag repo
934 """
935 if name in self._versions_mirantis:
936 # check tag
937 if tag:
938 _pkg = self.get_package_versions(
939 name,
940 tagged=True
941 )
942 _tags = []
943 for s in _pkg.keys():
944 for a in _pkg[s].keys():
945 for t in _pkg[s][a].keys():
946 _tags.append(t)
947 if any([t.startswith(tag) for t in _tags]):
948 return True
949 else:
950 return None
951 else:
952 return True
953 elif name in self._versions_other:
954 # check tag
955 if tag:
956 _pkg = self.get_package_versions(
957 name,
958 tagged=True
959 )
960 _tags = []
961 for s in _pkg.keys():
962 for a in _pkg[s].keys():
963 for t in _pkg[s][a].keys():
964 _tags.append(t)
965 if any([t.startswith(tag) for t in _tags]):
966 return False
967 else:
968 return None
969 else:
970 return False
971 else:
972 logger.error(
973 "# ERROR: package '{}' not found "
974 "while determining maintainer".format(
975 name
976 )
977 )
978 return None
979
980 def get_filtered_versions(
981 self,
982 name,
983 tag=None,
984 include=None,
985 exclude=None
986 ):
987 """Method gets all the versions for the package
988 and filters them using keys above
989 """
990 if tag:
991 tag = unicode(tag) if not isinstance(tag, unicode) else tag
992 _out = {}
993 _vs = self.get_package_versions(name, tagged=True)
994 # iterate to filter out keywords
995 for s, apps in _vs.iteritems():
996 for a, _tt in apps.iteritems():
997 for t, vs in _tt.iteritems():
998 # filter tags
999 if tag and t != tag and t.rsplit('.', 1)[0] != tag:
1000 continue
1001 # Skip hotfix tag
1002 if t == tag + ".hotfix":
1003 continue
1004 for v, rp in vs.iteritems():
1005 for h, p in rp.iteritems():
1006 # filter headers with all keywords matching
1007 _h = re.split(r"[\-\_]+", h)
1008 _included = all([kw in _h for kw in include])
1009 _excluded = any([kw in _h for kw in exclude])
1010 if not _included or _excluded:
1011 continue
1012 else:
1013 nested_set(_out, [s, a, v], [])
1014 _dat = {
1015 "header": h
1016 }
1017 _dat.update(p)
1018 _out[s][a][v].append(_dat)
1019 return _out
1020
1021 def get_package_versions(self, name, tagged=False):
Alex74dc1352019-05-17 13:18:24 -05001022 """Method builds package version structure
1023 with repository properties included
1024 """
1025 # get data
Alexd0391d42019-05-21 18:48:55 -05001026 _vs = {}
1027
1028 if name in self._versions_mirantis:
1029 _vs.update(self._versions_mirantis[name])
1030 if name in self._versions_other:
1031 _vs.update(self._versions_other[name])
Alex0ed4f762019-05-17 17:55:33 -05001032
Alex74dc1352019-05-17 13:18:24 -05001033 # insert repo data, insert props into headers place
1034 _package = {}
1035 if tagged:
1036 for _v, _d1 in _vs.iteritems():
1037 # use tag as a next step
Alexd0391d42019-05-21 18:48:55 -05001038 for _md5, _info in _d1.iteritems():
1039 _s = _info['section']
1040 _a = _info['app']
1041 for _pair in _info['repo']:
1042 _rp = {}
Alex74dc1352019-05-17 13:18:24 -05001043 # extract props for a repo
Alex0ed4f762019-05-17 17:55:33 -05001044 _r, _m = self._get_indexed_values(_pair)
Alex74dc1352019-05-17 13:18:24 -05001045 # get tag
Alex0ed4f762019-05-17 17:55:33 -05001046 _tag = _r["props"]["tag"]
Alex74dc1352019-05-17 13:18:24 -05001047 # cut tag from the header
Alex0ed4f762019-05-17 17:55:33 -05001048 _cut_head = _r["header"].split("_", 1)[1]
Alex74dc1352019-05-17 13:18:24 -05001049 # populate dict
Alexd0391d42019-05-21 18:48:55 -05001050 _rp["maintainer"] = _m
1051 _rp["md5"] = _md5
1052 _rp.update(_r["props"])
Alex74dc1352019-05-17 13:18:24 -05001053 nested_set(
1054 _package,
Alexd0391d42019-05-21 18:48:55 -05001055 [_s, _a, _tag, _v, _cut_head],
1056 _rp
Alex74dc1352019-05-17 13:18:24 -05001057 )
1058 else:
1059 for _v, _d1 in _vs.iteritems():
Alexd0391d42019-05-21 18:48:55 -05001060 for _md5, _info in _d1.iteritems():
1061 _s = _info['section']
1062 _a = _info['app']
1063 for _pair in _info['repo']:
Alex0ed4f762019-05-17 17:55:33 -05001064 _r, _m = self._get_indexed_values(_pair)
Alexd0391d42019-05-21 18:48:55 -05001065 _info["maintainer"] = _m
1066 _info.update(_r["props"])
Alex74dc1352019-05-17 13:18:24 -05001067 nested_set(
1068 _package,
Alexd0391d42019-05-21 18:48:55 -05001069 [_s, _a, _v, _md5, _r["header"]],
1070 _info
Alex74dc1352019-05-17 13:18:24 -05001071 )
1072
1073 return _package
1074
Alexd9fd85e2019-05-16 16:58:24 -05001075 def parse_repos(self):
1076 # all tags to check
Alex74dc1352019-05-17 13:18:24 -05001077 major, updates, hotfix = ReposInfo().list_tags(splitted=True)
Alexd9fd85e2019-05-16 16:58:24 -05001078
1079 # major tags
1080 logger_cli.info("# Processing major tags")
1081 for _tag in major:
1082 self.fetch_versions(_tag)
1083
1084 # updates tags
1085 logger_cli.info("# Processing update tags")
1086 for _tag in updates:
1087 self.fetch_versions(_tag + ".update")
1088
1089 # hotfix tags
1090 logger_cli.info("# Processing hotfix tags")
1091 for _tag in hotfix:
1092 self.fetch_versions(_tag + ".hotfix")