#199 CLI plugins
Merged 4 years ago by mikem. Opened 5 years ago by tkopecek.
tkopecek/koji issue193  into  master

file modified
+2 -2
@@ -66,7 +66,7 @@ 

  

  test:

  	coverage erase

- 	PYTHONPATH=hub/.:plugins/hub/.:plugins/builder/. coverage run \

+ 	PYTHONPATH=hub/.:plugins/hub/.:plugins/builder/.:plugins/cli/.:cli/. coverage run \

  	    --source . /usr/bin/nosetests

  	coverage report

  	coverage html
@@ -74,7 +74,7 @@ 

  

  test3:

  	coverage erase

- 	PYTHONPATH=hub/.:plugins/hub/.:plugins/builder/. coverage3 run \

+ 	PYTHONPATH=hub/.:plugins/hub/.:plugins/builder/.:plugins/cli/.:cli/. coverage3 run \

  	    --rcfile .coveragerc3 --source . \

  	    /usr/bin/nosetests-3 \

  	    tests/test_lib tests/test_cli

file modified
+13
@@ -1,3 +1,12 @@ 

+ SUBDIRS=koji_cli

+ 

+ PYTHON=python

+ PACKAGE = $(shell basename `pwd`)

+ PYVER := $(shell $(PYTHON) -c 'import sys; print("%.3s" %(sys.version))')

+ PYSYSDIR := $(shell $(PYTHON) -c 'import sys; print(sys.prefix)')

+ PYLIBDIR = $(PYSYSDIR)/lib/python$(PYVER)

+ PKGDIR = $(PYLIBDIR)/site-packages

+ 

  FILES = koji

  

  _default:
@@ -13,7 +22,11 @@ 

  		exit 1; \

  	fi

  

+ 	for d in $(SUBDIRS); do make DESTDIR=$(DESTDIR) \

+ 		-C $$d install; [ $$? = 0 ] || exit 1; done

+ 

  	mkdir -p $(DESTDIR)/usr/bin

  	install -p -m 755 $(FILES) $(DESTDIR)/usr/bin

  	mkdir -p $(DESTDIR)/etc/koji.conf.d

  	install -p -m 644 koji.conf $(DESTDIR)/etc/koji.conf

+ 	install -p -m 644 koji.conf $(DESTDIR)/etc/koji.conf

file modified
+122 -7501
@@ -26,143 +26,62 @@ 

  

  from __future__ import absolute_import

  from __future__ import division

- import sys

- from six.moves import range

- from six.moves import zip

- import six

- from six.moves import filter

- from six.moves import map

- try:

-     import krbV

- except ImportError:  # pragma: no cover

-     krbV = None

- try:

-     import ast

- except ImportError:  # pragma: no cover

-     ast = None

- try:

-     import json

- except ImportError:  # pragma: no cover

-     try:

-         import simplejson as json

-     except ImportError:

-         json = None

- import six.moves.configparser

- import base64

- import dateutil.parser

- import errno

- import koji

- import koji.util

- import fnmatch

- from koji.util import md5_constructor

  import logging

+ import optparse

  import os

  import re

- import pprint

- import pycurl

- import random

- import socket

- import stat

- import string

- import time

- import traceback

- import six.moves.xmlrpc_client

- try:

-     import libcomps

- except ImportError:  # pragma: no cover

-     libcomps = None

-     try:

-         import yum.comps as yumcomps

-     except ImportError:

-         yumcomps = None

- import optparse

+ import six

+ import sys

+ import types

  

+ import six.moves.configparser

+ import six.moves.xmlrpc_client

  

- # fix OptionParser for python 2.3 (optparse verion 1.4.1+)

- # code taken from optparse version 1.5a2

- OptionParser = optparse.OptionParser

- if optparse.__version__ == "1.4.1+":  # pragma: no cover

-     def _op_error(self, msg):

-         self.print_usage(sys.stderr)

-         msg = "%s: error: %s\n" % (self._get_prog_name(), msg)

-         if msg:

-             sys.stderr.write(msg)

-         sys.exit(2)

-     OptionParser.error = _op_error

+ import koji

+ import koji.util

+ import koji.plugin

  

- greetings = ('hello', 'hi', 'yo', "what's up", "g'day", 'back to work',

-              'bonjour',

-              'hallo',

-              'ciao',

-              'hola',

-             u'olá',

-             u'dobrý den',

-             u'zdravstvuite',

-             u'góðan daginn',

-              'hej',

-              'tervehdys',

-             u'grüezi',

-             u'céad míle fáilte',

-             u'hylô',

-             u'bună ziua',

-             u'jó napot',

-              'dobre dan',

-             u'你好',

-             u'こんにちは',

-             u'नमस्कार',

-             u'안녕하세요')

+ from koji_cli.lib import _, OptionParser, get_epilog_str, greetings, \

+         warn, categories

+ from koji_cli.commands import *

  

- def _(args):

-     """Stub function for translation"""

-     return args

  

- def _printable_unicode(s):

-     if six.PY2:

-         return s.encode('utf-8')

-     else:

-         return s

+ def register_plugin(plugin):

+     """Scan a given plugin for handlers

  

- ARGMAP = {'None': None,

-           'True': True,

-           'False': False}

+     Handlers are functions marked with one of the decorators defined in koji.plugin

+     """

+     for v in vars(plugin).itervalues():

+         if isinstance(v, (types.ClassType, types.TypeType)):

+             #skip classes

+             continue

+         if callable(v):

+             if getattr(v, 'exported_cli', False):

+                 if hasattr(v, 'export_alias'):

+                     name = getattr(v, 'export_alias')

+                 else:

+                     name = v.__name__

+                 # copy object to local namespace

+                 globals()[name] = v

  

- def arg_filter(arg):

-     try:

-         return int(arg)

-     except ValueError:

-         pass

-     try:

-         return float(arg)

-     except ValueError:

-         pass

-     if arg in ARGMAP:

-         return ARGMAP[arg]

-     #handle lists/dicts?

-     return arg

  

- categories = {

-     'admin' : 'admin commands',

-     'build' : 'build commands',

-     'search' : 'search commands',

-     'download' : 'download commands',

-     'monitor'  : 'monitor commands',

-     'info' : 'info commands',

-     'bind' : 'bind commands',

-     'misc' : 'miscellaneous commands',

- }

+ def load_plugins(options):

+     """Load plugins specified by our configuration plus system plugins. Order

+     is that system plugins are first, so they can be overriden by

+     user-specified ones with same name."""

+     logger = logging.getLogger('koji.plugins')

+     syspath = '%s/lib/python%s.%s/site-packages/koji_cli_plugins' % \

+               (sys.prefix, sys.version_info.major, sys.version_info.minor)

+     if os.path.exists(syspath):

+         tracker = koji.plugin.PluginTracker(path=syspath)

+         for name in sorted(os.listdir(syspath)):

+             if not name.endswith('.py'):

+                 continue

+             name = name[:-3]

+             logger.info('Loading plugin: %s', name)

+             tracker.load(name)

+             register_plugin(tracker.get(name))

  

- def get_epilog_str(progname=None):

-     if progname is None:

-         progname = os.path.basename(sys.argv[0]) or 'koji'

-     categories_ordered=', '.join(sorted(['all'] + list(categories.keys())))

-     epilog_str = '''

- Try "%(progname)s --help" for help about global options

- Try "%(progname)s help" to get all available commands

- Try "%(progname)s <command> --help" for help about the options of a particular command

- Try "%(progname)s help <category>" to get commands under a particular category

- Available categories are: %(categories)s

- ''' % ({'progname': progname, 'categories': categories_ordered})

