#64 add koji-diff-containers
Merged 2 years ago by tkopecek. Opened 2 years ago by ktdreyer.
ktdreyer/koji-tools koji-diff-containers  into  master

@@ -0,0 +1,206 @@ 

+ #!/usr/bin/python3

+ 

+ import argparse

+ import koji

+ from requests import Session

+ import posixpath

+ import operator

+ from prettytable import PrettyTable

+ 

+ 

+ DESCRIPTION = """

+ Show the RPM package differences between two container builds.

+ """

+ 

+ 

+ def get_session(profile):

+     """

+     Return an anonymous koji session for this profile name.

+ 

+     :param str profile: profile name, like "koji" or "cbs"

+     :returns: anonymous koji.ClientSession

+     """

+     # Note: this raises koji.ConfigurationError if we could not find this

+     # profile name.

+     # (ie. "check /etc/koji.conf.d/*.conf")

+     conf = koji.read_config(profile)

+     conf['authtype'] = 'noauth'

+     hub = conf['server']

+     return koji.ClientSession(hub, {})

+ 

+ 

+ def get_koji_pathinfo(profile):

+     """

+     Return a Koji PathInfo object for our profile.

+ 

+     :param str profile: profile name, like "koji" or "cbs"

+     :returns: koji.PathInfo

+     """

+     conf = koji.read_config(profile)

+     top = conf['topurl']  # or 'topdir' here for NFS access

+     pathinfo = koji.PathInfo(topdir=top)

+     return pathinfo

+ 

+ 

+ def get_package_url(profile, package):

+     conf = koji.read_config(profile)

+     top = conf['weburl']

+     url = posixpath.join(top, 'packageinfo?packageID=%(id)d' % package)

+     return url

+ 

+ 

+ def get_metadata_url(profile, build):

+     """

+     Return the URL to the metadata.json for this build.

+ 

+     :param str profile: profile name, like "koji" or "cbs"

+     :param dict build: Koji build information

+     """

+     pathinfo = get_koji_pathinfo(profile)

+     builddir = pathinfo.build(build)

+     url = posixpath.join(builddir, 'metadata.json')

+     return url

+ 

+ 

+ def get_metadata(profile, session, rsession, build):

+     """

+     Get the content-generator metadata for this Koji build.

+ 

+     :param str profile: profile name, like "koji" or "cbs"

+     :param session: koji.ClientSession

+     :param rsession: requests.Session

+     :param dict build: Koji build information

+     :returns: dict of entire content-generator metadata.

+     """

+     url = get_metadata_url(profile, build)

+     response = rsession.get(url)

+     response.raise_for_status()

+     return response.json()

+ 

+ 

+ def list_rpms(metadata):

+     """

+     Parse an OSBS container's metadata for the list of RPMs therein.

+ 

+     :param dict metadata: Koji content-generator metadata.

+     :returns: a dict of rpm version-releases, keyed by name.

+     """

+     output_items = metadata['output']  # logs, plus per-arch container images.

+     nvrs = dict()

+     for output_item in output_items:

+         components = output_item.get('components', [])

+         for component in components:

+             if component['type'] != 'rpm':

+                 continue

+             nvrs[component['name']] = {

+                 'version': component['version'],

+                 'release': component['release'],

+             }

+     return nvrs

+ 

+ 

+ def diff_rpms(old_metadata, new_metadata):

+     """

+     Diff two OSBS container's metadata for RPM differences.

+ 

+     :param dict old_metadata: Koji content-generator metadata.

+     :param dict new_metadata: Koji content-generator metadata.

+     :returns: a prettytable.PrettyTable object

+     """

+     # TODO: move this out to a separate method, and pass in "old_nvrs" and

+     # "new_nvrs".

+     old_nvrs = list_rpms(old_metadata)

+     new_nvrs = list_rpms(new_metadata)

+     print('found %d old NVRs' % len(old_nvrs))

+     print('found %d new NVRs' % len(new_nvrs))

+     added = new_nvrs.keys() - old_nvrs.keys()

+     removed = old_nvrs.keys() - new_nvrs.keys()

+ 

+     table = PrettyTable()

+     # TODO: instead of "Old" and "New" here, use the container NVRs.

+     table.field_names = ['RPM', 'Old', 'New']

+     table.align = 'l'

+ 

+     for name in removed:

+         nvr = old_nvrs[name]

+         table.add_row([name, '%(version)s-%(release)s' % nvr, None])

+     for name in added:

+         nvr = new_nvrs[name]

+         table.add_row([name, None, '%(version)s-%(release)s' % nvr])

+ 

+     # TODO: sort this old_nvrs dict:

+     for name, version_release in old_nvrs.items():

+         if name not in new_nvrs:

+             continue

+         old = '%(version)s-%(release)s' % version_release

+         new = '%(version)s-%(release)s' % new_nvrs[name]

+         if new != old:

+             table.add_row([name, old, new])

+     return table

+ 

+ 

+ def parse_args():

+     parser = argparse.ArgumentParser(description=DESCRIPTION)

+     parser.add_argument('--profile',

+                         help='Koji profile. Your Koji client profile'

+                              ' definitions are stored in'

+                              ' /etc/koji.conf.d/*.conf.',

+                         required=True)

+     parser.add_argument('old_nvr',

+                         help='old container to compare, eg'

+                         ' "rhceph-container-1-1"')

+     parser.add_argument('new_nvr',

+                         help='old container to compare, eg'

+                         ' "rhceph-container-1-2"')

+     args = parser.parse_args()

+ 

+     for nvr in (args.old_nvr, args.new_nvr):

+         if '-container' not in nvr:

+             raise ValueError('%s must be a "-container" build' % nvr)

+ 

+     return args

+ 

+ 

+ def help_missing_nvr(profile, session, nvr):

+     """

+     A user has specified a build NVR that does not exist. Link to the package

+     in kojiweb.

+     """

+     print('"%s" is not a Koji build' % nvr)

+     build = koji.parse_NVR(nvr)

+     name = build['name']

+     package = session.getPackage(name)

+     if not package:

+         print('There is no "%s" package in Koji.' % name)

+         return

+     print('Please choose a valid %s build:' % name)

+     url = get_package_url(profile, package)

+     print(url)

+ 

+ 

+ def main():

+     args = parse_args()

+     session = get_session(args.profile)

+ 

+     old_build = session.getBuild(args.old_nvr)

+     if not old_build:

+         help_missing_nvr(args.profile, session, args.old_nvr)

+         raise SystemExit(1)

+ 

+     new_build = session.getBuild(args.new_nvr)

+     if not new_build:

+         help_missing_nvr(args.profile, session, args.new_nvr)

+         raise SystemExit(1)

+ 

+     rsession = Session()

+     old_metadata = get_metadata(args.profile, session, rsession, old_build)

+     new_metadata = get_metadata(args.profile, session, rsession, new_build)

+ 

+     table = diff_rpms(old_metadata, new_metadata)

+ 

+     print('Found %d differences:' % len(table._rows))

+     print(table.get_string(sort_key=operator.itemgetter(1, 0), sortby="RPM"))

+ 

+ 

+ if __name__ == '__main__':

+     main()

Use this to compare RPM NVRs between two container builds.

A lot of this code is copied & pasted between koji-search-containers. Longer-term it would make sense to share that in a common library instead (or maybe koji-containerbuild's CLI).

rebased onto 7850395

2 years ago

Commit c390da9 fixes this pull-request

Pull-Request has been merged by tkopecek

2 years ago

Pull-Request has been merged by tkopecek

2 years ago
Metadata