From a1d02a14c3c479b0f52b8b99a9254c843ff1087a Mon Sep 17 00:00:00 2001 From: Brendan Reilly Date: Aug 30 2017 15:43:55 +0000 Subject: [PATCH 1/4] Added list builds command to koji CLI --- diff --git a/cli/koji_cli/commands.py b/cli/koji_cli/commands.py index 1c88128..00d9464 100644 --- a/cli/koji_cli/commands.py +++ b/cli/koji_cli/commands.py @@ -2873,6 +2873,78 @@ def anon_handle_list_pkgs(goptions, session, args): print(fmt % pkg) +def anon_handle_list_builds(goptions, session, args): + "[info] Print the build listing" + usage = _("usage: %prog list-builds [options]") + usage += _("\n(Specify the --help global option for a list of other help options)") + parser = OptionParser(usage=usage) + parser.add_option("--package", help=_("List builds for this package")) + parser.add_option("--buildid", help=_("List build from build ID")) + parser.add_option("--beforedate", help=_("List builds built before this date. YYYY-MM-DD format")) + parser.add_option("--afterdate", help=_("List builds built after this date. YYYY-MM-DD format")) + parser.add_option("--state", help=_("List builds in this state")) + parser.add_option("--type", help=_("List builds of this type.")) + parser.add_option("--owner", help=_("List builds built by this owner")) + parser.add_option("--volume", help=_("List builds by volume ID")) + parser.add_option("--quiet", action="store_true", default=goptions.quiet, + help=_("Do not print the header information")) + (options, args) = parser.parse_args(args) + activate_session(session, goptions) + opts = {} + for key in ('state', 'type'): + opts[key] = getattr(options, key) + if options.package: + try: + opts['packageID'] = int(options.package) + except ValueError: + opts['packageID'] = session.getPackageID(options.package) + if options.owner: + try: + opts['userID'] = int(options.owner) + except ValueError: + opts['userID'] = session.getUser(options.owner)['id'] + if options.volume: + try: + opts['volumeID'] = int(options.volume) + except ValueError: + volumes = session.listVolumes() + for volume in volumes: + if options.volume == volume['name']: + opts['volumeID'] = volume['id'] + if options.beforedate: + opts['completeBefore'] = options.beforedate + if options.afterdate: + opts['completeAfter'] = options.afterdate + if options.buildid: + try: + buildid = int(options.buildid) + except ValueError: + buildid = options.buildid + data = [session.getBuild(buildid)] + if options.type == 'maven': + data[0].update(session.getMavenBuild(buildid)) + else: + data = session.listBuilds(**opts) + if options.type == 'maven' and options.buildid: + fmt = "%(nvr)-55s %(group_id)-20s %(artifact_id)-20s %(owner_name)s" + elif options.type == 'maven': + fmt = "%(nvr)-55s %(maven_group_id)-20s %(maven_artifact_id)-20s %(owner_name)s" + else: + fmt = "%(nvr)-55s %(owner_name)s" + if not options.quiet: + if options.type == 'maven': + print("%-55s %-20s %-20s %s" % ("Build", "Group Id", "Artifact Id", "Built by")) + print("%s %s %s %s" % ("-"*55, "-"*20, "-"*20, "-"*16)) + else: + print("%-55s %s" % ("Build", "Built by")) + print("%s %s" % ("-"*55, "-"*16)) + + output = [ fmt % x for x in data ] + output.sort() + for line in output: + print(line) + + def anon_handle_rpminfo(goptions, session, args): "[info] Print basic information about an RPM" usage = _("usage: %prog rpminfo [options] [ ...]") From 964507ad290db60a5603e8cf084c810a1858984d Mon Sep 17 00:00:00 2001 From: Brendan Reilly Date: Aug 30 2017 15:43:55 +0000 Subject: [PATCH 2/4] Added more failure handling for list-builds --- diff --git a/cli/koji_cli/commands.py b/cli/koji_cli/commands.py index 00d9464..67dbe38 100644 --- a/cli/koji_cli/commands.py +++ b/cli/koji_cli/commands.py @@ -2879,7 +2879,7 @@ def anon_handle_list_builds(goptions, session, args): usage += _("\n(Specify the --help global option for a list of other help options)") parser = OptionParser(usage=usage) parser.add_option("--package", help=_("List builds for this package")) - parser.add_option("--buildid", help=_("List build from build ID")) + parser.add_option("--buildid", help=_("List build from build ID or nvr")) parser.add_option("--beforedate", help=_("List builds built before this date. YYYY-MM-DD format")) parser.add_option("--afterdate", help=_("List builds built after this date. YYYY-MM-DD format")) parser.add_option("--state", help=_("List builds in this state")) @@ -2889,28 +2889,46 @@ def anon_handle_list_builds(goptions, session, args): parser.add_option("--quiet", action="store_true", default=goptions.quiet, help=_("Do not print the header information")) (options, args) = parser.parse_args(args) + if len(args) != 0: + parser.error(_("This command takes no arguments")) + assert False # pragma: no cover activate_session(session, goptions) opts = {} for key in ('state', 'type'): - opts[key] = getattr(options, key) + value = getattr(options, key) + if value is not None: + opts[key] = value if options.package: try: opts['packageID'] = int(options.package) except ValueError: - opts['packageID'] = session.getPackageID(options.package) + package = session.getPackageID(options.package) + if package is None: + parser.error(_("Invalid package")) + assert False # pragma: no cover + opts['packageID'] = package if options.owner: try: opts['userID'] = int(options.owner) except ValueError: - opts['userID'] = session.getUser(options.owner)['id'] + user = session.getUser(options.owner) + if user is None: + parser.error(_("Invalid owner")) + assert False # pragma: no cover + opts['userID'] = user['id'] if options.volume: try: opts['volumeID'] = int(options.volume) except ValueError: volumes = session.listVolumes() + volumeID = None for volume in volumes: if options.volume == volume['name']: - opts['volumeID'] = volume['id'] + volumeID = volume['id'] + if volumeID is None: + parser.error(_("Invalid volume")) + assert False # pragma: no cover + opts['volumeID'] = volume if options.beforedate: opts['completeBefore'] = options.beforedate if options.afterdate: @@ -2921,10 +2939,18 @@ def anon_handle_list_builds(goptions, session, args): except ValueError: buildid = options.buildid data = [session.getBuild(buildid)] + if data is None: + parser.error(_("Invalid build ID")) + assert False # pragma: no cover if options.type == 'maven': data[0].update(session.getMavenBuild(buildid)) else: - data = session.listBuilds(**opts) + # Check filter exists + if any(opts): + data = session.listBuilds(**opts) + else: + parser.error(_("Filter must be provided for list")) + assert False # pragma: no cover if options.type == 'maven' and options.buildid: fmt = "%(nvr)-55s %(group_id)-20s %(artifact_id)-20s %(owner_name)s" elif options.type == 'maven': From 48c96f9cccd8919327dae6363ae5126f2ec29a56 Mon Sep 17 00:00:00 2001 From: Brendan Reilly Date: Aug 30 2017 15:43:55 +0000 Subject: [PATCH 3/4] Added better state handling, prefix option, tz handling, sorting to list-builds --- diff --git a/cli/koji_cli/commands.py b/cli/koji_cli/commands.py index 67dbe38..781f585 100644 --- a/cli/koji_cli/commands.py +++ b/cli/koji_cli/commands.py @@ -2879,13 +2879,18 @@ def anon_handle_list_builds(goptions, session, args): usage += _("\n(Specify the --help global option for a list of other help options)") parser = OptionParser(usage=usage) parser.add_option("--package", help=_("List builds for this package")) - parser.add_option("--buildid", help=_("List build from build ID or nvr")) - parser.add_option("--beforedate", help=_("List builds built before this date. YYYY-MM-DD format")) - parser.add_option("--afterdate", help=_("List builds built after this date. YYYY-MM-DD format")) + parser.add_option("--buildid", help=_("List specific build from ID or nvr")) + parser.add_option("--before", + help=_("List builds built before this time. 'YYYY-MM-DD HH24:MI:SS' ISO format")) + parser.add_option("--after", + help=_("List builds built after this time. 'YYYY-MM-DD HH24:MI:SS' ISO format")) parser.add_option("--state", help=_("List builds in this state")) parser.add_option("--type", help=_("List builds of this type.")) + parser.add_option("--prefix", help=_("Only list packages starting with this prefix")) parser.add_option("--owner", help=_("List builds built by this owner")) parser.add_option("--volume", help=_("List builds by volume ID")) + parser.add_option("--descending", action="store_true", default=False, + help=_("Print the list in descending order")) parser.add_option("--quiet", action="store_true", default=goptions.quiet, help=_("Do not print the header information")) (options, args) = parser.parse_args(args) @@ -2894,7 +2899,7 @@ def anon_handle_list_builds(goptions, session, args): assert False # pragma: no cover activate_session(session, goptions) opts = {} - for key in ('state', 'type'): + for key in ('type', 'prefix'): value = getattr(options, key) if value is not None: opts[key] = value @@ -2929,10 +2934,39 @@ def anon_handle_list_builds(goptions, session, args): parser.error(_("Invalid volume")) assert False # pragma: no cover opts['volumeID'] = volume - if options.beforedate: - opts['completeBefore'] = options.beforedate - if options.afterdate: - opts['completeAfter'] = options.afterdate + if options.state: + try: + state = int(options.state) + if state > 4 or state < 0: + parser.error(_("Invalid state")) + assert False # pragma: no cover + opts['state'] = state + except ValueError: + try: + opts['state'] = koji.BUILD_STATES[options.state] + except KeyError: + parser.error(_("Invalid state")) + assert False # pragma: no cover + for opt in ('before', 'after'): + val = getattr(options, opt) + if not val: + continue + try: + ts = float(val) + setattr(options, opt, ts) + continue + except ValueError: + pass + try: + dt = dateutil.parser.parse(val) + ts = time.mktime(dt.timetuple()) + setattr(options, opt, ts) + except: + parser.error(_("Invalid time specification: %s") % val) + if options.before: + opts['completeBefore'] = getattr(options, 'before') + if options.after: + opts['completeAfter'] = getattr(options, 'after') if options.buildid: try: buildid = int(options.buildid) @@ -2951,6 +2985,8 @@ def anon_handle_list_builds(goptions, session, args): else: parser.error(_("Filter must be provided for list")) assert False # pragma: no cover + sorteddata = sorted(data, key=lambda k: k['nvr'], reverse=options.descending) + if options.type == 'maven' and options.buildid: fmt = "%(nvr)-55s %(group_id)-20s %(artifact_id)-20s %(owner_name)s" elif options.type == 'maven': @@ -2965,8 +3001,7 @@ def anon_handle_list_builds(goptions, session, args): print("%-55s %s" % ("Build", "Built by")) print("%s %s" % ("-"*55, "-"*16)) - output = [ fmt % x for x in data ] - output.sort() + output = [ fmt % x for x in sorteddata ] for line in output: print(line) diff --git a/tests/test_cli/data/list-commands.txt b/tests/test_cli/data/list-commands.txt index b1e8b40..df64059 100644 --- a/tests/test_cli/data/list-commands.txt +++ b/tests/test_cli/data/list-commands.txt @@ -95,6 +95,7 @@ info commands: latest-build Print the latest builds for a tag list-api Print the list of XML-RPC APIs list-buildroot List the rpms used in or built in a buildroot + list-builds Print the build listing list-channels Print channels listing list-external-repos List external repos list-groups Print the group listings From 894a4906d7d3350971f40409877b6dea9a496cef Mon Sep 17 00:00:00 2001 From: Brendan Reilly Date: Aug 30 2017 15:43:55 +0000 Subject: [PATCH 4/4] Removed special maven case, added sorting option, now displays state --- diff --git a/cli/koji_cli/commands.py b/cli/koji_cli/commands.py index 781f585..982314f 100644 --- a/cli/koji_cli/commands.py +++ b/cli/koji_cli/commands.py @@ -2881,16 +2881,18 @@ def anon_handle_list_builds(goptions, session, args): parser.add_option("--package", help=_("List builds for this package")) parser.add_option("--buildid", help=_("List specific build from ID or nvr")) parser.add_option("--before", - help=_("List builds built before this time. 'YYYY-MM-DD HH24:MI:SS' ISO format")) + help=_("List builds built before this time")) parser.add_option("--after", - help=_("List builds built after this time. 'YYYY-MM-DD HH24:MI:SS' ISO format")) + help=_("List builds built after this time")) parser.add_option("--state", help=_("List builds in this state")) parser.add_option("--type", help=_("List builds of this type.")) parser.add_option("--prefix", help=_("Only list packages starting with this prefix")) parser.add_option("--owner", help=_("List builds built by this owner")) parser.add_option("--volume", help=_("List builds by volume ID")) - parser.add_option("--descending", action="store_true", default=False, - help=_("Print the list in descending order")) + parser.add_option("-k", "--sort-key", action="append", metavar='FIELD', + default=[], help=_("Sort the list by the named field")) + parser.add_option("-r", "--reverse", action="store_true", default=False, + help=_("Print the list in reverse order")) parser.add_option("--quiet", action="store_true", default=goptions.quiet, help=_("Do not print the header information")) (options, args) = parser.parse_args(args) @@ -2933,7 +2935,7 @@ def anon_handle_list_builds(goptions, session, args): if volumeID is None: parser.error(_("Invalid volume")) assert False # pragma: no cover - opts['volumeID'] = volume + opts['volumeID'] = volumeID if options.state: try: state = int(options.state) @@ -2976,8 +2978,6 @@ def anon_handle_list_builds(goptions, session, args): if data is None: parser.error(_("Invalid build ID")) assert False # pragma: no cover - if options.type == 'maven': - data[0].update(session.getMavenBuild(buildid)) else: # Check filter exists if any(opts): @@ -2985,25 +2985,20 @@ def anon_handle_list_builds(goptions, session, args): else: parser.error(_("Filter must be provided for list")) assert False # pragma: no cover - sorteddata = sorted(data, key=lambda k: k['nvr'], reverse=options.descending) - - if options.type == 'maven' and options.buildid: - fmt = "%(nvr)-55s %(group_id)-20s %(artifact_id)-20s %(owner_name)s" - elif options.type == 'maven': - fmt = "%(nvr)-55s %(maven_group_id)-20s %(maven_artifact_id)-20s %(owner_name)s" - else: - fmt = "%(nvr)-55s %(owner_name)s" + if not options.sort_key: + options.sort_key = ['nvr'] + data = sorted(data, key=lambda b: [b.get(k) for k in options.sort_key], + reverse=options.reverse) + for build in data: + build['state'] = koji.BUILD_STATES[build['state']] + + fmt = "%(nvr)-55s %(owner_name)-16s %(state)s" if not options.quiet: - if options.type == 'maven': - print("%-55s %-20s %-20s %s" % ("Build", "Group Id", "Artifact Id", "Built by")) - print("%s %s %s %s" % ("-"*55, "-"*20, "-"*20, "-"*16)) - else: - print("%-55s %s" % ("Build", "Built by")) - print("%s %s" % ("-"*55, "-"*16)) + print("%-55s %-16s %s" % ("Build", "Built by", "State")) + print("%s %s %s" % ("-"*55, "-"*16, "-"*16)) - output = [ fmt % x for x in sorteddata ] - for line in output: - print(line) + for build in data: + print(fmt % build) def anon_handle_rpminfo(goptions, session, args):