-     return _(epilog_str)

  

  def get_options():

      """process options from command line and config file"""
@@ -210,42 +129,6 @@ 

      parser.add_option("--help-commands", action="store_true", default=False, help=_("list commands"))

      (options, args) = parser.parse_args()

  

-     if options.help_commands:

-         list_commands()

-         sys.exit(0)

-     if not args:

-         list_commands()

-         sys.exit(0)

- 

-     aliases = {

-         'cancel-task' : 'cancel',

-         'cxl' : 'cancel',

-         'list-commands' : 'help',

-         'move-pkg': 'move-build',

-         'move': 'move-build',

-         'latest-pkg': 'latest-build',

-         'tag-pkg': 'tag-build',

-         'tag': 'tag-build',

-         'untag-pkg': 'untag-build',

-         'untag': 'untag-build',

-         'watch-tasks': 'watch-task',

-     }

-     cmd = args[0]

-     cmd = aliases.get(cmd, cmd)

-     if cmd.lower() in greetings:

-         cmd = "moshimoshi"

-     cmd = cmd.replace('-', '_')

-     if ('anon_handle_' + cmd) in globals():

-         if not options.force_auth and '--mine' not in args:

-             options.noauth = True

-         cmd = 'anon_handle_' + cmd

-     elif ('handle_' + cmd) in globals():

-         cmd = 'handle_' + cmd

-     else:

-         list_commands()

-         parser.error('Unknown command: %s' % args[0])

-         assert False  # pragma: no cover

- 

      # load local config

      try:

          result = koji.read_config(options.profile, user_config=options.configFile)
@@ -282,7364 +165,102 @@ 

              else:

                  warn("Warning: The pkgurl option is obsolete, please use topurl instead")

  

-     return options, cmd, args[1:]

- 

- def ensure_connection(session):

-     try:

-         ret = session.getAPIVersion()

-     except six.moves.xmlrpc_client.ProtocolError:

-         error(_("Error: Unable to connect to server"))

-     if ret != koji.API_VERSION:

-         warn(_("WARNING: The server is at API version %d and the client is at %d" % (ret, koji.API_VERSION)))

- 

- def print_task_headers():

-     """Print the column headers"""

-     print("ID       Pri  Owner                State    Arch       Name")

- 

- def print_task(task,depth=0):

-     """Print a task"""

-     task = task.copy()

-     task['state'] = koji.TASK_STATES.get(task['state'],'BADSTATE')

-     fmt = "%(id)-8s %(priority)-4s %(owner_name)-20s %(state)-8s %(arch)-10s "

-     if depth:

-         indent = "  "*(depth-1) + " +"

-     else:

-         indent = ''

-     label = koji.taskLabel(task)

-     print(''.join([fmt % task, indent, label]))

- 

- def print_task_recurse(task,depth=0):

-     """Print a task and its children"""

-     print_task(task,depth)

-     for child in task.get('children',()):

-         print_task_recurse(child,depth+1)

- 

- def parse_arches(arches, to_list=False):

-     """Parse comma or space-separated list of arches and return

-        only space-separated one."""

-     arches = arches.replace(',', ' ').split()

-     if to_list:

-         return arches

-     else:

-         return ' '.join(arches)

- 

- class TaskWatcher(object):

- 

-     def __init__(self,task_id,session,level=0,quiet=False):

-         self.id = task_id

-         self.session = session

-         self.info = None

-         self.level = level

-         self.quiet = quiet

- 

-     #XXX - a bunch of this stuff needs to adapt to different tasks

- 

-     def str(self):

-         if self.info:

-             label = koji.taskLabel(self.info)

-             return "%s%d %s" % ('  ' * self.level, self.id, label)

-         else:

-             return "%s%d" % ('  ' * self.level, self.id)

- 

-     def __str__(self):

-         return self.str()

- 

-     def get_failure(self):

-         """Print infomation about task completion"""

-         if self.info['state'] != koji.TASK_STATES['FAILED']:

-             return ''

-         error = None

-         try:

-             result = self.session.getTaskResult(self.id)

-         except (six.moves.xmlrpc_client.Fault,koji.GenericError) as e:

-             error = e

-         if error is None:

-             # print("%s: complete" % self.str())

-             # We already reported this task as complete in update()

-             return ''

-         else:

-             return '%s: %s' % (error.__class__.__name__, str(error).strip())

- 

-     def update(self):

-         """Update info and log if needed.  Returns True on state change."""

-         if self.is_done():

-             # Already done, nothing else to report

-             return False

-         last = self.info

-         self.info = self.session.getTaskInfo(self.id, request=True)

-         if self.info is None:

-             if not self.quiet:

-                 print("No such task id: %i" % self.id)

-             sys.exit(1)

-         state = self.info['state']

-         if last:

-             #compare and note status changes

-             laststate = last['state']

-             if laststate != state:

-                 if not self.quiet:

-                     print("%s: %s -> %s" % (self.str(), self.display_state(last), self.display_state(self.info)))

-                 return True

-             return False

-         else:

-             # First time we're seeing this task, so just show the current state

-             if not self.quiet:

-                 print("%s: %s" % (self.str(), self.display_state(self.info)))

-             return False

- 

-     def is_done(self):

-         if self.info is None:

-             return False

-         state = koji.TASK_STATES[self.info['state']]

-         return (state in ['CLOSED','CANCELED','FAILED'])

- 

-     def is_success(self):

-         if self.info is None:

-             return False

-         state = koji.TASK_STATES[self.info['state']]

-         return (state == 'CLOSED')

- 

-     def display_state(self, info):

-         # We can sometimes be passed a task that is not yet open, but

-         # not finished either.  info would be none.

-         if not info:

-             return 'unknown'

-         if info['state'] == koji.TASK_STATES['OPEN']:

-             if info['host_id']:

-                 host = self.session.getHost(info['host_id'])

-                 return 'open (%s)' % host['name']

-             else:

-                 return 'open'

-         elif info['state'] == koji.TASK_STATES['FAILED']:

-             return 'FAILED: %s' % self.get_failure()

-         else:

-             return koji.TASK_STATES[info['state']].lower()

- 

- def display_tasklist_status(tasks):

-     free = 0

-     open = 0

-     failed = 0

-     done = 0

-     for task_id in tasks.keys():

-         status = tasks[task_id].info['state']

-         if status == koji.TASK_STATES['FAILED']:

-             failed += 1

-         elif status == koji.TASK_STATES['CLOSED'] or status == koji.TASK_STATES['CANCELED']:

-             done += 1

-         elif status == koji.TASK_STATES['OPEN'] or status == koji.TASK_STATES['ASSIGNED']:

-             open += 1

-         elif status == koji.TASK_STATES['FREE']:

-             free += 1

-     print("  %d free  %d open  %d done  %d failed" % (free, open, done, failed))

- 

- def display_task_results(tasks):

-     for task in [task for task in tasks.values() if task.level == 0]:

-         state = task.info['state']

-         task_label = task.str()

- 

-         if state == koji.TASK_STATES['CLOSED']:

-             print('%s completed successfully' % task_label)

-         elif state == koji.TASK_STATES['FAILED']:

-             print('%s failed' % task_label)

-         elif state == koji.TASK_STATES['CANCELED']:

-             print('%s was canceled' % task_label)

-         else:

-             # shouldn't happen

-             print('%s has not completed' % task_label)

- 

- def watch_tasks(session,tasklist,quiet=False):

-     global options

-     if not tasklist:

-         return

-     if not quiet:

-         print("Watching tasks (this may be safely interrupted)...")

-     sys.stdout.flush()

-     rv = 0

-     try:

-         tasks = {}

-         for task_id in tasklist:

