123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259 |
- #!/bin/env python
- """
- Update stats about XCP-ng RPMs.
- Prerequisites:
- - a directory that contains
- - a local CentOS repo of SRPMs, all SRPMs in the first-level directory. Directory name: centos.
- - a local EPEL repo of SRPMs, all SRPMs in the first-level directory. Directory name: epel.
- - a local XCP-ng repo of SRPMs, all SRPMs in the first-level directory. Directory name: xcp-ng/{version}, e.g. xcp-ng/8.0
- - a working directory: workdir
- - the user running the script must have access to koji through cli
- """
- from __future__ import print_function
- import argparse
- import subprocess
- import os
- import rpm
- import json
- import urllib2
- DEVNULL = open(os.devnull, 'w')
- MAIN_TAGS = ['base', 'updates', 'candidates', 'testing', 'ci']
- def check_dir(dirpath):
- if not os.path.isdir(dirpath):
- raise Exception("Directory %s doesn't exist" % dirpath)
- return dirpath
- def list_tags_for_version(version):
- tags = subprocess.check_output(['koji', 'list-tags', 'v%s*' % version]).splitlines()
- sorted_tags = []
- # first get the main tags
- for tag_suffix in MAIN_TAGS:
- t = 'v%s-%s' % (version, tag_suffix)
- if t in tags:
- sorted_tags.append(t)
- tags.remove(t)
- # # add the remaining tags
- # # UPDATE: not adding them anymore until we download the corresponding SRPMs and RPMs from koji
- # # and can add them as repos to be used by rpmwatcher_extract_deps.py
- # sorted_tags += sorted(tags)
- return sorted_tags
- def get_builds_for_tag(tag, latest=False):
- builds = []
- param_latest = ['--latest'] if latest else []
- lines = subprocess.check_output(['koji', 'list-tagged', tag, '--quiet'] + param_latest).splitlines()
- for line in lines:
- srpm_nvr = line.split()[0]
- builds.append(srpm_nvr)
- return sorted(builds)
- def get_info_from_srpm_file(filepath):
- if not os.path.exists(filepath):
- return {}
- output = subprocess.check_output(
- ['rpm', '-qp', filepath, '--qf', '%{name};;%{vendor};;%{summary};;%{nvr};;%{epoch};;%{version};;%{release}'],
- stderr=DEVNULL).split(';;')
- return {
- 'name': output[0],
- 'vendor': output[1],
- 'summary': output[2],
- 'nvr': output[3],
- 'epoch': '' if output[4] == '(none)' else output[4],
- 'version': output[5],
- 'release': output[6],
- }
- def get_latest_srpms_info_from_dir(dirpath):
- result = {}
- for filename in os.listdir(dirpath):
- info = get_info_from_srpm_file(os.path.join(dirpath, filename))
- if info['name'] not in result:
- result[info['name']] = info
- else:
- # keep only the newest
- prev_info = result[info['name']]
- if rpm.labelCompare((prev_info['epoch'], prev_info['version'], prev_info['release']),
- (info['epoch'], info['version'], info['release'])) < 0:
- result[info['name']] = info
- return result
- def version_release(version, release):
- return version + '-' + release
- def main():
- parser = argparse.ArgumentParser(description='Update stats about XCP-ng RPMs')
- parser.add_argument('version', help='XCP-ng 2-digit version, e.g. 8.0')
- parser.add_argument('basedir', help='path to the base directory where repos must be present and where '
- 'we\'ll output results.')
- args = parser.parse_args()
- base_dir = os.path.abspath(check_dir(args.basedir))
- xcp_version = args.version
- xcp_srpm_repo = check_dir(os.path.join(base_dir, 'xcp-ng', xcp_version))
- centos_srpm_repo = check_dir(os.path.join(base_dir, 'centos'))
- epel_srpm_repo = check_dir(os.path.join(base_dir, 'epel'))
- work_dir = check_dir(os.path.join(base_dir, 'workdir', xcp_version))
- built_by = {}
- built_by['centos'] = get_builds_for_tag('built-by-centos')
- built_by['epel'] = get_builds_for_tag('built-by-epel')
- built_by['xs'] = get_builds_for_tag('built-by-xs')
- built_by['xcp-ng'] = get_builds_for_tag('built-by-xcp-ng')
- # Get "manually written" information about our packages
- expected_headers = ['SRPM_name', 'added_by', 'import_reason', 'latest_release_URL', 'latest_release_regexp']
- filename = "packages_provenance.csv"
- provenance_csv = urllib2.urlopen(
- 'https://raw.githubusercontent.com/xcp-ng/xcp/master/data/%s/%s' % (xcp_version, filename)
- ).read()
- provenance_csv = [line.split(';') for line in provenance_csv.splitlines()]
- csv_headers = provenance_csv[0]
- if csv_headers != expected_headers:
- raise Exception("The headers in %s were different from what was expected. Expected %s, got %s."
- % (filename, expected_headers, csv_headers))
- provenance_csv = provenance_csv[1:]
- # If we have information from previous runs, use that to detect updated packages
- previous_centos_file_path = os.path.join(work_dir, 'centos-srpms.json')
- centos_srpms_previous = {}
- if os.path.exists(previous_centos_file_path):
- with open(previous_centos_file_path) as f:
- centos_srpms_previous = json.load(f)
- previous_epel_file_path = os.path.join(work_dir, 'epel-srpms.json')
- epel_srpms_previous = {}
- if os.path.exists(previous_epel_file_path):
- with open(previous_epel_file_path) as f:
- epel_srpms_previous = json.load(f)
- # Read centos and epel repos
- # This takes time because each SRPM will be read by rpm -qp --qf
- # We could speed this up a lot by parsing repodata
- centos_srpms = get_latest_srpms_info_from_dir(centos_srpm_repo)
- epel_srpms = get_latest_srpms_info_from_dir(epel_srpm_repo)
- tags = list_tags_for_version(xcp_version)
- xcp_builds = {}
- excluded_builds = []
- latest_release_by_name = {}
- for tag in tags:
- builds = get_builds_for_tag(tag, latest=True)
- for srpm_nvr in builds:
- build_info = get_info_from_srpm_file(os.path.join(xcp_srpm_repo, srpm_nvr + '.src.rpm'))
- if not build_info:
- # SRPM not present in repos
- excluded_builds.append(srpm_nvr)
- continue
- name = build_info['name']
- # Keep only the latest release for a given SRPM name
- if name in latest_release_by_name:
- prev_info = latest_release_by_name[name]
- is_latest = rpm.labelCompare((prev_info['epoch'], prev_info['version'], prev_info['release']),
- (build_info['epoch'], build_info['version'], build_info['release'])) < 0
- if not is_latest:
- # skip
- continue
- else:
- # remove previous one
- del xcp_builds[prev_info['nvr']]
- latest_release_by_name[name] = {
- 'nvr': build_info['nvr'],
- 'epoch': build_info['epoch'],
- 'version': build_info['version'],
- 'release': build_info['release']
- }
- build_info['koji_tag'] = tag
- build_info['built-by'] = 'unknown'
- for builder in built_by:
- if srpm_nvr in built_by[builder]:
- build_info['built-by'] = builder
- break
- # Notify about updated packages in CentOS, EPEL...
- if name in centos_srpms:
- build_info['latest-centos'] = centos_srpms[name]
- if name in centos_srpms_previous and centos_srpms[name]['nvr'] != centos_srpms_previous[name]['nvr']:
- print("Updated in CentOS: %s from %s to %s (XCP-ng: %s)"
- % (name,
- version_release(centos_srpms_previous[name]['version'], centos_srpms_previous[name]['release']),
- version_release(centos_srpms[name]['version'], centos_srpms[name]['release']),
- version_release(build_info['version'], build_info['release'])))
- if name in epel_srpms:
- build_info['latest-epel'] = epel_srpms[name]
- if name in epel_srpms_previous and epel_srpms[name]['nvr'] != epel_srpms_previous[name]['nvr']:
- print("Updated in EPEL: %s from %s to %s (XCP-ng: %s)"
- % (name,
- version_release(epel_srpms_previous[name]['version'], epel_srpms_previous[name]['release']),
- version_release(epel_srpms[name]['version'], epel_srpms[name]['release']),
- version_release(build_info['version'], build_info['release'])))
- # provenance
- srpm_name_index = csv_headers.index('SRPM_name')
- for row in provenance_csv:
- if row[srpm_name_index] == name:
- for i in xrange(len(csv_headers)):
- field_name = csv_headers[i]
- if field_name == 'SRPM_name':
- continue
- if field_name in build_info:
- raise("Key collision! I'm trying to add the '%s' key which already exists!" % field_name)
- build_info[field_name] = row[i]
- xcp_builds[srpm_nvr] = build_info
- # Add list of RPMs nvra for each SRPM
- xcp_ng_rpms_srpms = {}
- with open(os.path.join(work_dir, 'xcp-ng-rpms-srpms.txt')) as f:
- for line in f.read().splitlines():
- rpm_filename, srpm_filename, rpm_shortname = line.split(",")
- if '-debuginfo-' in rpm_filename:
- continue
- srpm_nvr = srpm_filename[:-8] # remove .src.rpm
- rpm_nvra = rpm_filename[:-4] # remove .rpm
- if srpm_nvr in xcp_builds:
- if 'rpms' not in xcp_builds[srpm_nvr]:
- xcp_builds[srpm_nvr]['rpms'] = []
- xcp_builds[srpm_nvr]['rpms'].append(rpm_nvra)
- # also populate this dict that will be useful later
- xcp_ng_rpms_srpms[rpm_nvra] = {'name': rpm_shortname, 'srpm_nvr': srpm_nvr}
- # Get the list of RPMs that are considered "extra installable packages"
- # rpmwatcher_extract_roles.py will need them and can't use koji since it is run within a container
- lines = subprocess.check_output(['koji', 'list-groups', 'V%s' % xcp_version, 'installable_extras']).splitlines()
- lines = lines[1:]
- extra_rpms = []
- for line in lines:
- extra_rpms.append(line.strip().split(':')[0])
- # Write files
- with open(os.path.join(work_dir, 'excluded_builds.txt'), 'w') as f:
- f.write('\n'.join(excluded_builds))
- with open(os.path.join(work_dir, 'xcp-ng_builds_WIP.json'), 'w') as f:
- f.write(json.dumps(xcp_builds, sort_keys=True, indent=4))
- with open(os.path.join(work_dir, 'extra_installable.txt'), 'w') as f:
- f.write('\n'.join(extra_rpms))
- with open(os.path.join(work_dir, 'xcp-ng-rpms-srpms.json'), 'w') as f:
- f.write(json.dumps(xcp_ng_rpms_srpms, sort_keys=True, indent=4))
- with open(os.path.join(work_dir, 'centos-srpms.json'), 'w') as f:
- f.write(json.dumps(centos_srpms, sort_keys=True, indent=4))
- with open(os.path.join(work_dir, 'epel-srpms.json'), 'w') as f:
- f.write(json.dumps(epel_srpms, sort_keys=True, indent=4))
- if __name__ == "__main__":
- main()
|