-             tasks[task_id] = TaskWatcher(task_id,session,quiet=quiet)

-         while True:

-             all_done = True

-             for task_id, task in list(tasks.items()):

-                 changed = task.update()

-                 if not task.is_done():

-                     all_done = False

-                 else:

-                     if changed:

-                         # task is done and state just changed

-                         if not quiet:

-                             display_tasklist_status(tasks)

-                     if not task.is_success():

-                         rv = 1

-                 for child in session.getTaskChildren(task_id):

-                     child_id = child['id']

-                     if not child_id in list(tasks.keys()):

-                         tasks[child_id] = TaskWatcher(child_id, session, task.level + 1, quiet=quiet)

-                         tasks[child_id].update()

-                         # If we found new children, go through the list again,

-                         # in case they have children also

-                         all_done = False

-             if all_done:

-                 if not quiet:

-                     print('')

-                     display_task_results(tasks)

-                 break

- 

-             sys.stdout.flush()

-             time.sleep(options.poll_interval)

-     except KeyboardInterrupt:

-         if tasks and not quiet:

-             progname = os.path.basename(sys.argv[0]) or 'koji'

-             tlist = ['%s: %s' % (t.str(), t.display_state(t.info))

-                             for t in tasks.values() if not t.is_done()]

-             print( \

- """Tasks still running. You can continue to watch with the '%s watch-task' command.

- Running Tasks:

- %s""" % (progname, '\n'.join(tlist)))

-         raise

-     return rv

- 

- def watch_logs(session, tasklist, opts):

-     global options

-     print("Watching logs (this may be safely interrupted)...")

-     def _isDone(session, taskId):

-         info = session.getTaskInfo(taskId)

-         if info is None:

-             print("No such task id: %i" % taskId)

-             sys.exit(1)

-         state = koji.TASK_STATES[info['state']]

-         return (state in ['CLOSED','CANCELED','FAILED'])

- 

-     offsets = {}

-     for task_id in tasklist:

-         offsets[task_id] = {}

- 

-     lastlog = None

-     while True:

-         for task_id in tasklist[:]:

-             if _isDone(session, task_id):

-                 tasklist.remove(task_id)

- 

-             output = list_task_output_all_volumes(session, task_id)

-             # convert to list of (file, volume)

-             files = []

-             for filename, volumes in six.iteritems(output):

-                 files += [(filename, volume) for volume in volumes]

- 

-             if opts.log:

-                 logs = [file_volume for file_volume in files if file_volume[0] == opts.log]

-             else:

-                 logs = [file_volume for file_volume in files if file_volume[0].endswith('log')]

- 

-             taskoffsets = offsets[task_id]

-             for log, volume in logs:

-                 contents = 'placeholder'

-                 while contents:

-                     if (log, volume) not in taskoffsets:

-                         taskoffsets[(log, volume)] = 0

- 

-                     contents = session.downloadTaskOutput(task_id, log, taskoffsets[(log, volume)], 16384, volume=volume)

-                     taskoffsets[(log, volume)] += len(contents)

-                     if contents:

-                         currlog = "%d:%s:%s:" % (task_id, volume, log)

-                         if currlog != lastlog:

-                             if lastlog:

-                                 sys.stdout.write("\n")

-                             sys.stdout.write("==> %s <==\n" % currlog)

-                             lastlog = currlog

-                         sys.stdout.write(contents)

- 

-         if not tasklist:

-             break

- 

-         time.sleep(options.poll_interval)

- 

- 

- def list_task_output_all_volumes(session, task_id):

-     """List task output with all volumes, or fake it"""

-     try:

-         return session.listTaskOutput(task_id, all_volumes=True)

-     except koji.GenericError as e:

-         if 'got an unexpected keyword argument' not in str(e):

-             raise

-     # otherwise leave off the option and fake it

-     output = session.listTaskOutput(task_id)

-     return dict([fn, ['DEFAULT']] for fn in output)

- 

- 

- def handle_add_group(options, session, args):

-     "[admin] Add a group to a tag"

-     usage = _("usage: %prog add-group <tag> <group>")

-     usage += _("\n(Specify the --help global option for a list of other help options)")

-     parser = OptionParser(usage=usage)

-     (options, args) = parser.parse_args(args)

-     if len(args) != 2:

-         parser.error(_("Please specify a tag name and a group name"))

-         assert False  # pragma: no cover

-     tag = args[0]

-     group = args[1]

- 

-     activate_session(session)

-     if not session.hasPerm('admin'):

-         print("This action requires admin privileges")

-         return 1

- 

-     dsttag = session.getTag(tag)

-     if not dsttag:

-         print("Unknown tag: %s" % tag)

-         return 1

- 

-     groups = dict([(p['name'], p['group_id']) for p in session.getTagGroups(tag, inherit=False)])

-     group_id = groups.get(group, None)

-     if group_id is not None:

-         print("Group %s already exists for tag %s" % (group, tag))

-         return 1

- 

-     session.groupListAdd(tag, group)

- 

- def handle_assign_task(options, session, args):

-     "[admin] Assign a task to a host"

-     usage = _('usage: %prog assign-task task_id hostname')

-     usage += _('\n(Specify the --help global option for a list of other help options)')

-     parser = OptionParser(usage=usage)

-     parser.add_option('-f', '--force', action='store_true', default=False,

-                           help=_('force to assign a non-free task'))

-     (options, args) = parser.parse_args(args)

- 

-     if len(args) != 2:

-         parser.error(_('please specify a task id and a hostname'))

-     else:

-         task_id = int(args[0])

-         hostname = args[1]

- 

-     taskinfo = session.getTaskInfo(task_id, request=False)

-     if taskinfo is None:

-         raise koji.GenericError("No such task: %s" % task_id)

- 

-     hostinfo = session.getHost(hostname)

-     if hostinfo is None:

-         raise koji.GenericError("No such host: %s" % hostname)

- 

-     force = False

-     if options.force:

-         force = True

+     load_plugins(options)

  

-     activate_session(session)

-     if not session.hasPerm('admin'):

-         print("This action requires admin privileges")

-         return 1

- 

-     ret = session.assignTask(task_id, hostname, force)

-     if ret:

-         print('assigned task %d to host %s' % (task_id, hostname))

-     else:

-         print('failed to assign task %d to host %s' % (task_id, hostname))

- 

- 

- def handle_add_host(options, session, args):

-     "[admin] Add a host"

-     usage = _("usage: %prog add-host [options] hostname arch [arch2 ...]")

-     usage += _("\n(Specify the --help global option for a list of other help options)")

-     parser = OptionParser(usage=usage)

-     parser.add_option("--krb-principal", help=_("set a non-default kerberos principal for the host"))

-     (options, args) = parser.parse_args(args)

-     if len(args) < 2:

-         parser.error(_("Please specify a hostname and at least one arch"))

-         assert False  # pragma: no cover

-     host = args[0]

-     activate_session(session)

-     id = session.getHost(host)

-     if id:

-         print("%s is already in the database" % host)

-         return 1

-     else:

-         kwargs = {}

-         if options.krb_principal is not None:

-             kwargs['krb_principal'] = options.krb_principal

-         id = session.addHost(host, args[1:], **kwargs)

-         if id:

-             print("%s added: id %d" % (host, id))

- 

- def handle_edit_host(options, session, args):

-     "[admin] Edit a host"

-     usage = _("usage: %prog edit-host hostname ... [options]")

-     usage += _("\n(Specify the --help global option for a list of other help options)")

-     parser = OptionParser(usage=usage)

-     parser.add_option("--arches", help=_("Space or comma-separated list of supported architectures"))

-     parser.add_option("--capacity", type="float", help=_("Capacity of this host"))

-     parser.add_option("--description", metavar="DESC", help=_("Description of this host"))

-     parser.add_option("--comment", help=_("A brief comment about this host"))

-     (subopts, args) = parser.parse_args(args)

+     if options.help_commands:

+         list_commands()

+         sys.exit(0)

      if not args:

-         parser.error(_("Please specify a hostname"))

- 

-     activate_session(session)

- 

-     vals = {}

-     for key, val in subopts.__dict__.items():

-         if val is not None:

-             vals[key] = val

-     if 'arches' in vals:

-         vals['arches'] = parse_arches(vals['arches'])

- 

-     session.multicall = True

-     for host in args:

-         session.getHost(host)

-     error = False

-     for host, [info] in zip(args, session.multiCall(strict=True)):

-         if not info:

-             print(_("Host %s does not exist") % host)

-             error = True

- 

-     if error:

-         print(_("No changes made, please correct the command line"))

-         return 1

- 

-     session.multicall = True

-     for host in args:

-         session.editHost(host, **vals)

-     for host, [result] in zip(args, session.multiCall(strict=True)):

-         if result:

-             print(_("Edited %s") % host)

-         else:

-             print(_("No changes made to %s") % host)

- 

- def handle_add_host_to_channel(options, session, args):

-     "[admin] Add a host to a channel"

-     usage = _("usage: %prog add-host-to-channel [options] hostname channel")

-     usage += _("\n(Specify the --help global option for a list of other help options)")

-     parser = OptionParser(usage=usage)

-     parser.add_option("--list", action="store_true", help=optparse.SUPPRESS_HELP)

-     parser.add_option("--new", action="store_true", help=_("Create channel if needed"))

-     (options, args) = parser.parse_args(args)

-     if not options.list and len(args) != 2:

-         parser.error(_("Please specify a hostname and a channel"))

-         assert False  # pragma: no cover

-     activate_session(session)

-     if options.list:

-         for channel in session.listChannels():

-             print(channel['name'])

-         return

-     channel = args[1]

-     if not options.new:

-         channelinfo = session.getChannel(channel)

-         if not channelinfo:

-             print("No such channel: %s" % channel)

-             return 1

-     host = args[0]

-     hostinfo = session.getHost(host)

-     if not hostinfo:

-         print("No such host: %s" % host)

-         return 1

-     kwargs = {}

-     if options.new:

-         kwargs['create'] = True

-     session.addHostToChannel(host, channel, **kwargs)

- 

- def handle_remove_host_from_channel(options, session, args):

-     "[admin] Remove a host from a channel"

-     usage = _("usage: %prog remove-host-from-channel [options] hostname channel")

-     usage += _("\n(Specify the --help global option for a list of other help options)")

-     parser = OptionParser(usage=usage)

-     (options, args) = parser.parse_args(args)

-     if len(args) != 2:

-         parser.error(_("Please specify a hostname and a channel"))

-         assert False  # pragma: no cover

-     host = args[0]

-     activate_session(session)

-     hostinfo = session.getHost(host)

-     if not hostinfo:

-         print("No such host: %s" % host)

-         return 1

-     hostchannels = [c['name'] for c in session.listChannels(hostinfo['id'])]

- 

-     channel = args[1]

-     if channel not in hostchannels:

-         print("Host %s is not a member of channel %s" % (host, channel))

-         return 1

- 

-     session.removeHostFromChannel(host, channel)

- 

- def handle_remove_channel(options, session, args):

-     "[admin] Remove a channel entirely"

-     usage = _("usage: %prog remove-channel [options] channel")

-     usage += _("\n(Specify the --help global option for a list of other help options)")

-     parser = OptionParser(usage=usage)

-     parser.add_option("--force", action="store_true", help=_("force removal, if possible"))

-     (options, args) = parser.parse_args(args)

-     if len(args) != 1:

-         parser.error(_("Incorrect number of arguments"))

-         assert False  # pragma: no cover

-     activate_session(session)

-     cinfo = session.getChannel(args[0])

-     if not cinfo:

-         print("No such channel: %s" % args[0])

-         return 1

-     session.removeChannel(args[0], force=options.force)

- 

- def handle_rename_channel(options, session, args):

-     "[admin] Rename a channel"

-     usage = _("usage: %prog rename-channel [options] old-name new-name")

-     usage += _("\n(Specify the --help global option for a list of other help options)")

-     parser = OptionParser(usage=usage)

-     (options, args) = parser.parse_args(args)

-     if len(args) != 2:

-         parser.error(_("Incorrect number of arguments"))

-         assert False  # pragma: no cover

-     activate_session(session)

-     cinfo = session.getChannel(args[0])

-     if not cinfo:

-         print("No such channel: %s" % args[0])

-         return 1

-     session.renameChannel(args[0], args[1])

+         list_commands()

+         sys.exit(0)

  

- def handle_add_pkg(options, session, args):

-     "[admin] Add a package to the listing for tag"

-     usage = _("usage: %prog add-pkg [options] tag package [package2 ...]")

-     usage += _("\n(Specify the --help global option for a list of other help options)")

-     parser = OptionParser(usage=usage)

-     parser.add_option("--force", action='store_true', help=_("Override blocks if necessary"))

-     parser.add_option("--owner", help=_("Specify owner"))

-     parser.add_option("--extra-arches", help=_("Specify extra arches"))

-     (options, args) = parser.parse_args(args)

-     if len(args) < 2:

-         parser.error(_("Please specify a tag and at least one package"))

-         assert False  # pragma: no cover

-     if not options.owner:

-         parser.error(_("Please specify an owner for the package(s)"))

+     aliases = {

+         'cancel-task' : 'cancel',

+         'cxl' : 'cancel',

+         'list-commands' : 'help',

+         'move-pkg': 'move-build',

+         'move': 'move-build',

+         'latest-pkg': 'latest-build',

+         'tag-pkg': 'tag-build',

+         'tag': 'tag-build',

+         'untag-pkg': 'untag-build',

+         'untag': 'untag-build',

+         'watch-tasks': 'watch-task',

+     }

+     cmd = args[0]

+     cmd = aliases.get(cmd, cmd)

+     if cmd.lower() in greetings:

+         cmd = "moshimoshi"

+     cmd = cmd.replace('-', '_')

+     if ('anon_handle_' + cmd) in globals():

+         if not options.force_auth and '--mine' not in args:

+             options.noauth = True

+         cmd = 'anon_handle_' + cmd

+     elif ('handle_' + cmd) in globals():

+         cmd = 'handle_' + cmd

+     else:

+         list_commands()

+         parser.error('Unknown command: %s' % args[0])

          assert False  # pragma: no cover

-     if not session.getUser(options.owner):

-         print("User %s does not exist" % options.owner)

-         return 1

-     activate_session(session)

-     tag = args[0]

-     opts = {}

-     opts['force'] = options.force

-     opts['block'] = False

-     # check if list of packages exists for that tag already

-     dsttag=session.getTag(tag)

-     if dsttag is None:

-         print("No such tag: %s" % tag)

-         sys.exit(1)

-     pkglist = dict([(p['package_name'], p['package_id']) for p in session.listPackages(tagID=dsttag['id'])])

-     to_add = []

-     for package in args[1:]:

-         package_id = pkglist.get(package, None)

-         if not package_id is None:

-             print("Package %s already exists in tag %s" % (package, tag))

-             continue

-         to_add.append(package)

-     if options.extra_arches:

-         opts['extra_arches'] = parse_arches(options.extra_arches)

  

-     # add the packages

-     print("Adding %i packages to tag %s" % (len(to_add), dsttag['name']))

-     session.multicall = True

-     for package in to_add:

-         session.packageListAdd(tag, package, options.owner, **opts)

-     session.multiCall(strict=True)

+     return options, cmd, args[1:]

  

  

- def handle_block_pkg(options, session, args):

-     "[admin] Block a package in the listing for tag"

-     usage = _("usage: %prog block-pkg [options] tag package [package2 ...]")

+ def handle_help(options, session, args):

+     "[info] List available commands"

+     usage = _("usage: %prog help <category> ...")

      usage += _("\n(Specify the --help global option for a list of other help options)")

      parser = OptionParser(usage=usage)

-     (options, args) = parser.parse_args(args)

-     if len(args) < 2:

-         parser.error(_("Please specify a tag and at least one package"))

-         assert False  # pragma: no cover

-     activate_session(session)

-     tag = args[0]

-     # check if list of packages exists for that tag already

-     dsttag=session.getTag(tag)

-     if dsttag is None:

-         print("No such tag: %s" % tag)

-         return 1

-     pkglist = dict([(p['package_name'], p['package_id']) for p in session.listPackages(tagID=dsttag['id'], inherited=True)])

-     ret = 0

-     for package in args[1:]:

-         package_id = pkglist.get(package, None)

-         if package_id is None:

-             print("Package %s doesn't exist in tag %s" % (package, tag))

-             ret = 1

-     if ret:

-         return ret

-     session.multicall = True

-     for package in args[1:]:

-         session.packageListBlock(tag, package)

-     session.multiCall(strict=True)

+     # the --admin opt is for backwards compatibility. It is equivalent to: koji help admin

+     parser.add_option("--admin", action="store_true", help=optparse.SUPPRESS_HELP)

  

- def handle_remove_pkg(options, session, args):

-     "[admin] Remove a package from the listing for tag"

-     usage = _("usage: %prog remove-pkg [options] tag package [package2 ...]")

-     usage += _("\n(Specify the --help global option for a list of other help options)")

-     parser = OptionParser(usage=usage)

-     parser.add_option("--force", action='store_true', help=_("Override blocks if necessary"))

      (options, args) = parser.parse_args(args)

-     if len(args) < 2:

-         parser.error(_("Please specify a tag and at least one package"))

-         assert False  # pragma: no cover

-     activate_session(session)

-     tag = args[0]

-     opts = {}

-     opts['force'] = options.force

-     # check if list of packages exists for that tag already

-     dsttag=session.getTag(tag)

-     if dsttag is None:

-         print("No such tag: %s" % tag)

-         return 1

-     pkglist = dict([(p['package_name'], p['package_id']) for p in session.listPackages(tagID=dsttag['id'])])

-     ret = 0

-     for package in args[1:]:

-         package_id = pkglist.get(package, None)

-         if package_id is None:

-             print("Package %s is not in tag %s" % (package, tag))

-             ret = 1

-     if ret:

-         return ret

-     session.multicall = True

-     for package in args[1:]:

-         session.packageListRemove(tag, package, **opts)

-     session.multiCall(strict=True)

  

- def _unique_path(prefix):

-     """Create a unique path fragment by appending a path component

-     to prefix.  The path component will consist of a string of letter and numbers

-     that is unlikely to be a duplicate, but is not guaranteed to be unique."""

-     # Use time() in the dirname to provide a little more information when

-     # browsing the filesystem.

-     # For some reason repr(time.time()) includes 4 or 5

-     # more digits of precision than str(time.time())

-     return '%s/%r.%s' % (prefix, time.time(),

-                       ''.join([random.choice(string.ascii_letters) for i in range(8)]))

+     chosen = set(args)

+     if options.admin:

+         chosen.add('admin')

+     avail = set(list(categories.keys()) + ['all'])

+     unavail = chosen - avail

+     for arg in unavail:

+         print("No such help category: %s" % arg)

  

- def _format_size(size):

-     if (size / 1073741824 >= 1):

-         return "%0.2f GiB" % (size / 1073741824.0)

-     if (size / 1048576 >= 1):

-         return "%0.2f MiB" % (size / 1048576.0)

-     if (size / 1024 >=1):

-         return "%0.2f KiB" % (size / 1024.0)

-     return "%0.2f B" % (size)

+     if not chosen:

+         list_commands()

+     else:

+         list_commands(chosen)

  

- def _format_secs(t):

-     h = t / 3600

-     t %= 3600

-     m = t / 60

-     s = t % 60

-     return "%02d:%02d:%02d" % (h, m, s)

  

- def _progress_callback(uploaded, total, piece, time, total_time):

-     if total == 0:

-         percent_done = 0.0

+ def list_commands(categories_chosen=None):

+     if categories_chosen is None or "all" in categories_chosen:

+         categories_chosen = list(categories.keys())

      else:

-         percent_done = float(uploaded)/float(total)

-     percent_done_str = "%02d%%" % (percent_done * 100)

-     data_done = _format_size(uploaded)

-     elapsed = _format_secs(total_time)

+         # copy list since we're about to modify it

+         categories_chosen = list(categories_chosen)

+     categories_chosen.sort()

+     handlers = []

+     for name,value in globals().items():

+         if name.startswith('handle_'):

+             alias = name.replace('handle_','')

+             alias = alias.replace('_','-')

+             handlers.append((alias,value))

+         elif name.startswith('anon_handle_'):

+             alias = name.replace('anon_handle_','')

+             alias = alias.replace('_','-')

+             handlers.append((alias,value))

+     handlers.sort()

+     print(_("Available commands:"))

+     for category in categories_chosen:

+         print(_("\n%s:" % categories[category]))

+         for alias,handler in handlers:

+             desc = handler.__doc__

+             if desc.startswith('[%s] ' % category):

+                 desc = desc[len('[%s] ' % category):]

+             elif category != 'misc' or desc.startswith('['):

+                 continue

+             print("        %-25s %s" % (alias, desc))

  

-     speed = "- B/sec"

-     if (time):

-         if (uploaded != total):

-             speed = _format_size(float(piece)/float(time)) + "/sec"

-         else:

-             speed = _format_size(float(total)/float(total_time)) + "/sec"

+     print("%s" % get_epilog_str().rstrip("\n"))

  

-     # write formated string and flush

-     sys.stdout.write("[% -36s] % 4s % 8s % 10s % 14s\r" % ('='*(int(percent_done*36)), percent_done_str, elapsed, data_done, speed))

-     sys.stdout.flush()

- 

- def _running_in_bg():

-     try:

-         return (not os.isatty(0)) or (os.getpgrp() != os.tcgetpgrp(0))

-     except OSError as e:

-         return True

- 

- def handle_build(options, session, args):

-     "[build] Build a package from source"

-     usage = _("usage: %prog build [options] target <srpm path or scm url>")

-     usage += _("\n(Specify the --help global option for a list of other help options)")

-     parser = OptionParser(usage=usage)

-     parser.add_option("--skip-tag", action="store_true",

-                       help=_("Do not attempt to tag package"))

-     parser.add_option("--scratch", action="store_true",

-                       help=_("Perform a scratch build"))

-     parser.add_option("--wait", action="store_true",

-                       help=_("Wait on the build, even if running in the background"))

-     parser.add_option("--nowait", action="store_false", dest="wait",

-                       help=_("Don't wait on build"))

-     parser.add_option("--quiet", action="store_true",

-                       help=_("Do not print the task information"), default=options.quiet)

-     parser.add_option("--arch-override", help=_("Override build arches"))

-     parser.add_option("--repo-id", type="int", help=_("Use a specific repo"))

-     parser.add_option("--noprogress", action="store_true",

-                       help=_("Do not display progress of the upload"))

-     parser.add_option("--background", action="store_true",

-                       help=_("Run the build at a lower priority"))

-     (build_opts, args) = parser.parse_args(args)

-     if len(args) != 2:

-         parser.error(_("Exactly two arguments (a build target and a SCM URL or srpm file) are required"))

-         assert False  # pragma: no cover

-     if build_opts.arch_override and not build_opts.scratch:

-         parser.error(_("--arch_override is only allowed for --scratch builds"))

-     activate_session(session)

-     target = args[0]

-     if target.lower() == "none" and build_opts.repo_id:

-         target = None

-         build_opts.skip_tag = True

-     else:

-         build_target = session.getBuildTarget(target)

-         if not build_target:

-             parser.error(_("Unknown build target: %s" % target))

-         dest_tag = session.getTag(build_target['dest_tag'])

-         if not dest_tag:

-             parser.error(_("Unknown destination tag: %s" % build_target['dest_tag_name']))

-         if dest_tag['locked'] and not build_opts.scratch:

-             parser.error(_("Destination tag %s is locked" % dest_tag['name']))

-     source = args[1]

-     opts = {}

-     if build_opts.arch_override:

-         opts['arch_override'] = parse_arches(build_opts.arch_override)

-     for key in ('skip_tag', 'scratch', 'repo_id'):

-         val = getattr(build_opts, key)

-         if val is not None:

-             opts[key] = val

-     priority = None

-     if build_opts.background:

-         #relative to koji.PRIO_DEFAULT

-         priority = 5

-     # try to check that source is an SRPM

-     if '://' not in source:

-         #treat source as an srpm and upload it

-         if not build_opts.quiet:

-             print("Uploading srpm: %s" % source)

-         serverdir = _unique_path('cli-build')

-         if _running_in_bg() or build_opts.noprogress or build_opts.quiet:

-             callback = None

-         else:

-             callback = _progress_callback

-         session.uploadWrapper(source, serverdir, callback=callback)

-         print('')

-         source = "%s/%s" % (serverdir, os.path.basename(source))

-     task_id = session.build(source, target, opts, priority=priority)

-     if not build_opts.quiet:

-         print("Created task: %d" % task_id)

-         print("Task info: %s/taskinfo?taskID=%s" % (options.weburl, task_id))

-     if build_opts.wait or (build_opts.wait is None and not _running_in_bg()):

-         session.logout()

-         return watch_tasks(session, [task_id], quiet=build_opts.quiet)

-     else:

-         return

- 

- def handle_chain_build(options, session, args):

-     # XXX - replace handle_build with this, once chain-building has gotten testing

-     "[build] Build one or more packages from source"

-     usage = _("usage: %prog chain-build [options] target URL [URL2 [:] URL3 [:] URL4 ...]")

-     usage += _("\n(Specify the --help global option for a list of other help options)")

-     parser = OptionParser(usage=usage)

-     parser.add_option("--nowait", action="store_true",

-                       help=_("Don't wait on build"))

-     parser.add_option("--quiet", action="store_true",

-                       help=_("Do not print the task information"), default=options.quiet)

-     parser.add_option("--background", action="store_true",

-                       help=_("Run the build at a lower priority"))

-     (build_opts, args) = parser.parse_args(args)

-     if len(args) < 2:

-         parser.error(_("At least two arguments (a build target and a SCM URL) are required"))

-         assert False  # pragma: no cover

-     activate_session(session)

-     target = args[0]

-     build_target = session.getBuildTarget(target)

-     if not build_target:

-         parser.error(_("Unknown build target: %s" % target))

-     dest_tag = session.getTag(build_target['dest_tag'], strict=True)

-     if dest_tag['locked']:

-         parser.error(_("Destination tag %s is locked" % dest_tag['name']))

- 

-     # check that the destination tag is in the inheritance tree of the build tag

-     # otherwise there is no way that a chain-build can work

-     ancestors = session.getFullInheritance(build_target['build_tag'])

-     if dest_tag['id'] not in [build_target['build_tag']] + [ancestor['parent_id'] for ancestor in ancestors]:

-         print(_("Packages in destination tag %(dest_tag_name)s are not inherited by build tag %(build_tag_name)s" % build_target))

-         print(_("Target %s is not usable for a chain-build" % build_target['name']))

-         return 1

- 

-     sources = args[1:]

- 

-     src_list = []

-     build_level = []

-     #src_lists is a list of lists of sources to build.

-     #  each list is block of builds ("build level") which must all be completed

-     #  before the next block begins. Blocks are separated on the command line with ':'

-     for src in sources:

-         if src == ':':

-             if build_level:

-                 src_list.append(build_level)

-                 build_level = []

-         elif '://' in src:

-             # quick check that src might be a url

-             build_level.append(src)

-         elif '/' not in src and not src.endswith('.rpm') and len(src.split('-')) >= 3:

-             # quick check that it looks like a N-V-R

-             build_level.append(src)

-         else:

-             print(_('"%s" is not a SCM URL or package N-V-R' % src))

-             return 1

-     if build_level:

-         src_list.append(build_level)

- 

-     if len(src_list) < 2:

-         parser.error(_('You must specify at least one dependency between builds with : (colon)\nIf there are no dependencies, use the build command instead'))

- 

-     priority = None

-     if build_opts.background:

-         #relative to koji.PRIO_DEFAULT

-         priority = 5

- 

-     task_id = session.chainBuild(src_list, target, priority=priority)

-     if not build_opts.quiet:

-         print("Created task: %d" % task_id)

-         print("Task info: %s/taskinfo?taskID=%s" % (options.weburl, task_id))

-     if _running_in_bg() or build_opts.nowait:

-         return

-     else:

-         session.logout()

-         return watch_tasks(session, [task_id], quiet=build_opts.quiet)

- 

- def handle_maven_build(options, session, args):

-     "[build] Build a Maven package from source"

-     usage = _("usage: %prog maven-build [options] target URL")

-     usage += _("\n       %prog maven-build --ini=CONFIG... [options] target")

-     usage += _("\n(Specify the --help global option for a list of other help options)")

-     parser = OptionParser(usage=usage)

-     parser.add_option("--patches", action="store", metavar="URL",

-                       help=_("SCM URL of a directory containing patches to apply to the sources before building"))

-     parser.add_option("-G", "--goal", action="append",

-                       dest="goals", metavar="GOAL", default=[],

-                       help=_("Additional goal to run before \"deploy\""))

-     parser.add_option("-P", "--profile", action="append",

-                       dest="profiles", metavar="PROFILE", default=[],

-                       help=_("Enable a profile for the Maven build"))

-     parser.add_option("-D", "--property", action="append",

-                       dest="properties", metavar="NAME=VALUE", default=[],

-                       help=_("Pass a system property to the Maven build"))

-     parser.add_option("-E", "--env", action="append",

-                       dest="envs", metavar="NAME=VALUE", default=[],

-                       help=_("Set an environment variable"))

-     parser.add_option("-p", "--package", action="append",

-                       dest="packages", metavar="PACKAGE", default=[],

-                       help=_("Install an additional package into the buildroot"))

-     parser.add_option("-J", "--jvm-option", action="append",

-                       dest="jvm_options", metavar="OPTION", default=[],

-                       help=_("Pass a command-line option to the JVM"))

-     parser.add_option("-M", "--maven-option", action="append",

-                       dest="maven_options", metavar="OPTION", default=[],

-                       help=_("Pass a command-line option to Maven"))

-     parser.add_option("--ini", action="append",

-                       dest="inis", metavar="CONFIG", default=[],

-                       help=_("Pass build parameters via a .ini file"))

-     parser.add_option("-s", "--section",

-                       help=_("Get build parameters from this section of the .ini"))

-     parser.add_option("--debug", action="store_true",

-                       help=_("Run Maven build in debug mode"))

-     parser.add_option("--specfile", action="store", metavar="URL",

-                       help=_("SCM URL of a spec file fragment to use to generate wrapper RPMs"))

-     parser.add_option("--skip-tag", action="store_true",

-                       help=_("Do not attempt to tag package"))

-     parser.add_option("--scratch", action="store_true",

-                       help=_("Perform a scratch build"))

-     parser.add_option("--nowait", action="store_true",

-                       help=_("Don't wait on build"))

-     parser.add_option("--quiet", action="store_true",

-                       help=_("Do not print the task information"), default=options.quiet)

-     parser.add_option("--background", action="store_true",

-                       help=_("Run the build at a lower priority"))

-     (build_opts, args) = parser.parse_args(args)

-     if build_opts.inis:

-         if len(args) != 1:

-             parser.error(_("Exactly one argument (a build target) is required"))

-     else:

-         if len(args) != 2:

-             parser.error(_("Exactly two arguments (a build target and a SCM URL) are required"))

-     activate_session(session)

-     target = args[0]

-     build_target = session.getBuildTarget(target)

-     if not build_target:

-         parser.error(_("Unknown build target: %s" % target))

-     dest_tag = session.getTag(build_target['dest_tag'])

-     if not dest_tag:

-         parser.error(_("Unknown destination tag: %s" % build_target['dest_tag_name']))

-     if dest_tag['locked'] and not build_opts.scratch:

-         parser.error(_("Destination tag %s is locked" % dest_tag['name']))

-     if build_opts.inis:

-         try:

-             params = koji.util.parse_maven_param(build_opts.inis, scratch=build_opts.scratch,

-                                                  section=build_opts.section)

-         except ValueError as e:

-             parser.error(e.args[0])

-         opts = list(params.values())[0]

-         if opts.pop('type', 'maven') != 'maven':

-             parser.error(_("Section %s does not contain a maven-build config") % list(params.keys())[0])

-         source = opts.pop('scmurl')

-     else:

-         source = args[1]

-         opts = koji.util.maven_opts(build_opts, scratch=build_opts.scratch)

-     if '://' not in source:

-         parser.error(_("Invalid SCM URL: %s" % source))

-     if build_opts.debug:

-         opts.setdefault('maven_options', []).append('--debug')

-     if build_opts.skip_tag:

-         opts['skip_tag'] = True

-     priority = None

-     if build_opts.background:

-         #relative to koji.PRIO_DEFAULT

-         priority = 5

-     task_id = session.mavenBuild(source, target, opts, priority=priority)

-     if not build_opts.quiet:

-         print("Created task: %d" % task_id)

-         print("Task info: %s/taskinfo?taskID=%s" % (options.weburl, task_id))

-     if _running_in_bg() or build_opts.nowait:

-         return

-     else:

-         session.logout()

-         return watch_tasks(session, [task_id], quiet=build_opts.quiet)

- 

- def handle_wrapper_rpm(options, session, args):

-     """[build] Build wrapper rpms for any archives associated with a build."""

-     usage = _("usage: %prog wrapper-rpm [options] target build-id|n-v-r URL")

-     usage += _("\n(Specify the --help global option for a list of other help options)")

-     parser = OptionParser(usage=usage)

-     parser.add_option("--create-build", action="store_true", help=_("Create a new build to contain wrapper rpms"))

-     parser.add_option("--ini", action="append",

-                       dest="inis", metavar="CONFIG", default=[],

-                       help=_("Pass build parameters via a .ini file"))

-     parser.add_option("-s", "--section",

-                       help=_("Get build parameters from this section of the .ini"))

-     parser.add_option("--skip-tag", action="store_true", help=_("If creating a new build, don't tag it"))

-     parser.add_option("--scratch", action="store_true", help=_("Perform a scratch build"))

-     parser.add_option("--nowait", action="store_true", help=_("Don't wait on build"))

-     parser.add_option("--background", action="store_true", help=_("Run the build at a lower priority"))

- 

-     (build_opts, args) = parser.parse_args(args)

-     if build_opts.inis:

-         if len(args)!= 1:

-             parser.error(_("Exactly one argument (a build target) is required"))

-     else:

-         if len(args) < 3:

-             parser.error(_("You must provide a build target, a build ID or NVR, and a SCM URL to a specfile fragment"))

-     activate_session(session)

- 

-     target = args[0]

-     if build_opts.inis:

-         try:

-             params = koji.util.parse_maven_param(build_opts.inis, scratch=build_opts.scratch,

-                                                  section=build_opts.section)

-         except ValueError as e:

-             parser.error(e.args[0])

-         opts = list(params.values())[0]

-         if opts.get('type') != 'wrapper':

-             parser.error(_("Section %s does not contain a wrapper-rpm config") % list(params.keys())[0])

-         url = opts['scmurl']

-         package = opts['buildrequires'][0]

-         target_info = session.getBuildTarget(target, strict=True)

-         latest_builds = session.getLatestBuilds(target_info['dest_tag'], package=package)

-         if not latest_builds:

-             parser.error(_("No build of %s in %s") % (package, target_info['dest_tag_name']))

-         build_id = latest_builds[0]['nvr']

-     else:

-         build_id = args[1]

-         if build_id.isdigit():

-             build_id = int(build_id)

-         url = args[2]

-     priority = None

-     if build_opts.background:

-         priority = 5

-     opts = {}

-     if build_opts.create_build:

-         opts['create_build'] = True

-     if build_opts.skip_tag:

-         opts['skip_tag'] = True

-     if build_opts.scratch:

-         opts['scratch'] = True

-     task_id = session.wrapperRPM(build_id, url, target, priority, opts=opts)

-     print("Created task: %d" % task_id)

-     print("Task info: %s/taskinfo?taskID=%s" % (options.weburl, task_id))

-     if _running_in_bg() or build_opts.nowait:

-         return

-     else:

-         session.logout()

-         return watch_tasks(session,[task_id],quiet=options.quiet)

- 

- def handle_maven_chain(options, session, args):

-     "[build] Run a set of Maven builds in dependency order"

-     usage = _("usage: %prog maven-chain [options] target config...")

-     usage += _("\n(Specify the --help global option for a list of other help options)")

-     parser = OptionParser(usage=usage)

-     parser.add_option("--skip-tag", action="store_true",

-                       help=_("Do not attempt to tag builds"))

-     parser.add_option("--scratch", action="store_true",

-                       help=_("Perform scratch builds"))

-     parser.add_option("--debug", action="store_true",

-                       help=_("Run Maven build in debug mode"))

-     parser.add_option("--force", action="store_true",

-                       help=_("Force rebuilds of all packages"))

-     parser.add_option("--nowait", action="store_true",

-                       help=_("Don't wait on build"))

-     parser.add_option("--background", action="store_true",

-                       help=_("Run the build at a lower priority"))

-     (build_opts, args) = parser.parse_args(args)

-     if len(args) < 2:

-         parser.error(_("Two arguments (a build target and a config file) are required"))

-         assert False  # pragma: no cover

-     activate_session(session)

-     target = args[0]

-     build_target = session.getBuildTarget(target)

-     if not build_target:

-         parser.error(_("Unknown build target: %s") % target)

-     dest_tag = session.getTag(build_target['dest_tag'])

-     if not dest_tag:

-         parser.error(_("Unknown destination tag: %s") % build_target['dest_tag_name'])

-     if dest_tag['locked'] and not build_opts.scratch:

-         parser.error(_("Destination tag %s is locked") % dest_tag['name'])

-     opts = {}

-     for key in ('skip_tag', 'scratch', 'debug', 'force'):

-         val = getattr(build_opts, key)

-         if val:

-             opts[key] = val

-     try:

-         builds = koji.util.parse_maven_chain(args[1:], scratch=opts.get('scratch'))

-     except ValueError as e:

-         parser.error(e.args[0])

-     priority = None

-     if build_opts.background:

-         priority = 5

-     task_id = session.chainMaven(builds, target, opts, priority=priority)

-     print("Created task: %d" % task_id)

-     print("Task info: %s/taskinfo?taskID=%s" % (options.weburl, task_id))

-     if _running_in_bg() or build_opts.nowait:

-         return

-     else:

-         session.logout()

-         return watch_tasks(session, [task_id], quiet=options.quiet)

- 

- def handle_resubmit(options, session, args):

-     """[build] Retry a canceled or failed task, using the same parameter as the original task."""

-     usage = _("usage: %prog resubmit [options] taskID")

-     usage += _("\n(Specify the --help global option for a list of other help options)")

-     parser = OptionParser(usage=usage)

-     parser.add_option("--nowait", action="store_true", help=_("Don't wait on task"))

-     parser.add_option("--nowatch", action="store_true", dest="nowait",

-             help=_("An alias for --nowait"))

-     parser.add_option("--quiet", action="store_true", help=_("Do not print the task information"), default=options.quiet)

-     (options, args) = parser.parse_args(args)

-     if len(args) != 1:

-         parser.error(_("Please specify a single task ID"))

-         assert False  # pragma: no cover

-     activate_session(session)

-     taskID = int(args[0])

-     if not options.quiet:

-         print("Resubmitting the following task:")

-         _printTaskInfo(session, taskID, 0, False, True)

-     newID = session.resubmitTask(taskID)

-     if not options.quiet:

-         print("Resubmitted task %s as new task %s" % (taskID, newID))

-     if _running_in_bg() or options.nowait:

-         return

-     else:

-         session.logout()

-         return watch_tasks(session, [newID], quiet=options.quiet)

- 

- def handle_call(options, session, args):

-     "Execute an arbitrary XML-RPC call"

-     usage = _("usage: %prog call [options] name [arg...]")

-     usage += _("\n(Specify the --help global option for a list of other help options)")

-     parser = OptionParser(usage=usage)

-     parser.add_option("--python", action="store_true", help=_("Use python syntax for values"))

-     parser.add_option("--kwargs", help=_("Specify keyword arguments as a dictionary (implies --python)"))

-     parser.add_option("--json-output", action="store_true", help=_("Use JSON syntax for output"))

-     (options, args) = parser.parse_args(args)

-     if len(args) < 1:

-         parser.error(_("Please specify the name of the XML-RPC method"))

-         assert False  # pragma: no cover

-     if options.kwargs:

-         options.python = True

-     if options.python and ast is None:

-         parser.error(_("The ast module is required to read python syntax"))

-     if options.json_output and json is None:

-         parser.error(_("The json module is required to output JSON syntax"))

-     activate_session(session)

-     name = args[0]

-     non_kw = []

-     kw = {}

-     if options.python:

-         non_kw = [ast.literal_eval(a) for a in args[1:]]

-         if options.kwargs:

-             kw = ast.literal_eval(options.kwargs)

-     else:

-         for arg in args[1:]:

-             if arg.find('=') != -1:

-                 key, value = arg.split('=', 1)

-                 kw[key] = arg_filter(value)

-             else:

-                 non_kw.append(arg_filter(arg))

-     response = getattr(session, name).__call__(*non_kw, **kw)

-     if options.json_output:

-         print(json.dumps(response, indent=2, separators=(',', ': ')))

-     else:

-         pprint.pprint(response)

- 

- def anon_handle_mock_config(options, session, args):

-     "[info] Create a mock config"

-     usage = _("usage: %prog mock-config [options]")

-     usage += _("\n(Specify the --help global option for a list of other help options)")

-     parser = OptionParser(usage=usage)

-     parser.add_option("-a", "--arch", help=_("Specify the arch"))

-     parser.add_option("-n", "--name", help=_("Specify the name for the buildroot"))

-     parser.add_option("--tag", help=_("Create a mock config for a tag"))

-     parser.add_option("--target", help=_("Create a mock config for a build target"))

-     parser.add_option("--task", help=_("Duplicate the mock config of a previous task"))

-     parser.add_option("--latest", action="store_true", help=_("use the latest redirect url"))

-     parser.add_option("--buildroot", help=_("Duplicate the mock config for the specified buildroot id"))

-     parser.add_option("--mockdir", default="/var/lib/mock", metavar="DIR",

-                       help=_("Specify mockdir"))

-     parser.add_option("--topdir", metavar="DIR",

-                       help=_("Specify topdir"))

-     parser.add_option("--topurl", metavar="URL", default=options.topurl,

-                       help=_("URL under which Koji files are accessible"))

-     parser.add_option("--distribution", default="Koji Testing",

-                       help=_("Change the distribution macro"))

-     parser.add_option("--yum-proxy", help=_("Specify a yum proxy"))

-     parser.add_option("-o", metavar="FILE", dest="ofile", help=_("Output to a file"))

-     (options, args) = parser.parse_args(args)

-     activate_session(session)

-     if args:

-         #for historical reasons, we also accept buildroot name as first arg

-         if not options.name:

-             options.name = args[0]

-         else:

-             parser.error(_("Name already specified via option"))

-     arch = None

-     opts = {}

-     for k in ('topdir', 'topurl', 'distribution', 'mockdir', 'yum_proxy'):

-         if hasattr(options, k):

-             opts[k] = getattr(options, k)

-     if options.buildroot:

-         try:

-             br_id = int(options.buildroot)

-         except ValueError:

-             parser.error(_("Buildroot id must be an integer"))

-         brootinfo = session.getBuildroot(br_id)

-         if options.latest:

-             opts['repoid'] = 'latest'

-         else:

-             opts['repoid'] = brootinfo['repo_id']

-         opts['tag_name'] = brootinfo['tag_name']

-         arch = brootinfo['arch']

-     elif options.task:

-         try:

-             task_id = int(options.task)

-         except ValueError:

-             parser.error(_("Task id must be an integer"))

-         broots = session.listBuildroots(taskID=task_id)

-         if not broots:

-             print(_("No buildroots for task %s (or no such task)") % options.task)

-             return 1

-         if len(broots) > 1:

-             print(_("Multiple buildroots found: %s" % [br['id'] for br in broots]))

-         brootinfo = broots[-1]

-         if options.latest:

-             opts['repoid'] = 'latest'

-         else:

-             opts['repoid'] = brootinfo['repo_id']

-         opts['tag_name'] = brootinfo['tag_name']

-         arch = brootinfo['arch']

-         def_name = "%s-task_%i" % (opts['tag_name'], task_id)

-     elif options.tag:

-         if not options.arch:

-             print(_("Please specify an arch"))

-             return 1

-         tag = session.getTag(options.tag)

-         if not tag:

-             parser.error(_("Invalid tag: %s" % options.tag))

-         arch = options.arch

-         config = session.getBuildConfig(tag['id'])

-         if not config:

-             print(_("Could not get config info for tag: %(name)s") % tag)

-             return 1

-         opts['tag_name'] = tag['name']

-         if options.latest:

-             opts['repoid'] = 'latest'

-         else:

-             repo = session.getRepo(config['id'])

-             if not repo:

-                 print(_("Could not get a repo for tag: %(name)s") % tag)

-                 return 1

-             opts['repoid'] = repo['id']

-         def_name = "%(tag_name)s-repo_%(repoid)s" % opts

-     elif options.tar