From c14ecced37d5d4d062e2f4bf16e275b1bb7df1b9 Mon Sep 17 00:00:00 2001 From: Mike McLean Date: Jun 13 2017 09:20:15 +0000 Subject: [PATCH 1/19] script for split --- diff --git a/split_cli.py b/split_cli.py new file mode 100755 index 0000000..518cd03 --- /dev/null +++ b/split_cli.py @@ -0,0 +1,98 @@ +#!/usr/bin/python + +import imp +import inspect +import koji +import os.path +import sys + +filename = sys.argv[1] +fo = file(filename) +mod = imp.load_module('some_code', fo, fo.name, ('.py', 'U', 1)) +fo.close() + +destdir = sys.argv[2] +if not os.path.isdir(destdir): + raise Exception("Not a directory" % destdir) + +def get_dest(name, obj): + dest = None + if name in ['get_options', 'handle_help', 'list_commands']: + dest = 'cli/koji' + elif name in ['handle_runroot', 'handle_save_failed_tree']: + dest = 'plugins/cli/' + name[7:] + '.py' + elif name.startswith('handle_') or name.startswith('anon_handle'): + dest = 'cli/koji_cli/commands.py' + elif inspect.isclass(obj): + dest = 'cli/koji_cli/lib.py' + elif name.startswith('print_group_'): + dest = 'cli/koji_cli/commands.py' + elif name.startswith('_import_comps'): + dest = 'cli/koji_cli/commands.py' + elif not name.startswith('_'): + dest = 'cli/koji_cli/lib.py' + elif name in ['_unique_path', '_format_size', '_format_secs', + '_progress_callback', '_running_in_bg', '_']: + dest = 'cli/koji_cli/lib.py' + elif name.startswith('_'): + dest = 'cli/koji_cli/commands.py' + return dest + +order = [] +modfile = inspect.getsourcefile(mod) +sys.stderr.write("Module file: %r\n" % modfile) +for name in vars(mod): + obj = getattr(mod, name) + if inspect.isclass(obj) or inspect.isfunction(obj): + objfile = inspect.getsourcefile(obj) + if objfile != modfile: + sys.stderr.write("Skipping %s from %s\n" % (name, inspect.getfile(obj))) + continue + data = inspect.getsourcelines(obj) + lineno = data[1] + order.append((lineno, data[0], name, obj)) + + +dests = set() +for (lineno, source, name, obj) in sorted(order): + dest = get_dest(name, obj) + dests.add(dest) +outfiles = {} +for dest in dests: + if dest: + fn = os.path.join(destdir, dest) + outfiles[dest] = file(fn, 'w') + +orig = file(filename).readlines() +ofs = 0 +last_dest = None +for (lineno, source, name, obj) in sorted(order): + lineno -= 1 # make 0-indexed + dest = get_dest(name, obj) + if dest is None: + # the _ functions go different places + # defer (treat as intermediate content) + continue + if lineno > ofs: + # intermediate content + if last_dest == dest: + fo = outfiles[dest] + else: + fo = outfiles['cli/koji'] + for line in orig[ofs:lineno]: + fo.write(line) + fo = outfiles[dest] + for line in source: + fo.write(line) + ofs = lineno + len(source) + last_dest = dest + +sys.stderr.write('Orig: %i lines, ofs: %i\n' % (len(orig), ofs)) +if len(orig) > ofs: + sys.stderr.write('Writing tail\n') + fo = outfiles['cli/koji'] + for line in orig[ofs:]: + fo.write(line) + +for dest in outfiles: + outfiles[dest].close() From 411c6273f7c8ff74852ea020950e3db41557cbee Mon Sep 17 00:00:00 2001 From: Tomas Kopecek Date: Jun 13 2017 09:20:21 +0000 Subject: [PATCH 2/19] update split_cli.py for builtins --- diff --git a/split_cli.py b/split_cli.py index 518cd03..3bd0fab 100755 --- a/split_cli.py +++ b/split_cli.py @@ -44,7 +44,11 @@ sys.stderr.write("Module file: %r\n" % modfile) for name in vars(mod): obj = getattr(mod, name) if inspect.isclass(obj) or inspect.isfunction(obj): - objfile = inspect.getsourcefile(obj) + try: + objfile = inspect.getsourcefile(obj) + except TypeError as ex: + sys.stderr.write("Skipping %s from %s\n" % (name, obj)) + continue if objfile != modfile: sys.stderr.write("Skipping %s from %s\n" % (name, inspect.getfile(obj))) continue From a1a5f2f25ab767c627b6e0eecfae5c1c3e560a86 Mon Sep 17 00:00:00 2001 From: Tomas Kopecek Date: Jun 13 2017 09:20:24 +0000 Subject: [PATCH 3/19] touch __init__.py in koji_cli --- diff --git a/split_cli.py b/split_cli.py index 3bd0fab..909e80c 100755 --- a/split_cli.py +++ b/split_cli.py @@ -100,3 +100,5 @@ if len(orig) > ofs: for dest in outfiles: outfiles[dest].close() + +open('cli/koji_cli/__init__.py', 'a+').close() From 81b2b36d074f000cca96d393094b02343428e9d3 Mon Sep 17 00:00:00 2001 From: Tomas Kopecek Date: Jun 13 2017 09:20:38 +0000 Subject: [PATCH 4/19] apply split --- diff --git a/cli/koji b/cli/koji index c4a22cd..4bf44e9 100755 --- a/cli/koji +++ b/cli/koji @@ -112,57 +112,12 @@ greetings = ('hello', 'hi', 'yo', "what's up", "g'day", 'back to work', u'नमस्कार', u'안녕하세요') -def _(args): - """Stub function for translation""" - return args -def _printable_unicode(s): - if six.PY2: - return s.encode('utf-8') - else: - return s ARGMAP = {'None': None, 'True': True, 'False': False} -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 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 --help" for help about the options of a particular command -Try "%(progname)s help " 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""" @@ -284,7362 +239,74 @@ def get_options(): 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 ") - 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 - - 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 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]) - -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)")) - 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) - - -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 ...]") - 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) - -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)])) - -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) - -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 - else: - percent_done = float(uploaded)/float(total) - percent_done_str = "%02d%%" % (percent_done * 100) - data_done = _format_size(uploaded) - elapsed = _format_secs(total_time) - - 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" - - # 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 ") - 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...") +def handle_help(options, session, args): + "[info] List available commands" + usage = _("usage: %prog help ...") 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) + # 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_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) + 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 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.target: - if not options.arch: - print(_("Please specify an arch")) - return 1 - arch = options.arch - target = session.getBuildTarget(options.target) - if not target: - parser.error(_("Invalid target: %s" % options.target)) - opts['tag_name'] = target['build_tag_name'] - if options.latest: - opts['repoid'] = 'latest' - else: - repo = session.getRepo(target['build_tag']) - if not repo: - print(_("Could not get a repo for tag: %(name)s") % opts['tag_name']) - return 1 - opts['repoid'] = repo['id'] - else: - parser.error(_("Please specify one of: --tag, --target, --task, --buildroot")) - assert False # pragma: no cover - if options.name: - name = options.name - else: - name = "%(tag_name)s-repo_%(repoid)s" % opts - output = koji.genMockConfig(name, arch, **opts) - if options.ofile: - fo = open(options.ofile, 'w') - fo.write(output) - fo.close() + if not chosen: + list_commands() else: - print(output) - -def handle_disable_host(options, session, args): - "[admin] Mark one or more hosts as disabled" - usage = _("usage: %prog disable-host [options] hostname ...") - usage += _("\n(Specify the --help global option for a list of other help options)") - parser = OptionParser(usage=usage) - parser.add_option("--comment", help=_("Comment indicating why the host(s) are being disabled")) - (options, args) = parser.parse_args(args) - - activate_session(session) - session.multicall = True - for host in args: - session.getHost(host) - error = False - for host, [id] in zip(args, session.multiCall(strict=True)): - if not id: - 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.disableHost(host) - if options.comment is not None: - session.editHost(host, comment=options.comment) - session.multiCall(strict=True) - -def handle_enable_host(options, session, args): - "[admin] Mark one or more hosts as enabled" - usage = _("usage: %prog enable-host [options] hostname ...") - usage += _("\n(Specify the --help global option for a list of other help options)") - parser = OptionParser(usage=usage) - parser.add_option("--comment", help=_("Comment indicating why the host(s) are being enabled")) - (options, args) = parser.parse_args(args) - - activate_session(session) - session.multicall = True - for host in args: - session.getHost(host) - error = False - for host, [id] in zip(args, session.multiCall(strict=True)): - if not id: - 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.enableHost(host) - if options.comment is not None: - session.editHost(host, comment=options.comment) - session.multiCall(strict=True) - + list_commands(chosen) -def handle_restart_hosts(options, session, args): - "[admin] Restart enabled hosts" - usage = _("usage: %prog restart-hosts [options]") - usage += _("\n(Specify the --help global option for a list of other help options)") - parser = OptionParser(usage=usage) - parser.add_option("--wait", action="store_true", - help=_("Wait on the task, even if running in the background")) - parser.add_option("--nowait", action="store_false", dest="wait", - help=_("Don't wait on task")) - parser.add_option("--quiet", action="store_true", - help=_("Do not print the task information"), default=options.quiet) - (my_opts, args) = parser.parse_args(args) - activate_session(session) - task_id = session.restartHosts() - if my_opts.wait or (my_opts.wait is None and not _running_in_bg()): - session.logout() - return watch_tasks(session, [task_id], quiet=my_opts.quiet) +def list_commands(categories_chosen=None): + if categories_chosen is None or "all" in categories_chosen: + categories_chosen = list(categories.keys()) else: - return - - -def linked_upload(localfile, path, name=None): - """Link a file into the (locally writable) workdir, bypassing upload""" - old_umask = os.umask(0o02) - try: - if name is None: - name = os.path.basename(localfile) - dest_dir = os.path.join(koji.pathinfo.work(), path) - dst = os.path.join(dest_dir, name) - koji.ensuredir(dest_dir) - # fix uid/gid to keep httpd happy - st = os.stat(koji.pathinfo.work()) - os.chown(dest_dir, st.st_uid, st.st_gid) - print("Linking rpm to: %s" % dst) - os.link(localfile, dst) - finally: - os.umask(old_umask) + # 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)) + print("%s" % get_epilog_str().rstrip("\n")) -def handle_import(options, session, args): - "[admin] Import externally built RPMs into the database" - usage = _("usage: %prog import [options] package [package...]") - usage += _("\n(Specify the --help global option for a list of other help options)") - parser = OptionParser(usage=usage) - parser.add_option("--link", action="store_true", help=_("Attempt to hardlink instead of uploading")) - parser.add_option("--test", action="store_true", help=_("Don't actually import")) - parser.add_option("--create-build", action="store_true", help=_("Auto-create builds as needed")) - parser.add_option("--src-epoch", help=_("When auto-creating builds, use this epoch")) - (options, args) = parser.parse_args(args) - if len(args) < 1: - parser.error(_("At least one package must be specified")) - assert False # pragma: no cover - if options.src_epoch in ('None', 'none', '(none)'): - options.src_epoch = None - elif options.src_epoch: - try: - options.src_epoch = int(options.src_epoch) - except (ValueError, TypeError): - parser.error(_("Invalid value for epoch: %s") % options.src_epoch) - assert False # pragma: no cover - activate_session(session) - to_import = {} - for path in args: - data = koji.get_header_fields(path, ('name','version','release','epoch', - 'arch','sigmd5','sourcepackage','sourcerpm')) - if data['sourcepackage']: - data['arch'] = 'src' - nvr = "%(name)s-%(version)s-%(release)s" % data - else: - nvr = "%(name)s-%(version)s-%(release)s" % koji.parse_NVRA(data['sourcerpm']) - to_import.setdefault(nvr,[]).append((path,data)) - builds_missing = False - nvrs = list(to_import.keys()) - nvrs.sort() - for nvr in nvrs: - to_import[nvr].sort() - for path, data in to_import[nvr]: - if data['sourcepackage']: - break - else: - #no srpm included, check for build - binfo = session.getBuild(nvr) - if not binfo: - print(_("Missing build or srpm: %s") % nvr) - builds_missing = True - if builds_missing and not options.create_build: - print(_("Aborting import")) - return - - #local function to help us out below - def do_import(path, data): - rinfo = dict([(k,data[k]) for k in ('name','version','release','arch')]) - prev = session.getRPM(rinfo) - if prev and not prev.get('external_repo_id', 0): - if prev['payloadhash'] == koji.hex_string(data['sigmd5']): - print(_("RPM already imported: %s") % path) - else: - print(_("WARNING: md5sum mismatch for %s") % path) - print(_(" A different rpm with the same name has already been imported")) - print(_(" Existing sigmd5 is %r, your import has %r") % ( - prev['payloadhash'], koji.hex_string(data['sigmd5']))) - print(_("Skipping import")) - return - if options.test: - print(_("Test mode -- skipping import for %s") % path) - return - serverdir = _unique_path('cli-import') - if options.link: - linked_upload(path, serverdir) - else: - sys.stdout.write(_("uploading %s... ") % path) - sys.stdout.flush() - session.uploadWrapper(path, serverdir) - print(_("done")) - sys.stdout.flush() - sys.stdout.write(_("importing %s... ") % path) - sys.stdout.flush() - try: - session.importRPM(serverdir, os.path.basename(path)) - except koji.GenericError as e: - print(_("\nError importing: %s" % str(e).splitlines()[-1])) - sys.stdout.flush() - else: - print(_("done")) - sys.stdout.flush() - - for nvr in nvrs: - # check for existing build - need_build = True - binfo = session.getBuild(nvr) - if binfo: - b_state = koji.BUILD_STATES[binfo['state']] - if b_state == 'COMPLETE': - need_build = False - elif b_state in ['FAILED', 'CANCELED']: - if not options.create_build: - print(_("Build %s state is %s. Skipping import") % (nvr, b_state)) - continue - else: - print(_("Build %s exists with state=%s. Skipping import") % (nvr, b_state)) - continue - - # import srpms first, if any - for path, data in to_import[nvr]: - if data['sourcepackage']: - if binfo and b_state != 'COMPLETE': - # need to fix the state - print(_("Creating empty build: %s") % nvr) - b_data = koji.util.dslice(binfo, ['name', 'version', 'release']) - b_data['epoch'] = data['epoch'] - session.createEmptyBuild(**b_data) - binfo = session.getBuild(nvr) - do_import(path, data) - need_build = False - - if need_build: - # if we're doing this here, we weren't given the matching srpm - if not options.create_build: - if binfo: - # should have caught this earlier, but just in case... - b_state = koji.BUILD_STATES[binfo['state']] - print(_("Build %s state is %s. Skipping import") % (nvr, b_state)) - continue - else: - print(_("No such build: %s (include matching srpm or use " - "--create-build option to add it)") % nvr) - continue - else: - # let's make a new build - b_data = koji.parse_NVR(nvr) - if options.src_epoch: - b_data['epoch'] = options.src_epoch - else: - # pull epoch from first rpm - data = to_import[nvr][0][1] - b_data['epoch'] = data['epoch'] - if options.test: - print(_("Test mode -- would have created empty build: %s") % nvr) - else: - print(_("Creating empty build: %s") % nvr) - session.createEmptyBuild(**b_data) - binfo = session.getBuild(nvr) - - for path, data in to_import[nvr]: - if data['sourcepackage']: - continue - do_import(path, data) - - -def handle_import_cg(options, session, args): - "[admin] Import external builds with rich metadata" - usage = _("usage: %prog import-cg [options] metadata_file files_dir") - usage += _("\n(Specify the --help global option for a list of other help options)") - parser = OptionParser(usage=usage) - parser.add_option("--noprogress", action="store_true", - help=_("Do not display progress of the upload")) - parser.add_option("--link", action="store_true", help=_("Attempt to hardlink instead of uploading")) - parser.add_option("--test", action="store_true", help=_("Don't actually import")) - (options, args) = parser.parse_args(args) - if len(args) < 2: - parser.error(_("Please specify metadata files directory")) - assert False # pragma: no cover - if json is None: - parser.error(_("Unable to find json module")) - assert False # pragma: no cover - activate_session(session) - metadata = json.load(open(args[0], 'r')) - if 'output' not in metadata: - print(_("Metadata contains no output")) - sys.exit(1) - localdir = args[1] - - to_upload = [] - for info in metadata['output']: - if info.get('metadata_only', False): - continue - localpath = os.path.join(localdir, info.get('relpath', ''), info['filename']) - if not os.path.exists(localpath): - parser.error(_("No such file: %s") % localpath) - to_upload.append([localpath, info]) - - if options.test: - return - - # get upload path - # XXX - need a better way - serverdir = _unique_path('cli-import') - - for localpath, info in to_upload: - relpath = os.path.join(serverdir, info.get('relpath', '')) - if _running_in_bg() or options.noprogress: - callback = None - else: - callback = _progress_callback - if options.link: - linked_upload(localpath, relpath) - else: - print("Uploading %s" % localpath) - session.uploadWrapper(localpath, relpath, callback=callback) - if callback: - print('') - - session.CGImport(metadata, serverdir) - - -def handle_import_comps(options, session, args): - "Import group/package information from a comps file" - usage = _("usage: %prog import-comps [options] ") - 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 import")) - (local_options, args) = parser.parse_args(args) - if len(args) != 2: - parser.error(_("Incorrect number of arguments")) - assert False # pragma: no cover - activate_session(session) - # check if the tag exists - dsttag = session.getTag(args[1]) - if dsttag is None: - print("No such tag: %s" % args[1]) - return 1 - if libcomps is not None: - _import_comps(session, args[0], args[1], local_options) - elif yumcomps is not None: - _import_comps_alt(session, args[0], args[1], local_options) - else: - print("comps module not available") - return 1 - - -def _import_comps(session, filename, tag, options): - """Import comps data using libcomps module""" - comps = libcomps.Comps() - comps.fromxml_f(filename) - force = options.force - ptypes = { - libcomps.PACKAGE_TYPE_DEFAULT : 'default', - libcomps.PACKAGE_TYPE_OPTIONAL : 'optional', - libcomps.PACKAGE_TYPE_CONDITIONAL : 'conditional', - libcomps.PACKAGE_TYPE_MANDATORY : 'mandatory', - libcomps.PACKAGE_TYPE_UNKNOWN : 'unknown', - } - for group in comps.groups: - print("Group: %s (%s)" % (group.id, group.name)) - session.groupListAdd( - tag, group.id, force=force, display_name=group.name, - is_default=bool(group.default), - uservisible=bool(group.uservisible), - description=group.desc, - langonly=group.lang_only, - biarchonly=bool(group.biarchonly)) - for pkg in group.packages: - pkgopts = {'type' : ptypes[pkg.type], - 'basearchonly' : bool(pkg.basearchonly), - } - if pkg.type == libcomps.PACKAGE_TYPE_CONDITIONAL: - pkgopts['requires'] = pkg.requires - for k in pkgopts.keys(): - if six.PY2 and isinstance(pkgopts[k], unicode): - pkgopts[k] = str(pkgopts[k]) - s_opts = ', '.join(["'%s': %r" % (k, pkgopts[k]) for k in sorted(list(pkgopts.keys()))]) - print(" Package: %s: {%s}" % (pkg.name, s_opts)) - session.groupPackageListAdd(tag, group.id, pkg.name, force=force, **pkgopts) - # libcomps does not support group dependencies - # libcomps does not support metapkgs - - -def _import_comps_alt(session, filename, tag, options): - """Import comps data using yum.comps module""" - print('WARN: yum.comps does not support the biarchonly of group and basearchonly of package') - comps = yumcomps.Comps() - comps.add(filename) - force = options.force - for group in comps.groups: - print("Group: %(groupid)s (%(name)s)" % vars(group)) - session.groupListAdd(tag, group.groupid, force=force, display_name=group.name, - is_default=bool(group.default), - uservisible=bool(group.user_visible), - description=group.description, - langonly=group.langonly) - #yum.comps does not support the biarchonly field - for ptype, pdata in [('mandatory', group.mandatory_packages), - ('default', group.default_packages), - ('optional', group.optional_packages), - ('conditional', group.conditional_packages)]: - for pkg in pdata: - #yum.comps does not support basearchonly - pkgopts = {'type' : ptype} - if ptype == 'conditional': - pkgopts['requires'] = pdata[pkg] - for k in pkgopts.keys(): - if six.PY2 and isinstance(pkgopts[k], unicode): - pkgopts[k] = str(pkgopts[k]) - s_opts = ', '.join(["'%s': %r" % (k, pkgopts[k]) for k in sorted(list(pkgopts.keys()))]) - print(" Package: %s: {%s}" % (pkg, s_opts)) - session.groupPackageListAdd(tag, group.groupid, pkg, force=force, **pkgopts) - #yum.comps does not support group dependencies - #yum.comps does not support metapkgs - - -def handle_import_sig(options, session, args): - "[admin] Import signatures into the database" - usage = _("usage: %prog import-sig [options] package [package...]") - usage += _("\n(Specify the --help global option for a list of other help options)") - parser = OptionParser(usage=usage) - parser.add_option("--with-unsigned", action="store_true", - help=_("Also import unsigned sig headers")) - parser.add_option("--write", action="store_true", - help=_("Also write the signed copies")) - parser.add_option("--test", action="store_true", - help=_("Test mode -- don't actually import")) - (options, args) = parser.parse_args(args) - if len(args) < 1: - parser.error(_("At least one package must be specified")) - assert False # pragma: no cover - for path in args: - if not os.path.exists(path): - parser.error(_("No such file: %s") % path) - activate_session(session) - for path in args: - data = koji.get_header_fields(path, ('name','version','release','arch','siggpg','sigpgp','sourcepackage')) - if data['sourcepackage']: - data['arch'] = 'src' - sigkey = data['siggpg'] - if not sigkey: - sigkey = data['sigpgp'] - if not sigkey: - sigkey = "" - if not options.with_unsigned: - print(_("Skipping unsigned package: %s" % path)) - continue - else: - sigkey = koji.get_sigpacket_key_id(sigkey) - del data['siggpg'] - del data['sigpgp'] - rinfo = session.getRPM(data) - if not rinfo: - print("No such rpm in system: %(name)s-%(version)s-%(release)s.%(arch)s" % data) - continue - if rinfo.get('external_repo_id'): - print("Skipping external rpm: %(name)s-%(version)s-%(release)s.%(arch)s@%(external_repo_name)s" % rinfo) - continue - sighdr = koji.rip_rpm_sighdr(path) - previous = session.queryRPMSigs(rpm_id=rinfo['id'], sigkey=sigkey) - assert len(previous) <= 1 - if previous: - sighash = md5_constructor(sighdr).hexdigest() - if previous[0]['sighash'] == sighash: - print(_("Signature already imported: %s") % path) - continue - else: - print(_("Warning: signature mismatch: %s") % path) - print(_(" The system already has a signature for this rpm with key %s") % sigkey) - print(_(" The two signature headers are not the same")) - continue - print(_("Importing signature [key %s] from %s...") % (sigkey, path)) - if not options.test: - session.addRPMSig(rinfo['id'], base64.encodestring(sighdr)) - print(_("Writing signed copy")) - if not options.test: - session.writeSignedRPM(rinfo['id'], sigkey) - - -def handle_write_signed_rpm(options, session, args): - "[admin] Write signed RPMs to disk" - usage = _("usage: %prog write-signed-rpm [options] n-v-r [n-v-r...]") - usage += _("\n(Specify the --help global option for a list of other help options)") - parser = OptionParser(usage=usage) - parser.add_option("--all", action="store_true", help=_("Write out all RPMs signed with this key")) - parser.add_option("--buildid", help=_("Specify a build id rather than an n-v-r")) - (options, args) = parser.parse_args(args) - if len(args) < 1: - parser.error(_("A signature key must be specified")) - assert False # pragma: no cover - if len(args) < 2 and not (options.all or options.buildid): - parser.error(_("At least one RPM must be specified")) - assert False # pragma: no cover - key = args.pop(0) - activate_session(session) - if options.all: - rpms = session.queryRPMSigs(sigkey=key) - rpms = [session.getRPM(r['rpm_id']) for r in rpms] - elif options.buildid: - rpms = session.listRPMs(int(options.buildid)) - else: - rpms = [] - bad = [] - for nvra in args: - try: - koji.parse_NVRA(nvra) - rinfo = session.getRPM(nvra, strict=True) - if rinfo: - rpms.append(rinfo) - except koji.GenericError: - bad.append(nvra) - # for historical reasons, we also accept nvrs - for nvr in bad: - build = session.getBuild(nvr) - if not build: - raise koji.GenericError("No such rpm or build: %s" % nvr) - rpms.extend(session.listRPMs(buildID=build['id'])) - for i, rpminfo in enumerate(rpms): - nvra = "%(name)s-%(version)s-%(release)s.%(arch)s" % rpminfo - print("[%d/%d] %s" % (i+1, len(rpms), nvra)) - session.writeSignedRPM(rpminfo['id'], key) - - -def handle_prune_signed_copies(options, session, args): - "[admin] Prune signed copies" - usage = _("usage: %prog prune-sigs [options]") - usage += _("\n(Specify the --help global option for a list of other help options)") - parser = OptionParser(usage=usage) - parser.add_option("-n", "--test", action="store_true", help=_("Test mode")) - parser.add_option("-v", "--verbose", action="store_true", help=_("Be more verbose")) - parser.add_option("--days", type="int", default=5, help=_("Timeout before clearing")) - parser.add_option("-p", "--package", "--pkg", help=_("Limit to a single package")) - parser.add_option("-b", "--build", help=_("Limit to a single build")) - parser.add_option("-i", "--ignore-tag", action="append", default=[], - help=_("Ignore these tags when considering whether a build is/was latest")) - parser.add_option("--ignore-tag-file", - help=_("File to read tag ignore patterns from")) - parser.add_option("-r", "--protect-tag", action="append", default=[], - help=_("Do not prune signed copies from matching tags")) - parser.add_option("--protect-tag-file", - help=_("File to read tag protect patterns from")) - parser.add_option("--trashcan-tag", default="trashcan", help=_("Specify trashcan tag")) - parser.add_option("--debug", action="store_true", help=_("Show debugging output")) - (options, args) = parser.parse_args(args) - # different ideas/modes - # 1) remove all signed copies of builds that are not latest for some tag - # 2) remove signed copies when a 'better' signature is available - # 3) for a specified tag, remove all signed copies that are not latest (w/ inheritance) - # 4) for a specified tag, remove all signed copies (no inheritance) - # (but skip builds that are multiply tagged) - - #for now, we're just implementing mode #1 - #(with the modification that we check to see if the build was latest within - #the last N days) - if options.ignore_tag_file: - fo = open(options.ignore_tag_file) - options.ignore_tag.extend([line.strip() for line in fo.readlines()]) - fo.close() - if options.protect_tag_file: - fo = open(options.protect_tag_file) - options.protect_tag.extend([line.strip() for line in fo.readlines()]) - fo.close() - if options.debug: - options.verbose = True - cutoff_ts = time.time() - options.days * 24 * 3600 - if options.debug: - print("Cutoff date: %s" % time.asctime(time.localtime(cutoff_ts))) - if not options.build: - if options.verbose: - print("Getting builds...") - qopts = {'state' : koji.BUILD_STATES['COMPLETE']} - if options.package: - pkginfo = session.getPackage(options.package) - qopts['packageID'] = pkginfo['id'] - builds = [(b['nvr'], b) for b in session.listBuilds(**qopts)] - if options.verbose: - print("...got %i builds" % len(builds)) - builds.sort() - else: - #single build - binfo = session.getBuild(options.build) - if not binfo: - parser.error('No such build: %s' % options.build) - assert False # pragma: no cover - builds = [("%(name)s-%(version)s-%(release)s" % binfo, binfo)] - total_files = 0 - total_space = 0 - def _histline(event_id, x): - if event_id == x['revoke_event']: - ts = x['revoke_ts'] - fmt = "Untagged %(name)s-%(version)s-%(release)s from %(tag_name)s" - elif event_id == x['create_event']: - ts = x['create_ts'] - fmt = "Tagged %(name)s-%(version)s-%(release)s with %(tag_name)s" - if x['active']: - fmt += " [still active]" - else: - raise koji.GenericError("unknown event: (%r, %r)" % (event_id, x)) - time_str = time.asctime(time.localtime(ts)) - return "%s: %s" % (time_str, fmt % x) - for nvr, binfo in builds: - #listBuilds returns slightly different data than normal - if 'id' not in binfo: - binfo['id'] = binfo['build_id'] - if 'name' not in binfo: - binfo['name'] = binfo['package_name'] - if options.debug: - print("DEBUG: %s" % nvr) - #see how recently this build was latest for a tag - is_latest = False - is_protected = False - last_latest = None - tags = {} - for entry in session.tagHistory(build=binfo['id']): - #we used tagHistory rather than listTags so we can consider tags - #that the build was recently untagged from - tags.setdefault(entry['tag_name'], 1) - if options.debug: - print("Tags: %s" % list(tags.keys())) - for tag_name in tags: - if tag_name == options.trashcan_tag: - if options.debug: - print("Ignoring trashcan tag for build %s" % nvr) - continue - ignore_tag = False - for pattern in options.ignore_tag: - if fnmatch.fnmatch(tag_name, pattern): - if options.debug: - print("Ignoring tag %s for build %s" % (tag_name, nvr)) - ignore_tag = True - break - if ignore_tag: - continue - #in order to determine how recently this build was latest, we have - #to look at the tagging history. - hist = session.tagHistory(tag=tag_name, package=binfo['name']) - if not hist: - #really shouldn't happen - raise koji.GenericError("No history found for %s in %s" % (nvr, tag_name)) - timeline = [] - for x in hist: - #note that for revoked entries, we're effectively splitting them into - #two parts: creation and revocation. - timeline.append((x['create_event'], 1, x)) - #at the same event, revokes happen first - if x['revoke_event'] is not None: - timeline.append((x['revoke_event'], 0, x)) - timeline.sort() - #find most recent creation entry for our build and crop there - latest_ts = None - for i in range(len(timeline)-1, -1, -1): - #searching in reverse cronological order - event_id, is_create, entry = timeline[i] - if entry['build_id'] == binfo['id'] and is_create: - latest_ts = event_id - break - if not latest_ts: - #really shouldn't happen - raise koji.GenericError("No creation event found for %s in %s" % (nvr, tag_name)) - our_entry = entry - if options.debug: - print(_histline(event_id, our_entry)) - #now go through the events since most recent creation entry - timeline = timeline[i+1:] - if not timeline: - is_latest = True - if options.debug: - print("%s is latest in tag %s" % (nvr, tag_name)) - break - #before we go any further, is this a protected tag? - protect_tag = False - for pattern in options.protect_tag: - if fnmatch.fnmatch(tag_name, pattern): - protect_tag = True - break - if protect_tag: - # we use the same time limit as for the latest calculation - # if this build was in this tag within that limit, then we will - # not prune its signed copies - if our_entry['revoke_event'] is None: - #we're still tagged with a protected tag - if options.debug: - print("Build %s has protected tag %s" % (nvr, tag_name)) - is_protected = True - break - elif our_entry['revoke_ts'] > cutoff_ts: - #we were still tagged here sometime before the cutoff - if options.debug: - print("Build %s had protected tag %s until %s" \ - % (nvr, tag_name, time.asctime(time.localtime(our_entry['revoke_ts'])))) - is_protected = True - break - replaced_ts = None - revoke_ts = None - others = {} - for event_id, is_create, entry in timeline: - #So two things can knock this build from the title of latest: - # - it could be untagged (entry revoked) - # - another build could become latest (replaced) - #Note however that if the superceding entry is itself revoked, then - #our build could become latest again - if options.debug: - print(_histline(event_id, entry)) - if entry['build_id'] == binfo['id']: - if is_create: - #shouldn't happen - raise koji.GenericError("Duplicate creation event found for %s in %s" \ - % (nvr, tag_name)) - else: - #we've been revoked - revoke_ts = entry['revoke_ts'] - break - else: - if is_create: - #this build has become latest - replaced_ts = entry['create_ts'] - if entry['active']: - #this entry not revoked yet, so we're done for this tag - break - #since this entry is revoked later, our build might eventually be - #uncovered, so we have to keep looking - others[entry['build_id']] = 1 - else: - #other build revoked - #see if our build has resurfaced - if entry['build_id'] in others: - del others[entry['build_id']] - if replaced_ts is not None and not others: - #we've become latest again - #(note: we're not revoked yet because that triggers a break above) - replaced_ts = None - latest_ts = entry['revoke_ts'] - if last_latest is None: - timestamps = [] - else: - timestamps = [last_latest] - if revoke_ts is None: - if replaced_ts is None: - #turns out we are still latest - is_latest = True - if options.debug: - print("%s is latest (again) in tag %s" % (nvr, tag_name)) - break - else: - #replaced (but not revoked) - timestamps.append(replaced_ts) - if options.debug: - print("tag %s: %s not latest (replaced %s)" \ - % (tag_name, nvr, time.asctime(time.localtime(replaced_ts)))) - elif replaced_ts is None: - #revoked but not replaced - timestamps.append(revoke_ts) - if options.debug: - print("tag %s: %s not latest (revoked %s)" \ - % (tag_name, nvr, time.asctime(time.localtime(revoke_ts)))) - else: - #revoked AND replaced - timestamps.append(min(revoke_ts, replaced_ts)) - if options.debug: - print("tag %s: %s not latest (revoked %s, replaced %s)" \ - % (tag_name, nvr, time.asctime(time.localtime(revoke_ts)), - time.asctime(time.localtime(replaced_ts)))) - last_latest = max(timestamps) - if last_latest > cutoff_ts: - if options.debug: - print("%s was latest past the cutoff" % nvr) - is_latest = True - break - if is_latest: - continue - if is_protected: - continue - #not latest anywhere since cutoff, so we can remove all signed copies - rpms = session.listRPMs(buildID=binfo['id']) - session.multicall = True - for rpminfo in rpms: - session.queryRPMSigs(rpm_id=rpminfo['id']) - by_sig = {} - #index by sig - for rpminfo, [sigs] in zip(rpms, session.multiCall()): - for sig in sigs: - sigkey = sig['sigkey'] - by_sig.setdefault(sigkey, []).append(rpminfo) - builddir = koji.pathinfo.build(binfo) - build_files = 0 - build_space = 0 - if not by_sig and options.debug: - print("(build has no signatures)") - for sigkey, rpms in six.iteritems(by_sig): - mycount = 0 - archdirs = {} - sigdirs = {} - for rpminfo in rpms: - signedpath = "%s/%s" % (builddir, koji.pathinfo.signed(rpminfo, sigkey)) - try: - st = os.lstat(signedpath) - except OSError: - continue - if not stat.S_ISREG(st.st_mode): - #warn about this - print("Skipping %s. Not a regular file" % signedpath) - continue - if st.st_mtime > cutoff_ts: - print("Skipping %s. File newer than cutoff" % signedpath) - continue - if options.test: - print("Would have unlinked: %s" % signedpath) - else: - if options.verbose: - print("Unlinking: %s" % signedpath) - try: - os.unlink(signedpath) - except OSError as e: - print("Error removing %s: %s" % (signedpath, e)) - print("This script needs write access to %s" % koji.BASEDIR) - continue - mycount +=1 - build_files += 1 - build_space += st.st_size - #XXX - this makes some layout assumptions, but - # pathinfo doesn't report what we need - mydir = os.path.dirname(signedpath) - archdirs[mydir] = 1 - sigdirs[os.path.dirname(mydir)] = 1 - for dir in archdirs: - if options.test: - print("Would have removed dir: %s" % dir) - else: - if options.verbose: - print("Removing dir: %s" % dir) - try: - os.rmdir(dir) - except OSError as e: - print("Error removing %s: %s" % (signedpath, e)) - if len(sigdirs) == 1: - dir = list(sigdirs.keys())[0] - if options.test: - print("Would have removed dir: %s" % dir) - else: - if options.verbose: - print("Removing dir: %s" % dir) - try: - os.rmdir(dir) - except OSError as e: - print("Error removing %s: %s" % (signedpath, e)) - elif len(sigdirs) > 1: - print("Warning: more than one signature dir for %s: %r" % (sigkey, sigdirs)) - if build_files: - total_files += build_files - total_space += build_space - if options.verbose: - print("Build: %s, Removed %i signed copies (%i bytes). Total: %i/%i" \ - % (nvr, build_files, build_space, total_files, total_space)) - elif options.debug and by_sig: - print("(build has no signed copies)") - print("--- Grand Totals ---") - print("Files: %i" % total_files) - print("Bytes: %i" % total_space) - -def handle_set_build_volume(options, session, args): - "[admin] Move a build to a different volume" - usage = _("usage: %prog set-build-volume volume n-v-r [n-v-r ...]") - usage += _("\n(Specify the --help global option for a list of other help options)") - parser = OptionParser(usage=usage) - parser.add_option("-v", "--verbose", action="store_true", help=_("Be verbose")) - (options, args) = parser.parse_args(args) - if len(args) < 2: - parser.error("You must provide a volume and at least one build") - volinfo = session.getVolume(args[0]) - if not volinfo: - print("No such volume: %s" % args[0]) - return 1 - activate_session(session) - builds = [] - for nvr in args[1:]: - binfo = session.getBuild(nvr) - if not binfo: - print("No such build: %s" % nvr) - elif binfo['volume_id'] == volinfo['id']: - print("Build %s already on volume %s" %(nvr, volinfo['name'])) - else: - builds.append(binfo) - if not builds: - print("No builds to move") - return 1 - for binfo in builds: - session.changeBuildVolume(binfo['id'], volinfo['id']) - if options.verbose: - print("%s: %s -> %s" % (binfo['nvr'], binfo['volume_name'], volinfo['name'])) - -def handle_add_volume(options, session, args): - "[admin] Add a new storage volume" - usage = _("usage: %prog add-volume volume-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) != 1: - parser.error("Command requires exactly one volume-name.") - name = args[0] - volinfo = session.getVolume(name) - if volinfo: - print("Volume %s already exists" % name) - return 1 - activate_session(session) - volinfo = session.addVolume(name) - print("Added volume %(name)s with id %(id)i" % volinfo) - -def handle_list_volumes(options, session, args): - "[info] List storage volumes" - usage = _("usage: %prog list-volumes") - usage += _("\n(Specify the --help global option for a list of other help options)") - parser = OptionParser(usage=usage) - (options, args) = parser.parse_args(args) - for volinfo in session.listVolumes(): - print(volinfo['name']) - -def handle_list_permissions(options, session, args): - "[info] List user permissions" - usage = _("usage: %prog list-permissions [options]") - usage += _("\n(Specify the --help global option for a list of other help options)") - parser = OptionParser(usage=usage) - parser.add_option("--user", help=_("List permissions for the given user")) - parser.add_option("--mine", action="store_true", help=_("List your permissions")) - (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) - if options.user: - user = session.getUser(options.user) - if not user: - print("User %s does not exist" % options.user) - return 1 - perms = session.getUserPerms(user['id']) - elif options.mine: - perms = session.getPerms() - else: - perms = [p['name'] for p in session.getAllPerms()] - for perm in perms: - print(perm) - -def handle_add_user(options, session, args): - "[admin] Add a user" - usage = _("usage: %prog add-user username [options]") - usage += _("\n(Specify the --help global option for a list of other help options)") - parser = OptionParser(usage=usage) - parser.add_option("--principal", help=_("The Kerberos principal for this user")) - parser.add_option("--disable", help=_("Prohibit logins by this user"), action="store_true") - (options, args) = parser.parse_args(args) - if len(args) < 1: - parser.error(_("You must specify the username of the user to add")) - elif len(args) > 1: - parser.error(_("This command only accepts one argument (username)")) - username = args[0] - if options.disable: - status = koji.USER_STATUS['BLOCKED'] - else: - status = koji.USER_STATUS['NORMAL'] - activate_session(session) - user_id = session.createUser(username, status=status, krb_principal=options.principal) - print("Added user %s (%i)" % (username, user_id)) - -def handle_enable_user(options, session, args): - "[admin] Enable logins by a user" - usage = _("usage: %prog enable-user username") - 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) < 1: - parser.error(_("You must specify the username of the user to enable")) - elif len(args) > 1: - parser.error(_("This command only accepts one argument (username)")) - username = args[0] - activate_session(session) - session.enableUser(username) - -def handle_disable_user(options, session, args): - "[admin] Disable logins by a user" - usage = _("usage: %prog disable-user username") - 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) < 1: - parser.error(_("You must specify the username of the user to disable")) - elif len(args) > 1: - parser.error(_("This command only accepts one argument (username)")) - username = args[0] - activate_session(session) - session.disableUser(username) - -def handle_list_signed(options, session, args): - "[admin] List signed copies of rpms" - usage = _("usage: %prog list-signed [options]") - usage += _("\n(Specify the --help global option for a list of other help options)") - parser = OptionParser(usage=usage) - parser.add_option("--debug", action="store_true") - parser.add_option("--key", help=_("Only list RPMs signed with this key")) - parser.add_option("--build", help=_("Only list RPMs from this build")) - parser.add_option("--rpm", help=_("Only list signed copies for this RPM")) - parser.add_option("--tag", help=_("Only list RPMs within this tag")) - (options, args) = parser.parse_args(args) - activate_session(session) - qopts = {} - build_idx = {} - rpm_idx = {} - if options.key: - qopts['sigkey'] = options.key - if options.rpm: - rinfo = session.getRPM(options.rpm) - rpm_idx[rinfo['id']] = rinfo - if rinfo is None: - parser.error(_("No such RPM: %s") % options.rpm) - if rinfo.get('external_repo_id'): - print("External rpm: %(name)s-%(version)s-%(release)s.%(arch)s@%(external_repo_name)s" % rinfo) - return 1 - qopts['rpm_id'] = rinfo['id'] - if options.build: - binfo = session.getBuild(options.build) - build_idx[binfo['id']] = binfo - if binfo is None: - parser.error(_("No such build: %s") % options.rpm) - sigs = [] - rpms = session.listRPMs(buildID=binfo['id']) - for rinfo in rpms: - rpm_idx[rinfo['id']] = rinfo - sigs += session.queryRPMSigs(rpm_id=rinfo['id'], **qopts) - else: - sigs = session.queryRPMSigs(**qopts) - if options.tag: - print("getting tag listing") - rpms, builds = session.listTaggedRPMS(options.tag, inherit=False, latest=False) - print("got tag listing") - tagged = {} - for binfo in builds: - build_idx.setdefault(binfo['id'], binfo) - for rinfo in rpms: - rpm_idx.setdefault(rinfo['id'], rinfo) - tagged[rinfo['id']] = 1 - #Now figure out which sig entries actually have live copies - for sig in sigs: - rpm_id = sig['rpm_id'] - sigkey = sig['sigkey'] - if options.tag: - if tagged.get(rpm_id) is None: - continue - rinfo = rpm_idx.get(rpm_id) - if not rinfo: - rinfo = session.getRPM(rpm_id) - rpm_idx[rinfo['id']] = rinfo - binfo = build_idx.get(rinfo['build_id']) - if not binfo: - binfo = session.getBuild(rinfo['build_id']) - build_idx[binfo['id']] = binfo - binfo['name'] = binfo['package_name'] - builddir = koji.pathinfo.build(binfo) - signedpath = "%s/%s" % (builddir, koji.pathinfo.signed(rinfo, sigkey)) - if not os.path.exists(signedpath): - if options.debug: - print("No copy: %s" % signedpath) - continue - print(signedpath) - -def handle_import_in_place(options, session, args): - "[admin] Import RPMs that are already in place" - usage = _("usage: %prog import-in-place [options] package [package...]") - 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) < 1: - parser.error(_("At least one package must be specified")) - assert False # pragma: no cover - activate_session(session) - for nvr in args: - data = koji.parse_NVR(nvr) - sys.stdout.write(_("importing %s... ") % nvr) - try: - session.importBuildInPlace(data) - except koji.GenericError as e: - print(_("\nError importing: %s" % str(e).splitlines()[-1])) - sys.stdout.flush() - else: - print(_("done")) - sys.stdout.flush() - -def handle_import_archive(options, session, args): - "[admin] Import an archive file and associate it with a build" - usage = _("usage: %prog import-archive build-id|n-v-r /path/to/archive...") - usage += _("\n(Specify the --help global option for a list of other help options)") - parser = OptionParser(usage=usage) - parser.add_option("--noprogress", action="store_true", - help=_("Do not display progress of the upload")) - parser.add_option("--create-build", action="store_true", help=_("Auto-create builds as needed")) - parser.add_option("--link", action="store_true", help=_("Attempt to hardlink instead of uploading")) - parser.add_option("--type", help=_("The type of archive being imported. Currently supported types: maven, win, image")) - parser.add_option("--type-info", help=_("Type-specific information to associate with the archives. " - "For Maven archives this should be a local path to a .pom file. " - "For Windows archives this should be relpath:platforms[:flags])) " - "Images need an arch")) - (suboptions, args) = parser.parse_args(args) - - if not len(args) > 1: - parser.error(_("You must specify a build ID or N-V-R and an archive to import")) - assert False # pragma: no cover - - activate_session(session) - - if not suboptions.type: - parser.error(_("You must specify an archive type")) - assert False # pragma: no cover - if suboptions.type == 'maven': - if not (session.hasPerm('maven-import') or session.hasPerm('admin')): - parser.error(_("This action requires the maven-import privilege")) - assert False # pragma: no cover - if not suboptions.type_info: - parser.error(_("--type-info must point to a .pom file when importing Maven archives")) - assert False # pragma: no cover - pom_info = koji.parse_pom(suboptions.type_info) - maven_info = koji.pom_to_maven_info(pom_info) - suboptions.type_info = maven_info - elif suboptions.type == 'win': - if not (session.hasPerm('win-import') or session.hasPerm('admin')): - parser.error(_("This action requires the win-import privilege")) - assert False # pragma: no cover - if not suboptions.type_info: - parser.error(_("--type-info must be specified")) - assert False # pragma: no cover - type_info = suboptions.type_info.split(':', 2) - if len(type_info) < 2: - parser.error(_("--type-info must be in relpath:platforms[:flags] format")) - win_info = {'relpath': type_info[0], 'platforms': type_info[1].split()} - if len(type_info) > 2: - win_info['flags'] = type_info[2].split() - else: - win_info['flags'] = [] - suboptions.type_info = win_info - elif suboptions.type == 'image': - if not (session.hasPerm('image-import') or session.hasPerm('admin')): - parser.error(_("This action requires the image-import privilege")) - assert False # pragma: no cover - if not suboptions.type_info: - parser.error(_("--type-info must be specified")) - assert False # pragma: no cover - image_info = {'arch': suboptions.type_info} - suboptions.type_info = image_info - else: - parser.error(_("Unsupported archive type: %s" % suboptions.type)) - assert False # pragma: no cover - - buildinfo = session.getBuild(arg_filter(args[0])) - if not buildinfo: - if not suboptions.create_build: - parser.error(_("No such build: %s") % args[0]) - buildinfo = koji.parse_NVR(args[0]) - if buildinfo['epoch'] == '': - buildinfo['epoch'] = None - else: - buildinfo['epoch'] = int(buildinfo['epoch']) - if suboptions.type == 'maven': - # --type-info should point to a local .pom file - session.createMavenBuild(buildinfo, suboptions.type_info) - elif suboptions.type == 'win': - # We're importing, so we don't know what platform the build - # was run on. Use "import" as a placeholder. - session.createWinBuild(buildinfo, {'platform': 'import'}) - elif suboptions.type == 'image': - # --type-info should have an arch of the image - session.createImageBuild(buildinfo) - else: - # should get caught above - assert False # pragma: no cover - - for filepath in args[1:]: - filename = os.path.basename(filepath) - print("Uploading archive: %s" % filename) - serverdir = _unique_path('cli-import') - if _running_in_bg() or suboptions.noprogress: - callback = None - else: - callback = _progress_callback - if suboptions.link: - linked_upload(filepath, serverdir) - else: - session.uploadWrapper(filepath, serverdir, callback=callback) - print('') - serverpath = "%s/%s" % (serverdir, filename) - session.importArchive(serverpath, buildinfo, suboptions.type, suboptions.type_info) - print("Imported: %s" % filename) - -def handle_grant_permission(options, session, args): - "[admin] Grant a permission to a user" - usage = _("usage: %prog grant-permission [ ...]") - usage += _("\n(Specify the --help global option for a list of other help options)") - parser = OptionParser(usage=usage) - parser.add_option("--new", action="store_true", help=_("Create a new permission")) - (options, args) = parser.parse_args(args) - if len(args) < 2: - parser.error(_("Please specify a permission and at least one user")) - assert False # pragma: no cover - activate_session(session) - perm = args[0] - names = args[1:] - users = [] - for n in names: - user = session.getUser(n) - if user is None: - parser.error(_("No such user: %s" % n)) - assert False # pragma: no cover - users.append(user) - kwargs = {} - if options.new: - kwargs['create'] = True - for user in users: - session.grantPermission(user['name'], perm, **kwargs) - -def handle_revoke_permission(options, session, args): - "[admin] Revoke a permission from a user" - usage = _("usage: %prog revoke-permission [ ...]") - 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 permission and at least one user")) - assert False # pragma: no cover - activate_session(session) - perm = args[0] - names = args[1:] - users = [] - for n in names: - user = session.getUser(n) - if user is None: - parser.error(_("No such user: %s" % n)) - assert False # pragma: no cover - users.append(user) - for user in users: - session.revokePermission(user['name'], perm) - - -def handle_grant_cg_access(options, session, args): - "[admin] Add a user to a content generator" - usage = _("usage: %prog grant-cg-access ") - usage += _("\n(Specify the --help global option for a list of other help options)") - parser = OptionParser(usage=usage) - parser.add_option("--new", action="store_true", help=_("Create a new content generator")) - (options, args) = parser.parse_args(args) - if len(args) != 2: - parser.error(_("Please specify a user and content generator")) - assert False # pragma: no cover - activate_session(session) - user = args[0] - cg = args[1] - uinfo = session.getUser(user) - if uinfo is None: - parser.error(_("No such user: %s" % user)) - assert False # pragma: no cover - kwargs = {} - if options.new: - kwargs['create'] = True - session.grantCGAccess(uinfo['name'], cg, **kwargs) - - -def handle_revoke_cg_access(options, session, args): - "[admin] Remove a user from a content generator" - usage = _("usage: %prog revoke-cg-access ") - 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 user and content generator")) - assert False # pragma: no cover - activate_session(session) - user = args[0] - cg = args[1] - uinfo = session.getUser(user) - if uinfo is None: - parser.error(_("No such user: %s" % user)) - assert False # pragma: no cover - session.revokeCGAccess(uinfo['name'], cg) - - -def anon_handle_latest_build(options, session, args): - "[info] Print the latest builds for a tag" - usage = _("usage: %prog latest-build [options] tag package [package...]") - usage += _("\n(Specify the --help global option for a list of other help options)") - parser = OptionParser(usage=usage) - parser.add_option("--arch", help=_("List all of the latest packages for this arch")) - parser.add_option("--all", action="store_true", help=_("List all of the latest packages for this tag")) - parser.add_option("--quiet", action="store_true", help=_("Do not print the header information"), default=options.quiet) - parser.add_option("--paths", action="store_true", help=_("Show the file paths")) - parser.add_option("--type", help=_("Show builds of the given type only. Currently supported types: maven")) - (options, args) = parser.parse_args(args) - if len(args) == 0: - parser.error(_("A tag name must be specified")) - assert False # pragma: no cover - activate_session(session) - if options.all: - if len(args) > 1: - parser.error(_("A package name may not be combined with --all")) - assert False # pragma: no cover - # Set None as the package argument - args.append(None) - else: - if len(args) < 2: - parser.error(_("A tag name and package name must be specified")) - assert False # pragma: no cover - pathinfo = koji.PathInfo() - - for pkg in args[1:]: - if options.arch: - rpms, builds = session.getLatestRPMS(args[0], package=pkg, arch=options.arch) - builds_hash = dict([(x['build_id'], x) for x in builds]) - data = rpms - if options.paths: - for x in data: - z = x.copy() - x['name'] = builds_hash[x['build_id']]['package_name'] - x['path'] = os.path.join(pathinfo.build(x), pathinfo.rpm(z)) - fmt = "%(path)s" - else: - fmt = "%(name)s-%(version)s-%(release)s.%(arch)s" - else: - kwargs = {'package': pkg} - if options.type: - kwargs['type'] = options.type - data = session.getLatestBuilds(args[0], **kwargs) - if options.paths: - if options.type == 'maven': - for x in data: - x['path'] = pathinfo.mavenbuild(x) - fmt = "%(path)-40s %(tag_name)-20s %(maven_group_id)-20s %(maven_artifact_id)-20s %(owner_name)s" - else: - for x in data: - x['path'] = pathinfo.build(x) - fmt = "%(path)-40s %(tag_name)-20s %(owner_name)s" - else: - if options.type == 'maven': - fmt = "%(nvr)-40s %(tag_name)-20s %(maven_group_id)-20s %(maven_artifact_id)-20s %(owner_name)s" - else: - fmt = "%(nvr)-40s %(tag_name)-20s %(owner_name)s" - if not options.quiet: - if options.type == 'maven': - print("%-40s %-20s %-20s %-20s %s" % ("Build", "Tag", "Group Id", "Artifact Id", "Built by")) - print("%s %s %s %s %s" % ("-"*40, "-"*20, "-"*20, "-"*20, "-"*16)) - else: - print("%-40s %-20s %s" % ("Build","Tag","Built by")) - print("%s %s %s" % ("-"*40, "-"*20, "-"*16)) - options.quiet = True - - output = [ fmt % x for x in data] - output.sort() - for line in output: - print(line) - - -def anon_handle_list_api(options, session, args): - "[info] Print the list of XML-RPC APIs" - usage = _("usage: %prog list-api [options]") - 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) != 0: - parser.error(_("This command takes no arguments")) - assert False # pragma: no cover - activate_session(session) - tmplist = [(x['name'], x) for x in session._listapi()] - tmplist.sort() - funcs = [x[1] for x in tmplist] - for x in funcs: - if 'argdesc' in x: - args = x['argdesc'] - elif x['args']: - # older servers may not provide argdesc - expanded = [] - for arg in x['args']: - if type(arg) is str: - expanded.append(arg) - else: - expanded.append('%s=%s' % (arg[0], arg[1])) - args = "(%s)" % ", ".join(expanded) - else: - args = "()" - print('%s%s' % (x['name'], args)) - if x['doc']: - print(" description: %s" % x['doc']) - -def anon_handle_list_tagged(options, session, args): - "[info] List the builds or rpms in a tag" - usage = _("usage: %prog list-tagged [options] tag [package]") - usage += _("\n(Specify the --help global option for a list of other help options)") - parser = OptionParser(usage=usage) - parser.add_option("--arch", help=_("List rpms for this arch")) - parser.add_option("--rpms", action="store_true", help=_("Show rpms instead of builds")) - parser.add_option("--inherit", action="store_true", help=_("Follow inheritance")) - parser.add_option("--latest", action="store_true", help=_("Only show the latest builds/rpms")) - parser.add_option("--latest-n", type='int', metavar="N", help=_("Only show the latest N builds/rpms")) - parser.add_option("--quiet", action="store_true", help=_("Do not print the header information"), default=options.quiet) - parser.add_option("--paths", action="store_true", help=_("Show the file paths")) - parser.add_option("--sigs", action="store_true", help=_("Show signatures")) - parser.add_option("--type", help=_("Show builds of the given type only. Currently supported types: maven, win, image")) - parser.add_option("--event", type='int', metavar="EVENT#", help=_("query at event")) - parser.add_option("--ts", type='int', metavar="TIMESTAMP", help=_("query at timestamp")) - parser.add_option("--repo", type='int', metavar="REPO#", help=_("query at event for a repo")) - (options, args) = parser.parse_args(args) - if len(args) == 0: - parser.error(_("A tag name must be specified")) - assert False # pragma: no cover - elif len(args) > 2: - parser.error(_("Only one package name may be specified")) - assert False # pragma: no cover - activate_session(session) - pathinfo = koji.PathInfo() - package = None - if len(args) > 1: - package = args[1] - tag = args[0] - opts = {} - for key in ('latest','inherit'): - opts[key] = getattr(options, key) - if options.latest_n is not None: - opts['latest'] = options.latest_n - if package: - opts['package'] = package - if options.arch: - options.rpms = True - opts['arch'] = options.arch - if options.sigs: - opts['rpmsigs'] = True - options.rpms = True - if options.type: - opts['type'] = options.type - event = koji.util.eventFromOpts(session, options) - if event: - opts['event'] = event['id'] - event['timestr'] = time.asctime(time.localtime(event['ts'])) - if not options.quiet: - print("Querying at event %(id)i (%(timestr)s)" % event) - - if options.rpms: - rpms, builds = session.listTaggedRPMS(tag, **opts) - data = rpms - if options.paths: - build_idx = dict([(b['id'],b) for b in builds]) - for rinfo in data: - build = build_idx[rinfo['build_id']] - builddir = pathinfo.build(build) - if options.sigs: - sigkey = rinfo['sigkey'] - signedpath = os.path.join(builddir, pathinfo.signed(rinfo, sigkey)) - if os.path.exists(signedpath): - rinfo['path'] = signedpath - else: - rinfo['path'] = os.path.join(builddir, pathinfo.rpm(rinfo)) - fmt = "%(path)s" - data = [x for x in data if 'path' in x] - else: - fmt = "%(name)s-%(version)s-%(release)s.%(arch)s" - if options.sigs: - fmt = "%(sigkey)s " + fmt - else: - data = session.listTagged(tag, **opts) - if options.paths: - if options.type == 'maven': - for x in data: - x['path'] = pathinfo.mavenbuild(x) - fmt = "%(path)-40s %(tag_name)-20s %(maven_group_id)-20s %(maven_artifact_id)-20s %(owner_name)s" - else: - for x in data: - x['path'] = pathinfo.build(x) - fmt = "%(path)-40s %(tag_name)-20s %(owner_name)s" - else: - if options.type == 'maven': - fmt = "%(nvr)-40s %(tag_name)-20s %(maven_group_id)-20s %(maven_artifact_id)-20s %(owner_name)s" - else: - fmt = "%(nvr)-40s %(tag_name)-20s %(owner_name)s" - if not options.quiet: - if options.type == 'maven': - print("%-40s %-20s %-20s %-20s %s" % ("Build", "Tag", "Group Id", "Artifact Id", "Built by")) - print("%s %s %s %s %s" % ("-"*40, "-"*20, "-"*20, "-"*20, "-"*16)) - else: - print("%-40s %-20s %s" % ("Build","Tag","Built by")) - print("%s %s %s" % ("-"*40, "-"*20, "-"*16)) - - output = [ fmt % x for x in data] - output.sort() - for line in output: - print(line) - -def anon_handle_list_buildroot(options, session, args): - "[info] List the rpms used in or built in a buildroot" - usage = _("usage: %prog list-buildroot [options] buildroot-id") - usage += _("\n(Specify the --help global option for a list of other help options)") - parser = OptionParser(usage=usage) - parser.add_option("--paths", action="store_true", help=_("Show the file paths")) - parser.add_option("--built", action="store_true", help=_("Show the built rpms")) - parser.add_option("--verbose", "-v", action="store_true", help=_("Show more information")) - (options, args) = parser.parse_args(args) - if len(args) != 1: - parser.error(_("Incorrect number of arguments")) - assert False # pragma: no cover - activate_session(session) - buildrootID = int(args[0]) - opts = {} - if options.built: - opts['buildrootID'] = buildrootID - else: - opts['componentBuildrootID'] = buildrootID - data = session.listRPMs(**opts) - - fmt = "%(nvr)s.%(arch)s" - order = [(fmt % x, x) for x in data] - order.sort() - for nvra, rinfo in order: - if options.verbose and rinfo.get('is_update'): - print(nvra, "[update]") - else: - print(nvra) - -def anon_handle_list_untagged(options, session, args): - "[info] List untagged builds" - usage = _("usage: %prog list-untagged [options] [package]") - usage += _("\n(Specify the --help global option for a list of other help options)") - parser = OptionParser(usage=usage) - parser.add_option("--paths", action="store_true", help=_("Show the file paths")) - parser.add_option("--show-references", action="store_true", help=_("Show build references")) - (options, args) = parser.parse_args(args) - if len(args) > 1: - parser.error(_("Only one package name may be specified")) - assert False # pragma: no cover - activate_session(session) - package = None - if len(args) > 0: - package = args[0] - opts = {} - if package: - opts['name'] = package - pathinfo = koji.PathInfo() - - data = session.untaggedBuilds(**opts) - if options.show_references: - print("(Showing build references)") - refs = {} - refs2 = {} #reverse map - for x in session.buildMap(): - refs.setdefault(x['used'], {}).setdefault(x['built'], 1) - refs2.setdefault(x['built'], {}).setdefault(x['used'], 1) - #XXX - need to ignore refs to unreferenced builds - for x in data: - builds = refs.get(x['id']) - if builds: - x['refs'] = "%s" % builds - else: - x['refs'] = '' - #data = [x for x in data if x['id'] not in refs)] - if options.paths: - for x in data: - x['path'] = pathinfo.build(x) - fmt = "%(path)s" - else: - fmt = "%(name)s-%(version)s-%(release)s" - if options.show_references: - fmt = fmt + " %(refs)s" - - output = [ fmt % x for x in data] - output.sort() - for line in output: - print(line) - -def print_group_list_req_group(group): - print(" @%(name)s [%(tag_name)s]" % group) - -def print_group_list_req_package(pkg): - print(" %(package)s: %(basearchonly)s, %(type)s [%(tag_name)s]" % pkg) - -def anon_handle_list_groups(options, session, args): - "[info] Print the group listings" - usage = _("usage: %prog list-groups [options] [group]") - usage += _("\n(Specify the --help global option for a list of other help options)") - parser = OptionParser(usage=usage) - parser.add_option("--event", type='int', metavar="EVENT#", help=_("query at event")) - parser.add_option("--ts", type='int', metavar="TIMESTAMP", help=_("query at timestamp")) - parser.add_option("--repo", type='int', metavar="REPO#", help=_("query at event for a repo")) - (options, args) = parser.parse_args(args) - if len(args) < 1 or len(args) > 2: - parser.error(_("Incorrect number of arguments")) - assert False # pragma: no cover - opts = {} - activate_session(session) - event = koji.util.eventFromOpts(session, options) - if event: - opts['event'] = event['id'] - event['timestr'] = time.asctime(time.localtime(event['ts'])) - print("Querying at event %(id)i (%(timestr)s)" % event) - tags = dict([(x['id'], x['name']) for x in session.listTags()]) - tmp_list = [(x['name'], x) for x in session.getTagGroups(args[0], **opts)] - tmp_list.sort() - groups = [x[1] for x in tmp_list] - for group in groups: - if len(args) > 1 and group['name'] != args[1]: - continue - print("%s [%s]" % (group['name'], tags.get(group['tag_id'], group['tag_id']))) - groups = [(x['name'], x) for x in group['grouplist']] - groups.sort() - for x in [x[1] for x in groups]: - x['tag_name'] = tags.get(x['tag_id'], x['tag_id']) - print_group_list_req_group(x) - pkgs = [(x['package'], x) for x in group['packagelist']] - pkgs.sort() - for x in [x[1] for x in pkgs]: - x['tag_name'] = tags.get(x['tag_id'], x['tag_id']) - print_group_list_req_package(x) - -def handle_add_group_pkg(options, session, args): - "[admin] Add a package to a group's package listing" - usage = _("usage: %prog add-group-pkg [options] [...]") - 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) < 3: - parser.error(_("You must specify a tag name, group name, and one or more package names")) - assert False # pragma: no cover - tag = args[0] - group = args[1] - activate_session(session) - for pkg in args[2:]: - session.groupPackageListAdd(tag, group, pkg) - -def handle_block_group_pkg(options, session, args): - "[admin] Block a package from a group's package listing" - usage = _("usage: %prog block-group-pkg [options] [...]") - 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) < 3: - parser.error(_("You must specify a tag name, group name, and one or more package names")) - assert False # pragma: no cover - tag = args[0] - group = args[1] - activate_session(session) - for pkg in args[2:]: - session.groupPackageListBlock(tag, group, pkg) - -def handle_unblock_group_pkg(options, session, args): - "[admin] Unblock a package from a group's package listing" - usage = _("usage: %prog unblock-group-pkg [options] [...]") - 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) < 3: - parser.error(_("You must specify a tag name, group name, and one or more package names")) - assert False # pragma: no cover - tag = args[0] - group = args[1] - activate_session(session) - for pkg in args[2:]: - session.groupPackageListUnblock(tag, group, pkg) - -def handle_add_group_req(options, session, args): - "[admin] Add a group to a group's required list" - usage = _("usage: %prog add-group-req [options] ") - 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) != 3: - parser.error(_("You must specify a tag name and two group names")) - assert False # pragma: no cover - tag = args[0] - group = args[1] - req = args[2] - activate_session(session) - session.groupReqListAdd(tag, group, req) - -def handle_block_group_req(options, session, args): - "[admin] Block a group's requirement listing" - usage = _("usage: %prog block-group-req [options] ") - 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) != 3: - parser.error(_("You must specify a tag name and two group names")) - assert False # pragma: no cover - tag = args[0] - group = args[1] - req = args[2] - activate_session(session) - session.groupReqListBlock(tag, group, req) - -def handle_unblock_group_req(options, session, args): - "[admin] Unblock a group's requirement listing" - usage = _("usage: %prog unblock-group-req [options] ") - 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) != 3: - parser.error(_("You must specify a tag name and two group names")) - assert False # pragma: no cover - tag = args[0] - group = args[1] - req = args[2] - activate_session(session) - session.groupReqListUnblock(tag, group, req) - -def anon_handle_list_channels(options, session, args): - "[info] Print channels listing" - usage = _("usage: %prog list-channels") - usage += _("\n(Specify the --help global option for a list of other help options)") - parser = OptionParser(usage=usage) - parser.add_option("--quiet", action="store_true", help=_("Do not print header information"), default=options.quiet) - (options, args) = parser.parse_args(args) - activate_session(session) - channels = session.listChannels() - session.multicall = True - for channel in channels: - session.listHosts(channelID=channel['id']) - for channel, hosts in zip(channels, session.multiCall()): - channel['hosts'] = len(hosts[0]) - if not options.quiet: - print('Channel Hosts') - for channel in channels: - print("%(name)-15s %(hosts) 5d" % channel) - -def anon_handle_list_hosts(options, session, args): - "[info] Print the host listing" - usage = _("usage: %prog list-hosts [options]") - usage += _("\n(Specify the --help global option for a list of other help options)") - parser = OptionParser(usage=usage) - parser.add_option("--arch", action="append", default=[], help=_("Specify an architecture")) - parser.add_option("--channel", help=_("Specify a channel")) - parser.add_option("--ready", action="store_true", help=_("Limit to ready hosts")) - parser.add_option("--not-ready", action="store_false", dest="ready", help=_("Limit to not ready hosts")) - parser.add_option("--enabled", action="store_true", help=_("Limit to enabled hosts")) - parser.add_option("--not-enabled", action="store_false", dest="enabled", help=_("Limit to not enabled hosts")) - parser.add_option("--quiet", action="store_true", help=_("Do not print header information"), default=options.quiet) - (options, args) = parser.parse_args(args) - opts = {} - activate_session(session) - if options.arch: - opts['arches'] = options.arch - if options.channel: - channel = session.getChannel(options.channel) - if not channel: - parser.error(_('Unknown channel: %s' % options.channel)) - assert False # pragma: no cover - opts['channelID'] = channel['id'] - if options.ready is not None: - opts['ready'] = options.ready - if options.enabled is not None: - opts['enabled'] = options.enabled - tmp_list = [(x['name'], x) for x in session.listHosts(**opts)] - tmp_list.sort() - hosts = [x[1] for x in tmp_list] - - def yesno(x): - if x: return 'Y' - else: return 'N' - - # pull in the last update using multicall to speed it up a bit - session.multicall = True - for host in hosts: - session.getLastHostUpdate(host['id']) - updateList = session.multiCall() - - for host, [update] in zip(hosts, updateList): - if update is None: - host['update'] = '-' - else: - host['update'] = update.split('.')[0] - host['enabled'] = yesno(host['enabled']) - host['ready'] = yesno(host['ready']) - host['arches'] = ','.join(host['arches'].split()) - - if not options.quiet: - print("Hostname Enb Rdy Load/Cap Arches Last Update") - for host in hosts: - print("%(name)-28s %(enabled)-3s %(ready)-3s %(task_load)4.1f/%(capacity)-3.1f %(arches)-16s %(update)s" % host) - -def anon_handle_list_pkgs(options, session, args): - "[info] Print the package listing for tag or for owner" - usage = _("usage: %prog list-pkgs [options]") - usage += _("\n(Specify the --help global option for a list of other help options)") - parser = OptionParser(usage=usage) - parser.add_option("--owner", help=_("Specify owner")) - parser.add_option("--tag", help=_("Specify tag")) - parser.add_option("--package", help=_("Specify package")) - parser.add_option("--quiet", action="store_true", help=_("Do not print header information"), default=options.quiet) - parser.add_option("--noinherit", action="store_true", help=_("Don't follow inheritance")) - parser.add_option("--show-blocked", action="store_true", help=_("Show blocked packages")) - parser.add_option("--show-dups", action="store_true", help=_("Show superseded owners")) - parser.add_option("--event", type='int', metavar="EVENT#", help=_("query at event")) - parser.add_option("--ts", type='int', metavar="TIMESTAMP", help=_("query at timestamp")) - parser.add_option("--repo", type='int', metavar="REPO#", help=_("query at event for a repo")) - (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) - opts = {} - if options.owner: - user = session.getUser(options.owner) - if user is None: - parser.error(_("Invalid user")) - assert False # pragma: no cover - opts['userID'] = user['id'] - if options.tag: - tag = session.getTag(options.tag) - if tag is None: - parser.error(_("Invalid tag")) - assert False # pragma: no cover - opts['tagID'] = tag['id'] - if options.package: - opts['pkgID'] = options.package - allpkgs = False - if not opts: - # no limiting clauses were specified - allpkgs = True - opts['inherited'] = not options.noinherit - #hiding dups only makes sense if we're querying a tag - if options.tag: - opts['with_dups'] = options.show_dups - else: - opts['with_dups'] = True - event = koji.util.eventFromOpts(session, options) - if event: - opts['event'] = event['id'] - event['timestr'] = time.asctime(time.localtime(event['ts'])) - print("Querying at event %(id)i (%(timestr)s)" % event) - data = session.listPackages(**opts) - if not data: - print("(no matching packages)") - return 1 - if not options.quiet: - if allpkgs: - print("Package") - print('-'*23) - else: - print("%-23s %-23s %-16s %-15s" % ('Package','Tag','Extra Arches','Owner')) - print("%s %s %s %s" % ('-'*23,'-'*23,'-'*16,'-'*15)) - for pkg in data: - if allpkgs: - print(pkg['package_name']) - else: - if not options.show_blocked and pkg.get('blocked',False): - continue - if 'tag_id' in pkg: - if pkg['extra_arches'] is None: - pkg['extra_arches'] = "" - fmt = "%(package_name)-23s %(tag_name)-23s %(extra_arches)-16s %(owner_name)-15s" - if pkg.get('blocked',False): - fmt += " [BLOCKED]" - else: - fmt = "%(package_name)s" - print(fmt % pkg) - -def anon_handle_rpminfo(options, session, args): - "[info] Print basic information about an RPM" - usage = _("usage: %prog rpminfo [options] [ ...]") - usage += _("\n(Specify the --help global option for a list of other help options)") - parser = OptionParser(usage=usage) - parser.add_option("--buildroots", action="store_true", help=_("show buildroots the rpm was used in")) - (options, args) = parser.parse_args(args) - if len(args) < 1: - parser.error(_("Please specify an RPM")) - assert False # pragma: no cover - activate_session(session) - for rpm in args: - info = session.getRPM(rpm) - if info is None: - print("No such rpm: %s\n" % rpm) - continue - if info['epoch'] is None: - info['epoch'] = "" - else: - info['epoch'] = str(info['epoch']) + ":" - if not info.get('external_repo_id', 0): - buildinfo = session.getBuild(info['build_id']) - buildinfo['name'] = buildinfo['package_name'] - buildinfo['arch'] = 'src' - if buildinfo['epoch'] is None: - buildinfo['epoch'] = "" - else: - buildinfo['epoch'] = str(buildinfo['epoch']) + ":" - print("RPM: %(epoch)s%(name)s-%(version)s-%(release)s.%(arch)s [%(id)d]" % info) - if info.get('external_repo_id'): - repo = session.getExternalRepo(info['external_repo_id']) - print("External Repository: %(name)s [%(id)i]" % repo) - print("External Repository url: %(url)s" % repo) - else: - print("RPM Path: %s" % os.path.join(koji.pathinfo.build(buildinfo), koji.pathinfo.rpm(info))) - print("SRPM: %(epoch)s%(name)s-%(version)s-%(release)s [%(id)d]" % buildinfo) - print("SRPM Path: %s" % os.path.join(koji.pathinfo.build(buildinfo), koji.pathinfo.rpm(buildinfo))) - print("Built: %s" % time.strftime('%a, %d %b %Y %H:%M:%S %Z', time.localtime(info['buildtime']))) - print("SIGMD5: %(payloadhash)s" % info) - print("Size: %(size)s" % info) - if not info.get('external_repo_id', 0): - print("Build ID: %(build_id)s" % info) - if info['buildroot_id'] is None: - print("No buildroot data available") - else: - br_info = session.getBuildroot(info['buildroot_id']) - if br_info['br_type'] == koji.BR_TYPES['STANDARD']: - print("Buildroot: %(id)i (tag %(tag_name)s, arch %(arch)s, repo %(repo_id)i)" % br_info) - print("Build Host: %(host_name)s" % br_info) - print("Build Task: %(task_id)i" % br_info) - else: - print("Content generator: %(cg_name)s" % br_info) - print("Buildroot: %(id)i" % br_info) - print("Build Host OS: %(host_os)s (%(host_arch)s)" % br_info) - if info.get('extra'): - print("Extra: %(extra)r" % info) - if options.buildroots: - br_list = session.listBuildroots(rpmID=info['id'], queryOpts={'order':'buildroot.id'}) - print("Used in %i buildroots:" % len(br_list)) - if len(br_list): - print(" %8s %-28s %-8s %-29s" % ('id','build tag','arch','build host')) - print(" %s %s %s %s" % ('-'*8, '-'*28, '-'*8, '-'*29)) - for br_info in br_list: - print(" %(id)8i %(tag_name)-28s %(arch)-8s %(host_name)-29s" % br_info) - - -def anon_handle_buildinfo(options, session, args): - "[info] Print basic information about a build" - usage = _("usage: %prog buildinfo [options] [ ...]") - usage += _("\n(Specify the --help global option for a list of other help options)") - parser = OptionParser(usage=usage) - parser.add_option("--changelog", action="store_true", help=_("Show the changelog for the build")) - (options, args) = parser.parse_args(args) - if len(args) < 1: - parser.error(_("Please specify a build")) - assert False # pragma: no cover - activate_session(session) - for build in args: - if build.isdigit(): - build = int(build) - info = session.getBuild(build) - if info is None: - print("No such build: %s\n" % build) - continue - task = None - if info['task_id']: - task = session.getTaskInfo(info['task_id'], request=True) - taglist = [] - for tag in session.listTags(build): - taglist.append(tag['name']) - info['arch'] = 'src' - info['state'] = koji.BUILD_STATES[info['state']] - print("BUILD: %(name)s-%(version)s-%(release)s [%(id)d]" % info) - print("State: %(state)s" % info) - print("Built by: %(owner_name)s" % info) - source = info.get('source') - if source is not None: - print("Source: %s" % source) - if 'volume_name' in info: - print("Volume: %(volume_name)s" % info) - if task: - print("Task: %s %s" % (task['id'], koji.taskLabel(task))) - else: - print("Task: none") - print("Finished: %s" % koji.formatTimeLong(info['completion_time'])) - maven_info = session.getMavenBuild(info['id']) - if maven_info: - print("Maven groupId: %s" % maven_info['group_id']) - print("Maven artifactId: %s" % maven_info['artifact_id']) - print("Maven version: %s" % maven_info['version']) - win_info = session.getWinBuild(info['id']) - if win_info: - print("Windows build platform: %s" % win_info['platform']) - print("Tags: %s" % ' '.join(taglist)) - if info.get('extra'): - print("Extra: %(extra)r" % info) - archives_seen = {} - maven_archives = session.listArchives(buildID=info['id'], type='maven') - if maven_archives: - print("Maven archives:") - for archive in maven_archives: - archives_seen.setdefault(archive['id'], 1) - print(os.path.join(koji.pathinfo.mavenbuild(info), koji.pathinfo.mavenfile(archive))) - win_archives = session.listArchives(buildID=info['id'], type='win') - if win_archives: - print("Windows archives:") - for archive in win_archives: - archives_seen.setdefault(archive['id'], 1) - print(os.path.join(koji.pathinfo.winbuild(info), koji.pathinfo.winfile(archive))) - rpms = session.listRPMs(buildID=info['id']) - image_info = session.getImageBuild(info['id']) - img_archives = session.listArchives(buildID=info['id'], type='image') - if img_archives: - print('Image archives:') - for archive in img_archives: - archives_seen.setdefault(archive['id'], 1) - print(os.path.join(koji.pathinfo.imagebuild(info), archive['filename'])) - archive_idx = {} - for archive in session.listArchives(buildID=info['id']): - if archive['id'] in archives_seen: - continue - archive_idx.setdefault(archive['btype'], []).append(archive) - for btype in archive_idx: - archives = archive_idx[btype] - print('%s Archives:' % btype.capitalize()) - for archive in archives: - print(os.path.join(koji.pathinfo.typedir(info, btype), archive['filename'])) - if rpms: - print("RPMs:") - for rpm in rpms: - print(os.path.join(koji.pathinfo.build(info), koji.pathinfo.rpm(rpm))) - if options.changelog: - changelog = session.getChangelogEntries(info['id']) - if changelog: - print("Changelog:") - print(koji.util.formatChangelog(changelog)) - -def anon_handle_hostinfo(options, session, args): - "[info] Print basic information about a host" - usage = _("usage: %prog hostinfo [options] [ ...]") - 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) < 1: - parser.error(_("Please specify a host")) - assert False # pragma: no cover - activate_session(session) - for host in args: - if host.isdigit(): - host = int(host) - info = session.getHost(host) - if info is None: - print("No such host: %s\n" % host) - continue - print("Name: %(name)s" % info) - print("ID: %(id)d" % info) - print("Arches: %(arches)s" % info) - print("Capacity: %(capacity)s" % info) - print("Task Load: %(task_load).2f" % info) - if info['description']: - description = info['description'].splitlines() - print("Description: %s" % description[0]) - for line in description[1:]: - print("%s%s" % (" "*13, line)) - else: - print("Description:") - if info['comment']: - comment = info['comment'].splitlines() - print("Comment: %s" % comment[0]) - for line in comment[1:]: - print("%s%s" % (" "*9, line)) - else: - print("Comment:") - print("Enabled: %s" % (info['enabled'] and 'yes' or 'no')) - print("Ready: %s" % (info['ready'] and 'yes' or 'no')) - update = session.getLastHostUpdate(info['id']) - if update is None: - update = "never" - else: - update = update[:update.find('.')] - print("Last Update: %s" % update) - print("Channels: %s" % ' '.join([c['name'] for c in session.listChannels(hostID=info['id'])])) - print("Active Buildroots:") - states = {0:"INIT", 1:"WAITING", 2:"BUILDING"} - rows = [('NAME', 'STATE', 'CREATION TIME')] - for s in range(0,3): - for b in session.listBuildroots(hostID=info['id'], state=s): - rows.append((("%s-%s-%s" % (b['tag_name'], b['id'], b['repo_id'])), states[s], - b['create_event_time'][:b['create_event_time'].find('.')])) - if len(rows) > 1: - for row in rows: - print("%-50s %-10s %-20s" % row) - else: - print("None") - -def handle_clone_tag(options, session, args): - "[admin] Duplicate the contents of one tag onto another tag" - usage = _("usage: %prog clone-tag [options] ") - usage += _("\n(Specify the --help global option for a list of other help options)") - parser = OptionParser(usage=usage) - parser.add_option('--config', action='store_true', - help=_("Copy config from the source to the dest tag")) - parser.add_option('--groups', action='store_true', - help=_("Copy group information")) - parser.add_option('--pkgs', action='store_true', - help=_("Copy package list from the source to the dest tag")) - parser.add_option('--builds', action='store_true', - help=_("Tag builds into the dest tag")) - parser.add_option('--all', action='store_true', - help=_("The same as --config --groups --pkgs --builds")) - parser.add_option('--latest-only', action='store_true', - help=_("Tag only the latest build of each package")) - parser.add_option('--inherit-builds', action='store_true', - help=_("Include all builds inherited into the source tag into " - "the dest tag")) - parser.add_option('--ts', type='int', - help=_('Clone tag at a specific timestamp')) - parser.add_option('--event', type='int', - help=_('Clone tag at a specific event')) - parser.add_option('--repo', type='int', - help=_('Clone tag at a specific repo event')) - parser.add_option("-v","--verbose", action="store_true", - help=_("show changes")) - parser.add_option("-f","--force", action="store_true", - help=_("override tag locks if necessary")) - parser.add_option("-n","--test", action="store_true", help=_("test mode")) - (options, args) = parser.parse_args(args) - - if len(args) != 2: - parser.error(_("This command takes two arguments: ")) - assert False # pragma: no cover - activate_session(session) - - if not session.hasPerm('admin') and not options.test: - print(_("This action requires admin privileges")) - return - - if args[0] == args[1]: - sys.stdout.write('Source and destination tags must be different.\n') - return - - if options.all: - options.config = options.groups = options.pkgs = options.builds = True - - event = koji.util.eventFromOpts(session, options) or {} - if event: - event['timestr'] = time.asctime(time.localtime(event['ts'])) - print(_("Cloning at event %(id)i (%(timestr)s)") % event) - - # store tags. - srctag = session.getTag(args[0]) - dsttag = session.getTag(args[1]) - if not srctag: - sys.stdout.write("Unknown src-tag: %s\n" % args[0]) - return - if (srctag['locked'] and not options.force) or (dsttag and dsttag['locked'] and not options.force): - print(_("Error: You are attempting to clone from or to a tag which is locked.")) - print(_("Please use --force if this is what you really want to do.")) - return - - # init debug lists. - chgpkglist=[] - chgbldlist=[] - chggrplist=[] - # case of brand new dst-tag. - if not dsttag: - if not options.config: - print(_('Cannot create tag without specifying --config')) - return - # create a new tag, copy srctag header. - if not options.test: - session.createTag(args[1], parent=None, arches=srctag['arches'], - perm=srctag['perm_id'], - locked=srctag['locked'], - maven_support=srctag['maven_support'], - maven_include_all=srctag['maven_include_all']) - newtag = session.getTag(args[1]) # store the new tag, need its asigned id. - # get pkglist of src-tag, including inherited packages. - if options.pkgs: - srcpkgs = session.listPackages(tagID=srctag['id'], inherited=True, event=event.get('id')) - srcpkgs.sort(key = lambda x: x['package_name']) - if not options.test: - session.multicall = True - for pkgs in srcpkgs: - # for each package add one entry in the new tag. - chgpkglist.append(('[new]',pkgs['package_name'],pkgs['blocked'],pkgs['owner_name'],pkgs['tag_name'])) - if not options.test: - # add packages. - session.packageListAdd(newtag['name'],pkgs['package_name'], - owner=pkgs['owner_name'],block=pkgs['blocked'], - extra_arches=pkgs['extra_arches']) - if not options.test: - session.multiCall() - if options.builds: - # get --all latest builds from src tag - builds = session.listTagged(srctag['id'], event=event.get('id'), - inherit=options.inherit_builds, - latest=options.latest_only) - if not options.test: - session.multicall = True - for build in builds: - build['name'] = build['package_name'] # add missing 'name' field. - chgbldlist.append(('[new]', build['package_name'], - build['nvr'], koji.BUILD_STATES[build['state']], - build['owner_name'], build['tag_name'])) - # copy latest builds into new tag - if not options.test: - session.tagBuildBypass(newtag['name'], build, force=options.force) - if not options.test: - session.multiCall() - if options.groups: - # Copy the group data - srcgroups = session.getTagGroups(srctag['name'], event=event.get('id')) - if not options.test: - session.multicall = True - for group in srcgroups: - if not options.test: - session.groupListAdd(newtag['name'], group['name']) - for pkg in group['packagelist']: - if not options.test: - session.groupPackageListAdd(newtag['name'], group['name'], - pkg['package'], block=pkg['blocked']) - chggrplist.append(('[new]', pkg['package'], group['name'])) - if not options.test: - session.multiCall() - # case of existing dst-tag. - if dsttag: - # get fresh list of packages & builds into maps. - srcpkgs = {} - dstpkgs = {} - srclblds = {} - dstlblds = {} - srcgroups = {} - dstgroups = {} - if options.pkgs: - for pkg in session.listPackages(tagID=srctag['id'], inherited=True, event=event.get('id')): - srcpkgs[pkg['package_name']] = pkg - for pkg in session.listPackages(tagID=dsttag['id'], inherited=True): - dstpkgs[pkg['package_name']] = pkg - if options.builds: - src_builds = session.listTagged(srctag['id'], - event=event.get('id'), - inherit=options.inherit_builds, - latest=options.latest_only) - for build in src_builds: - srclblds[build['nvr']] = build - for build in session.getLatestBuilds(dsttag['name']): - dstlblds[build['nvr']] = build - if options.groups: - for group in session.getTagGroups(srctag['name'], event=event.get('id')): - srcgroups[group['name']] = group - for group in session.getTagGroups(dsttag['name']): - dstgroups[group['name']] = group - #construct to-do lists. - paddlist = [] # list containing new packages to be added from src tag - for (package_name, pkg) in six.iteritems(srcpkgs): - if package_name not in dstpkgs: - paddlist.append(pkg) - paddlist.sort(key = lambda x: x['package_name']) - pdellist = [] # list containing packages no more present in dst tag - for (package_name, pkg) in six.iteritems(dstpkgs): - if package_name not in srcpkgs: - pdellist.append(pkg) - pdellist.sort(key = lambda x: x['package_name']) - baddlist = [] # list containing new builds to be added from src tag - for (nvr, lbld) in six.iteritems(srclblds): - if nvr not in dstlblds: - baddlist.append(lbld) - baddlist.sort(key = lambda x: x['package_name']) - bdellist = [] # list containing new builds to be removed from src tag - for (nvr, lbld) in six.iteritems(dstlblds): - if nvr not in srclblds: - bdellist.append(lbld) - bdellist.sort(key = lambda x: x['package_name']) - gaddlist = [] # list containing new groups to be added from src tag - for (grpname, group) in six.iteritems(srcgroups): - if grpname not in dstgroups: - gaddlist.append(group) - gdellist = [] # list containing groups to be removed from src tag - for (grpname, group) in six.iteritems(dstgroups): - if grpname not in srcgroups: - gdellist.append(group) - grpchanges = {} # dict of changes to make in shared groups - for (grpname, group) in six.iteritems(srcgroups): - if grpname in dstgroups: - grpchanges[grpname] = {'adds':[], 'dels':[]} - # Store whether group is inherited or not - grpchanges[grpname]['inherited'] = False - if group['tag_id'] != dsttag['id']: - grpchanges[grpname]['inherited'] = True - srcgrppkglist=[] - dstgrppkglist=[] - for pkg in group['packagelist']: - srcgrppkglist.append(pkg['package']) - for pkg in dstgroups[grpname]['packagelist']: - dstgrppkglist.append(pkg['package']) - for pkg in srcgrppkglist: - if not pkg in dstgrppkglist: - grpchanges[grpname]['adds'].append(pkg) - for pkg in dstgrppkglist: - if not pkg in srcgrppkglist: - grpchanges[grpname]['dels'].append(pkg) - # ADD new packages. - if not options.test: - session.multicall = True - for pkg in paddlist: - chgpkglist.append(('[add]', pkg['package_name'], - pkg['blocked'], pkg['owner_name'], - pkg['tag_name'])) - if not options.test: - session.packageListAdd(dsttag['name'], pkg['package_name'], - owner=pkg['owner_name'], - block=pkg['blocked'], - extra_arches=pkg['extra_arches']) - if not options.test: - session.multiCall() - # ADD builds. - if not options.test: - session.multicall = True - for build in baddlist: - build['name'] = build['package_name'] # add missing 'name' field. - chgbldlist.append(('[add]', build['package_name'], build['nvr'], - koji.BUILD_STATES[build['state']], - build['owner_name'], build['tag_name'])) - # copy latest builds into new tag. - if not options.test: - session.tagBuildBypass(dsttag['name'], build, force=options.force) - if not options.test: - session.multiCall() - # ADD groups. - if not options.test: - session.multicall = True - for group in gaddlist: - if not options.test: - session.groupListAdd(dsttag['name'], group['name'], force=options.force) - for pkg in group['packagelist']: - if not options.test: - session.groupPackageListAdd(dsttag['name'], group['name'], pkg['package'], force=options.force) - chggrplist.append(('[new]', pkg['package'], group['name'])) - if not options.test: - session.multiCall() - # ADD group pkgs. - if not options.test: - session.multicall = True - for group in grpchanges: - for pkg in grpchanges[group]['adds']: - chggrplist.append(('[new]', pkg, group)) - if not options.test: - session.groupPackageListAdd(dsttag['name'], group, pkg, force=options.force) - if not options.test: - session.multiCall() - # DEL builds. - if not options.test: - session.multicall = True - for build in bdellist: - # dont delete an inherited build. - if build['tag_name'] == dsttag['name']: - build['name'] = build['package_name'] # add missing 'name' field. - chgbldlist.append(('[del]', build['package_name'], build['nvr'], - koji.BUILD_STATES[build['state']], - build['owner_name'], build['tag_name'])) - # go on del builds from new tag. - if not options.test: - session.untagBuildBypass(dsttag['name'], build, force=options.force) - if not options.test: - session.multiCall() - # DEL packages. - if not options.test: - session.multicall = True - for pkg in pdellist: - # delete only non-inherited packages. - if build['tag_name'] == dsttag['name']: - # check if package have owned builds inside. - builds = session.listTagged(dsttag['name'], package=pkg['package_name'], inherit=False) - #remove all its builds first if there are any. - for build in builds: - build['name'] = build['package_name'] #add missing 'name' field. - chgbldlist.append(('[del]', build['package_name'], build['nvr'], - koji.BUILD_STATES[build['state']], - build['owner_name'], build['tag_name'])) - # so delete latest build(s) from new tag. - if not options.test: - session.untagBuildBypass(dsttag['name'], build, force=options.force) - # now safe to remove package itselfm since we resolved its builds. - chgpkglist.append(('[del]', pkg['package_name'], pkg['blocked'], - pkg['owner_name'], pkg['tag_name'])) - if not options.test: - session.packageListRemove(dsttag['name'], pkg['package_name'], force=False) - # mark as blocked inherited packages. - if build['tag_name'] != dsttag['name']: - chgpkglist.append(('[blk]', pkg['package_name'], pkg['blocked'], - pkg['owner_name'], pkg['tag_name'])) - if not options.test: - session.packageListBlock(dsttag['name'], pkg['package_name']) - if not options.test: - session.multiCall() - # DEL groups. - if not options.test: - session.multicall = True - for group in gdellist: - # Only delete a group that isn't inherited - if group['tag_id'] == dsttag['id']: - if not options.test: - session.groupListRemove(dsttag['name'], group['name'], force=options.force) - for pkg in group['packagelist']: - chggrplist.append(('[del]', pkg['package'], group['name'])) - # mark as blocked inherited groups. - else: - if not options.test: - session.groupListBlock(dsttag['name'], group['name']) - for pkg in group['packagelist']: - chggrplist.append(('[blk]', pkg['package'], group['name'])) - if not options.test: - session.multiCall() - # DEL group pkgs. - if not options.test: - session.multicall = True - for group in grpchanges: - for pkg in grpchanges[group]['dels']: - # Only delete a group that isn't inherited - if not grpchanges[group]['inherited']: - chggrplist.append(('[del]', pkg, group)) - if not options.test: - session.groupPackageListRemove(dsttag['name'], group, pkg, force=options.force) - else: - chggrplist.append(('[blk]', pkg, group)) - if not options.test: - session.groupPackageListBlock(dsttag['name'], group, pkg) - if not options.test: - session.multiCall() - # print final list of actions. - if options.verbose: - pfmt=' %-7s %-28s %-10s %-10s %-10s\n' - bfmt=' %-7s %-28s %-40s %-10s %-10s %-10s\n' - gfmt=' %-7s %-28s %-28s\n' - sys.stdout.write('\nList of changes:\n\n') - sys.stdout.write(pfmt % ('Action', 'Package', 'Blocked', 'Owner', 'From Tag')) - sys.stdout.write(pfmt % ('-'*7, '-'*28, '-'*10, '-'*10, '-'*10)) - for changes in chgpkglist: - sys.stdout.write(pfmt % changes) - sys.stdout.write('\n') - sys.stdout.write(bfmt % ('Action', 'From/To Package', 'Latest Build(s)', 'State', 'Owner', 'From Tag')) - sys.stdout.write(bfmt % ('-'*7, '-'*28, '-'*40, '-'*10, '-'*10, '-'*10)) - for changes in chgbldlist: - sys.stdout.write(bfmt % changes) - sys.stdout.write('\n') - sys.stdout.write(gfmt % ('Action', 'Package', 'Group')) - sys.stdout.write(gfmt % ('-'*7, '-'*28, '-'*28)) - for changes in chggrplist: - sys.stdout.write(gfmt % changes) - - -def handle_add_target(options, session, args): - "[admin] Create a new build target" - usage = _("usage: %prog add-target name build-tag ") - 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 target name, a build tag, and destination tag")) - assert False # pragma: no cover - elif len(args) > 3: - parser.error(_("Incorrect number of arguments")) - assert False # pragma: no cover - name = args[0] - build_tag = args[1] - if len(args) > 2: - dest_tag = args[2] - else: - #most targets have the same name as their destination - dest_tag = name - activate_session(session) - if not session.hasPerm('admin'): - print("This action requires admin privileges") - return 1 - - chkbuildtag = session.getTag(build_tag) - chkdesttag = session.getTag(dest_tag) - if not chkbuildtag: - print("Build tag does not exist: %s" % build_tag) - return 1 - if not chkbuildtag.get("arches", None): - print("Build tag has no arches: %s" % build_tag) - return 1 - if not chkdesttag: - print("Destination tag does not exist: %s" % dest_tag) - return 1 - - session.createBuildTarget(name, build_tag, dest_tag) - -def handle_edit_target(options, session, args): - "[admin] Set the name, build_tag, and/or dest_tag of an existing build target to new values" - usage = _("usage: %prog edit-target [options] name") - usage += _("\n(Specify the --help global option for a list of other help options)") - parser = OptionParser(usage=usage) - parser.add_option("--rename", help=_("Specify new name for target")) - parser.add_option("--build-tag", help=_("Specify a different build tag")) - parser.add_option("--dest-tag", help=_("Specify a different destination tag")) - - (options, args) = parser.parse_args(args) - - if len(args) != 1: - parser.error(_("Please specify a build target")) - assert False # pragma: no cover - activate_session(session) - - if not session.hasPerm('admin'): - print("This action requires admin privileges") - return - - targetInfo = session.getBuildTarget(args[0]) - if targetInfo == None: - raise koji.GenericError("No build target with the name or id '%s'" % args[0]) - - targetInfo['orig_name'] = targetInfo['name'] - - if options.rename: - targetInfo['name'] = options.rename - if options.build_tag: - targetInfo['build_tag_name'] = options.build_tag - chkbuildtag = session.getTag(options.build_tag) - if not chkbuildtag: - print("Build tag does not exist: %s" % options.build_tag) - return 1 - if not chkbuildtag.get("arches", None): - print("Build tag has no arches: %s" % options.build_tag) - return 1 - if options.dest_tag: - chkdesttag = session.getTag(options.dest_tag) - if not chkdesttag: - print("Destination tag does not exist: %s" % options.dest_tag) - return 1 - targetInfo['dest_tag_name'] = options.dest_tag - - session.editBuildTarget(targetInfo['orig_name'], targetInfo['name'], targetInfo['build_tag_name'], targetInfo['dest_tag_name']) - -def handle_remove_target(options, session, args): - "[admin] Remove a build target" - usage = _("usage: %prog remove-target [options] 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) != 1: - parser.error(_("Please specify a build target to remove")) - assert False # pragma: no cover - activate_session(session) - - if not session.hasPerm('admin'): - print("This action requires admin privileges") - return - - target = args[0] - target_info = session.getBuildTarget(target) - if not target_info: - print("Build target %s does not exist" % target) - return 1 - - session.deleteBuildTarget(target_info['id']) - -def handle_remove_tag(options, session, args): - "[admin] Remove a tag" - usage = _("usage: %prog remove-tag [options] 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) != 1: - parser.error(_("Please specify a tag to remove")) - assert False # pragma: no cover - activate_session(session) - - if not session.hasPerm('admin'): - print("This action requires admin privileges") - return - - tag = args[0] - tag_info = session.getTag(tag) - if not tag_info: - print("Tag %s does not exist" % tag) - return 1 - - session.deleteTag(tag_info['id']) - -def anon_handle_list_targets(options, session, args): - "[info] List the build targets" - usage = _("usage: %prog list-targets [options]") - usage += _("\n(Specify the --help global option for a list of other help options)") - parser = OptionParser(usage=usage) - parser.add_option("--name", help=_("Specify the build target name")) - parser.add_option("--quiet", action="store_true", help=_("Do not print the header information"), default=options.quiet) - (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) - - fmt = "%(name)-30s %(build_tag_name)-30s %(dest_tag_name)-30s" - if not options.quiet: - print("%-30s %-30s %-30s" % ('Name','Buildroot','Destination')) - print("-" * 93) - tmp_list = [(x['name'], x) for x in session.getBuildTargets(options.name)] - tmp_list.sort() - targets = [x[1] for x in tmp_list] - for target in targets: - print(fmt % target) - #pprint.pprint(session.getBuildTargets()) - -def _printInheritance(tags, sibdepths=None, reverse=False): - if len(tags) == 0: - return - if sibdepths == None: - sibdepths = [] - currtag = tags[0] - tags = tags[1:] - if reverse: - siblings = len([tag for tag in tags if tag['parent_id'] == currtag['parent_id']]) - else: - siblings = len([tag for tag in tags if tag['child_id'] == currtag['child_id']]) - - outdepth = 0 - for depth in sibdepths: - if depth < currtag['currdepth']: - outspacing = depth - outdepth - sys.stdout.write(' ' * (outspacing * 3 - 1)) - sys.stdout.write(_printable_unicode(u'\u2502')) - outdepth = depth - - sys.stdout.write(' ' * ((currtag['currdepth'] - outdepth) * 3 - 1)) - if siblings: - sys.stdout.write(_printable_unicode(u'\u251c')) - else: - sys.stdout.write(_printable_unicode(u'\u2514')) - sys.stdout.write(_printable_unicode(u'\u2500')) - if reverse: - sys.stdout.write('%(name)s (%(tag_id)i)\n' % currtag) - else: - sys.stdout.write('%(name)s (%(parent_id)i)\n' % currtag) - - if siblings: - if len(sibdepths) == 0 or sibdepths[-1] != currtag['currdepth']: - sibdepths.append(currtag['currdepth']) - else: - if len(sibdepths) > 0 and sibdepths[-1] == currtag['currdepth']: - sibdepths.pop() - - _printInheritance(tags, sibdepths, reverse) - -def anon_handle_list_tag_inheritance(options, session, args): - "[info] Print the inheritance information for a tag" - usage = _("usage: %prog list-tag-inheritance [options] ") - usage += _("\n(Specify the --help global option for a list of other help options)") - parser = OptionParser(usage=usage) - parser.add_option("--reverse", action="store_true", help=_("Process tag's children instead of its parents")) - parser.add_option("--stop", help=_("Stop processing inheritance at this tag")) - parser.add_option("--jump", help=_("Jump from one tag to another when processing inheritance")) - parser.add_option("--event", type='int', metavar="EVENT#", help=_("query at event")) - parser.add_option("--ts", type='int', metavar="TIMESTAMP", help=_("query at timestamp")) - parser.add_option("--repo", type='int', metavar="REPO#", help=_("query at event for a repo")) - (options, args) = parser.parse_args(args) - if len(args) != 1: - parser.error(_("This command takes exctly one argument: a tag name or ID")) - assert False # pragma: no cover - activate_session(session) - event = koji.util.eventFromOpts(session, options) - if event: - event['timestr'] = time.asctime(time.localtime(event['ts'])) - print("Querying at event %(id)i (%(timestr)s)" % event) - if event: - tag = session.getTag(args[0], event=event['id']) - else: - tag = session.getTag(args[0]) - if not tag: - parser.error(_("Unknown tag: %s" % args[0])) - - opts = {} - opts['reverse'] = options.reverse or False - opts['stops'] = {} - opts['jumps'] = {} - if event: - opts['event'] = event['id'] - - if options.jump: - match = re.match(r'^(.*)/(.*)$', options.jump) - if match: - tag1 = session.getTagID(match.group(1)) - if not tag1: - parser.error(_("Unknown tag: %s" % match.group(1))) - tag2 = session.getTagID(match.group(2)) - if not tag2: - parser.error(_("Unknown tag: %s" % match.group(2))) - opts['jumps'][str(tag1)] = tag2 - - if options.stop: - tag1 = session.getTagID(options.stop) - if not tag1: - parser.error(_("Unknown tag: %s" % options.stop)) - opts['stops'] = {str(tag1): 1} - - sys.stdout.write('%s (%i)\n' % (tag['name'], tag['id'])) - data = session.getFullInheritance(tag['id'], **opts) - _printInheritance(data, None, opts['reverse']) - -def anon_handle_list_tags(options, session, args): - "[info] Print the list of tags" - usage = _("usage: %prog list-tags [options] [pattern]") - usage += _("\n(Specify the --help global option for a list of other help options)") - parser = OptionParser(usage=usage) - parser.add_option("--show-id", action="store_true", help=_("Show tag ids")) - parser.add_option("--verbose", action="store_true", help=_("Show more information")) - parser.add_option("--unlocked", action="store_true", help=_("Only show unlocked tags")) - parser.add_option("--build", help=_("Show tags associated with a build")) - parser.add_option("--package", help=_("Show tags associated with a package")) - (options, args) = parser.parse_args(args) - activate_session(session) - - pkginfo = {} - buildinfo = {} - - if options.package: - pkginfo = session.getPackage(options.package) - if not pkginfo: - parser.error(_("Invalid package %s" % options.package)) - assert False # pragma: no cover - - if options.build: - buildinfo = session.getBuild(options.build) - if not buildinfo: - parser.error(_("Invalid build %s" % options.build)) - assert False # pragma: no cover - - tags = session.listTags(buildinfo.get('id',None), pkginfo.get('id',None)) - tags.sort(key=lambda x: x['name']) - #if options.verbose: - # fmt = "%(name)s [%(id)i] %(perm)s %(locked)s %(arches)s" - if options.show_id: - fmt = "%(name)s [%(id)i]" - else: - fmt = "%(name)s" - for tag in tags: - if args: - for pattern in args: - if fnmatch.fnmatch(tag['name'], pattern): - break - else: - continue - if options.unlocked: - if tag['locked'] or tag['perm']: - continue - if not options.verbose: - print(fmt % tag) - else: - sys.stdout.write(fmt % tag) - if tag['locked']: - sys.stdout.write(' [LOCKED]') - if tag['perm']: - sys.stdout.write(' [%(perm)s perm required]' % tag) - print('') - -def anon_handle_list_tag_history(options, session, args): - "[info] Print a history of tag operations" - usage = _("usage: %prog list-tag-history [options]") - usage += _("\n(Specify the --help global option for a list of other help options)") - parser = OptionParser(usage=usage) - parser.add_option("--debug", action="store_true") - parser.add_option("--build", help=_("Only show data for a specific build")) - parser.add_option("--package", help=_("Only show data for a specific package")) - parser.add_option("--tag", help=_("Only show data for a specific tag")) - parser.add_option("--all", action="store_true", help=_("Allows listing the entire global history")) - (options, args) = parser.parse_args(args) - if len(args) != 0: - parser.error(_("This command takes no arguments")) - assert False # pragma: no cover - kwargs = {} - limited = False - if options.package: - kwargs['package'] = options.package - limited = True - if options.tag: - kwargs['tag'] = options.tag - limited = True - if options.build: - kwargs['build'] = options.build - limited = True - if not limited and not options.all: - parser.error(_("Please specify an option to limit the query")) - - activate_session(session) - - hist = session.tagHistory(**kwargs) - timeline = [] - for x in hist: - event_id = x['revoke_event'] - if event_id is not None: - timeline.append((event_id, x)) - event_id = x['create_event'] - timeline.append((event_id, x)) - timeline.sort() - def _histline(event_id, x): - if event_id == x['revoke_event']: - ts = x['revoke_ts'] - fmt = "%(name)s-%(version)s-%(release)s untagged from %(tag_name)s" - if 'revoker_name' in x: - fmt += " by %(revoker_name)s" - elif event_id == x['create_event']: - ts = x['create_ts'] - fmt = "%(name)s-%(version)s-%(release)s tagged into %(tag_name)s" - if 'creator_name' in x: - fmt += " by %(creator_name)s" - if x['active']: - fmt += " [still active]" - else: - raise koji.GenericError("unknown event: (%r, %r)" % (event_id, x)) - time_str = time.asctime(time.localtime(ts)) - return "%s: %s" % (time_str, fmt % x) - for event_id, x in timeline: - if options.debug: - print("%r" % x) - print(_histline(event_id, x)) - -def _print_histline(entry, **kwargs): - options = kwargs['options'] - event_id, table, create, x = entry - who = None - edit = x.get('.related') - if edit: - del x['.related'] - bad_edit = None - if len(edit) != 1: - bad_edit = "%i elements" % len(edit)+1 - other = edit[0] - #check edit for sanity - if create or not other[2]: - bad_edit = "out of order" - if event_id != other[0]: - bad_edit = "non-matching" - if bad_edit: - print("Warning: unusual edit at event %i in table %s (%s)" % (event_id, table, bad_edit)) - #we'll simply treat them as separate events - pprint.pprint(entry) - pprint.pprint(edit) - _print_histline(entry, **kwargs) - for data in edit: - _print_histline(entry, **kwargs) - return - if create: - ts = x['create_ts'] - if 'creator_name' in x: - who = "by %(creator_name)s" - else: - ts = x['revoke_ts'] - if 'revoker_name' in x: - who = "by %(revoker_name)s" - if table == 'tag_listing': - if edit: - fmt = "%(name)s-%(version)s-%(release)s re-tagged into %(tag.name)s" - elif create: - fmt = "%(name)s-%(version)s-%(release)s tagged into %(tag.name)s" - else: - fmt = "%(name)s-%(version)s-%(release)s untagged from %(tag.name)s" - elif table == 'user_perms': - if edit: - fmt = "permission %(permission.name)s re-granted to %(user.name)s" - elif create: - fmt = "permission %(permission.name)s granted to %(user.name)s" - else: - fmt = "permission %(permission.name)s revoked for %(user.name)s" - elif table == 'user_groups': - if edit: - fmt = "user %(user.name)s re-added to group %(group.name)s" - elif create: - fmt = "user %(user.name)s added to group %(group.name)s" - else: - fmt = "user %(user.name)s removed from group %(group.name)s" - elif table == 'cg_users': - if edit: - fmt = "user %(user.name)s re-added to content generator %(content_generator.name)s" - elif create: - fmt = "user %(user.name)s added to content generator %(content_generator.name)s" - else: - fmt = "user %(user.name)s removed from content generator %(content_generator.name)s" - elif table == 'tag_packages': - if edit: - fmt = "package list entry for %(package.name)s in %(tag.name)s updated" - elif create: - fmt = "package list entry created: %(package.name)s in %(tag.name)s" - else: - fmt = "package list entry revoked: %(package.name)s in %(tag.name)s" - elif table == 'tag_inheritance': - if edit: - fmt = "inheritance line %(tag.name)s->%(parent.name)s updated" - elif create: - fmt = "inheritance line %(tag.name)s->%(parent.name)s added" - else: - fmt = "inheritance line %(tag.name)s->%(parent.name)s removed" - elif table == 'tag_config': - if edit: - fmt = "tag configuration for %(tag.name)s altered" - elif create: - fmt = "new tag: %(tag.name)s" - else: - fmt = "tag deleted: %(tag.name)s" - elif table == 'tag_extra': - if edit: - fmt = "tag option %(key)s for tag %(tag.name)s altered" - elif create: - fmt = "added tag option %(key)s for tag %(tag.name)s" - else: - fmt = "tag option %(key)s removed for %(tag.name)s" - elif table == 'build_target_config': - if edit: - fmt = "build target configuration for %(build_target.name)s updated" - elif create: - fmt = "new build target: %(build_target.name)s" - else: - fmt = "build target deleted: %(build_target.name)s" - elif table == 'external_repo_config': - if edit: - fmt = "external repo configuration for %(external_repo.name)s altered" - elif create: - fmt = "new external repo: %(external_repo.name)s" - else: - fmt = "external repo deleted: %(external_repo.name)s" - elif table == 'tag_external_repos': - if edit: - fmt = "external repo entry for %(external_repo.name)s in tag %(tag.name)s updated" - elif create: - fmt = "external repo entry for %(external_repo.name)s added to tag %(tag.name)s" - else: - fmt = "external repo entry for %(external_repo.name)s removed from tag %(tag.name)s" - elif table == 'group_config': - if edit: - fmt = "group %(group.name)s configuration for tag %(tag.name)s updated" - elif create: - fmt = "group %(group.name)s added to tag %(tag.name)s" - else: - fmt = "group %(group.name)s removed from tag %(tag.name)s" - elif table == 'group_req_listing': - if edit: - fmt = "group dependency %(group.name)s->%(req.name)s updated in tag %(tag.name)s" - elif create: - fmt = "group dependency %(group.name)s->%(req.name)s added in tag %(tag.name)s" - else: - fmt = "group dependency %(group.name)s->%(req.name)s dropped from tag %(tag.name)s" - elif table == 'group_package_listing': - if edit: - fmt = "package entry %(package)s in group %(group.name)s, tag %(tag.name)s updated" - elif create: - fmt = "package %(package)s added to group %(group.name)s in tag %(tag.name)s" - else: - fmt = "package %(package)s removed from group %(group.name)s in tag %(tag.name)s" - else: - if edit: - fmt = "%s entry updated" % table - elif create: - fmt = "%s entry created" % table - else: - fmt = "%s entry revoked" % table - time_str = time.asctime(time.localtime(ts)) - parts = [time_str, fmt % x] - if options.events or options.verbose: - parts.insert(1, "(eid %i)" % event_id) - if who: - parts.append(who % x) - if create and x['active']: - parts.append("[still active]") - print(' '.join(parts)) - hidden_fields = ['active', 'create_event', 'revoke_event', 'creator_id', 'revoker_id', - 'creator_name', 'revoker_name', 'create_ts', 'revoke_ts'] - def get_nkey(key): - if key == 'perm_id': - return 'permission.name' - elif key.endswith('_id'): - return '%s.name' % key[:-3] - else: - return '%s.name' % key - if edit: - keys = list(x.keys()) - keys.sort() - y = other[-1] - for key in keys: - if key in hidden_fields: - continue - if x[key] == y[key]: - continue - if key[0] == '_': - continue - nkey = get_nkey(key) - if nkey in x and nkey in y: - continue - print(" %s: %s -> %s" % (key, x[key], y[key])) - elif create and options.verbose and table != 'tag_listing': - keys = list(x.keys()) - keys.sort() - # the table keys have already been represented in the base format string - also_hidden = list(_table_keys[table]) - also_hidden.extend([get_nkey(k) for k in also_hidden]) - for key in keys: - if key in hidden_fields or key in also_hidden: - continue - nkey = get_nkey(key) - if nkey in x: - continue - if key[0] == '_': - continue - if x.get('blocked') and key != 'blocked': - continue - if key.endswith('.name'): - dkey = key[:-5] - else: - dkey = key - print(" %s: %s" % (dkey, x[key])) - -_table_keys = { - 'user_perms' : ['user_id', 'perm_id'], - 'user_groups' : ['user_id', 'group_id'], - 'cg_users' : ['user_id', 'cg_id'], - 'tag_inheritance' : ['tag_id', 'parent_id'], - 'tag_config' : ['tag_id'], - 'tag_extra' : ['tag_id', 'key'], - 'build_target_config' : ['build_target_id'], - 'external_repo_config' : ['external_repo_id'], - 'tag_external_repos' : ['tag_id', 'external_repo_id'], - 'tag_listing' : ['build_id', 'tag_id'], - 'tag_packages' : ['package_id', 'tag_id'], - 'group_config' : ['group_id', 'tag_id'], - 'group_req_listing' : ['group_id', 'tag_id', 'req_id'], - 'group_package_listing' : ['group_id', 'tag_id', 'package'], - } - -def anon_handle_list_history(options, session, args): - "[info] Display historical data" - usage = _("usage: %prog list-history [options]") - usage += _("\n(Specify the --help global option for a list of other help options)") - parser = OptionParser(usage=usage) - parser.add_option("--debug", action="store_true") - parser.add_option("--build", help=_("Only show data for a specific build")) - parser.add_option("--package", help=_("Only show data for a specific package")) - parser.add_option("--tag", help=_("Only show data for a specific tag")) - parser.add_option("--editor", "--by", metavar="USER", help=_("Only show entries modified by user")) - parser.add_option("--user", help=_("Only show entries affecting a user")) - parser.add_option("--permission", help=_("Only show entries relating to a given permission")) - parser.add_option("--cg", help=_("Only show entries relating to a given permission")) - parser.add_option("--external-repo", "--erepo", help=_("Only show entries relating to a given external repo")) - parser.add_option("--build-target", "--target", help=_("Only show entries relating to a given build target")) - parser.add_option("--group", help=_("Only show entries relating to a given group")) - parser.add_option("--before", metavar="TIMESTAMP", help=_("Only show entries before timestamp")) - parser.add_option("--after", metavar="TIMESTAMP", help=_("Only show entries after timestamp")) - parser.add_option("--before-event", metavar="EVENT_ID", type='int', help=_("Only show entries before event")) - parser.add_option("--after-event", metavar="EVENT_ID", type='int', help=_("Only show entries after event")) - parser.add_option("--watch", action="store_true", help=_("Monitor history data")) - parser.add_option("--active", action='store_true', help=_("Only show entries that are currently active")) - parser.add_option("--revoked", action='store_false', dest='active', - help=_("Only show entries that are currently revoked")) - parser.add_option("--context", action="store_true", help=_("Show related entries")) - parser.add_option("-s", "--show", action="append", help=_("Show data from selected tables")) - parser.add_option("-v", "--verbose", action="store_true", help=_("Show more detail")) - parser.add_option("-e", "--events", action="store_true", help=_("Show event ids")) - parser.add_option("--all", action="store_true", help=_("Allows listing the entire global history")) - global_options = options - (options, args) = parser.parse_args(args) - if len(args) != 0: - parser.error(_("This command takes no arguments")) - assert False # pragma: no cover - kwargs = {} - limited = False - 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) - for opt in ('package', 'tag', 'build', 'editor', 'user', 'permission', - 'cg', 'external_repo', 'build_target', 'group', 'before', - 'after'): - val = getattr(options, opt) - if val: - kwargs[opt] = val - limited = True - if options.before_event: - kwargs['beforeEvent'] = options.before_event - if options.after_event: - kwargs['afterEvent'] = options.after_event - if options.active is not None: - kwargs['active'] = options.active - tables = None - if options.show: - tables = [] - for arg in options.show: - tables.extend(arg.split(',')) - if not limited and not options.all: - parser.error(_("Please specify an option to limit the query")) - - activate_session(session) - - if options.watch: - if not kwargs.get('afterEvent') and not kwargs.get('after'): - kwargs['afterEvent'] = session.getLastEvent()['id'] - - while True: - histdata = session.queryHistory(tables=tables, **kwargs) - timeline = [] - def distinguish_match(x, name): - """determine if create or revoke event matched""" - if options.context: - return True - name = "_" + name - ret = True - for key in x: - if key.startswith(name): - ret = ret and x[key] - return ret - for table in histdata: - hist = histdata[table] - for x in hist: - if x['revoke_event'] is not None: - if distinguish_match(x, 'revoked'): - timeline.append((x['revoke_event'], table, 0, x.copy())) - #pprint.pprint(timeline[-1]) - if distinguish_match(x, 'created'): - timeline.append((x['create_event'], table, 1, x)) - timeline.sort() - #group edits together - new_timeline = [] - last_event = None - edit_index = {} - for entry in timeline: - event_id, table, create, x = entry - if event_id != last_event: - edit_index = {} - last_event = event_id - key = tuple([x[k] for k in _table_keys[table]]) - prev = edit_index.get((table, event_id), {}).get(key) - if prev: - prev[-1].setdefault('.related', []).append(entry) - else: - edit_index.setdefault((table, event_id), {})[key] = entry - new_timeline.append(entry) - for entry in new_timeline: - if options.debug: - print("%r" % list(entry)) - _print_histline(entry, options=options) - if not options.watch: - break - else: - time.sleep(global_options.poll_interval) - # repeat query for later events - if last_event: - kwargs['afterEvent'] = last_event - -def _handleMap(lines, data, prefix=''): - for key, val in data.items(): - if key != '__starstar': - lines.append(' %s%s: %s' % (prefix, key, val)) - -def _handleOpts(lines, opts, prefix=''): - if opts: - lines.append("%sOptions:" % prefix) - _handleMap(lines, opts, prefix) - - -def _parseTaskParams(session, method, task_id): - try: - return _do_parseTaskParams(session, method, task_id) - except Exception: - if logger.isEnabledFor(logging.DEBUG): - tb_str = ''.join(traceback.format_exception(*sys.exc_info())) - logger.debug(tb_str) - return ['Unable to parse task parameters'] - - -def _do_parseTaskParams(session, method, task_id): - """Parse the return of getTaskRequest()""" - params = session.getTaskRequest(task_id) - - lines = [] - - if method == 'buildSRPMFromCVS': - lines.append("CVS URL: %s" % params[0]) - elif method == 'buildSRPMFromSCM': - lines.append("SCM URL: %s" % params[0]) - elif method == 'buildArch': - lines.append("SRPM: %s/work/%s" % (options.topdir, params[0])) - lines.append("Build Tag: %s" % session.getTag(params[1])['name']) - lines.append("Build Arch: %s" % params[2]) - lines.append("SRPM Kept: %r" % params[3]) - if len(params) > 4: - _handleOpts(lines, params[4]) - elif method == 'tagBuild': - build = session.getBuild(params[1]) - lines.append("Destination Tag: %s" % session.getTag(params[0])['name']) - lines.append("Build: %s" % koji.buildLabel(build)) - elif method == 'buildNotification': - build = params[1] - buildTarget = params[2] - lines.append("Recipients: %s" % (", ".join(params[0]))) - lines.append("Build: %s" % koji.buildLabel(build)) - lines.append("Build Target: %s" % buildTarget['name']) - lines.append("Web URL: %s" % params[3]) - elif method == 'build': - lines.append("Source: %s" % params[0]) - lines.append("Build Target: %s" % params[1]) - if len(params) > 2: - _handleOpts(lines, params[2]) - elif method == 'maven': - lines.append("SCM URL: %s" % params[0]) - lines.append("Build Target: %s" % params[1]) - if len(params) > 2: - _handleOpts(lines, params[2]) - elif method == 'buildMaven': - lines.append("SCM URL: %s" % params[0]) - lines.append("Build Tag: %s" % params[1]['name']) - if len(params) > 2: - _handleOpts(lines, params[2]) - elif method == 'wrapperRPM': - lines.append("Spec File URL: %s" % params[0]) - lines.append("Build Tag: %s" % params[1]['name']) - if params[2]: - lines.append("Build: %s" % koji.buildLabel(params[2])) - if params[3]: - lines.append("Task: %s %s" % (params[3]['id'], koji.taskLabel(params[3]))) - if len(params) > 4: - _handleOpts(lines, params[4]) - elif method == 'chainmaven': - lines.append("Builds:") - for package, opts in params[0].items(): - lines.append(" " + package) - _handleMap(lines, opts, prefix=" ") - lines.append("Build Target: %s" % params[1]) - if len(params) > 2: - _handleOpts(lines, params[2]) - elif method == 'winbuild': - lines.append("VM: %s" % params[0]) - lines.append("SCM URL: %s" % params[1]) - lines.append("Build Target: %s" % params[2]) - if len(params) > 3: - _handleOpts(lines, params[3]) - elif method == 'vmExec': - lines.append("VM: %s" % params[0]) - lines.append("Exec Params:") - for info in params[1]: - if isinstance(info, dict): - _handleMap(lines, info, prefix=' ') - else: - lines.append(" %s" % info) - if len(params) > 2: - _handleOpts(lines, params[2]) - elif method in ('createLiveCD', 'createAppliance', 'createLiveMedia'): - argnames = ['Name', 'Version', 'Release', 'Arch', 'Target Info', 'Build Tag', 'Repo', 'Kickstart File'] - for n, v in zip(argnames, params): - lines.append("%s: %s" % (n, v)) - if len(params) > 8: - _handleOpts(lines, params[8]) - elif method in ('appliance', 'livecd', 'livemedia'): - argnames = ['Name', 'Version', 'Arches', 'Target', 'Kickstart'] - for n, v in zip(argnames, params): - lines.append("%s: %s" % (n, v)) - if len(params) > 5: - _handleOpts(lines, params[5]) - elif method == 'newRepo': - tag = session.getTag(params[0]) - lines.append("Tag: %s" % tag['name']) - elif method == 'prepRepo': - lines.append("Tag: %s" % params[0]['name']) - elif method == 'createrepo': - lines.append("Repo ID: %i" % params[0]) - lines.append("Arch: %s" % params[1]) - oldrepo = params[2] - if oldrepo: - lines.append("Old Repo ID: %i" % oldrepo['id']) - lines.append("Old Repo Creation: %s" % koji.formatTimeLong(oldrepo['creation_time'])) - if len(params) > 3: - lines.append("External Repos: %s" % ', '.join([ext['external_repo_name'] for ext in params[3]])) - elif method == 'tagNotification': - destTag = session.getTag(params[2]) - srcTag = None - if params[3]: - srcTag = session.getTag(params[3]) - build = session.getBuild(params[4]) - user = session.getUser(params[5]) - - lines.append("Recipients: %s" % ", ".join(params[0])) - lines.append("Successful?: %s" % (params[1] and 'yes' or 'no')) - lines.append("Tagged Into: %s" % destTag['name']) - if srcTag: - lines.append("Moved From: %s" % srcTag['name']) - lines.append("Build: %s" % koji.buildLabel(build)) - lines.append("Tagged By: %s" % user['name']) - lines.append("Ignore Success?: %s" % (params[6] and 'yes' or 'no')) - if params[7]: - lines.append("Failure Message: %s" % params[7]) - elif method == 'dependantTask': - lines.append("Dependant Tasks: %s" % ", ".join([str(depID) for depID in params[0]])) - lines.append("Subtasks:") - for subtask in params[1]: - lines.append(" Method: %s" % subtask[0]) - lines.append(" Parameters: %s" % ", ".join([str(subparam) for subparam in subtask[1]])) - if len(subtask) > 2 and subtask[2]: - subopts = subtask[2] - _handleOpts(lines, subopts, prefix=' ') - lines.append("") - elif method == 'chainbuild': - lines.append("Build Groups:") - group_num = 0 - for group_list in params[0]: - group_num += 1 - lines.append(" %i: %s" % (group_num, ', '.join(group_list))) - lines.append("Build Target: %s" % params[1]) - if len(params) > 2: - _handleOpts(lines, params[2]) - elif method == 'waitrepo': - lines.append("Build Target: %s" % params[0]) - if params[1]: - lines.append("Newer Than: %s" % params[1]) - if params[2]: - lines.append("NVRs: %s" % ', '.join(params[2])) - - return lines - -def _printTaskInfo(session, task_id, level=0, recurse=True, verbose=True): - """Recursive function to print information about a task - and its children.""" - - BUILDDIR = '/var/lib/mock' - indent = " "*2*level - - info = session.getTaskInfo(task_id) - - if info is None: - raise koji.GenericError("No such task: %d" % task_id) - - if info['host_id']: - host_info = session.getHost(info['host_id']) - else: - host_info = None - buildroot_infos = session.listBuildroots(taskID=task_id) - build_info = session.listBuilds(taskID=task_id) - - files = list_task_output_all_volumes(session, task_id) - logs = [] - output = [] - for filename in files: - if filename.endswith('.log'): - logs += [os.path.join(koji.pathinfo.work(volume=volume), - koji.pathinfo.taskrelpath(task_id), - filename) for volume in files[filename]] - else: - output += [os.path.join(koji.pathinfo.work(volume=volume), - koji.pathinfo.taskrelpath(task_id), - filename) for volume in files[filename]] - - owner = session.getUser(info['owner'])['name'] - - print("%sTask: %d" % (indent, task_id)) - print("%sType: %s" % (indent, info['method'])) - if verbose: - print("%sRequest Parameters:" % indent) - for line in _parseTaskParams(session, info['method'], task_id): - print("%s %s" % (indent, line)) - print("%sOwner: %s" % (indent, owner)) - print("%sState: %s" % (indent, koji.TASK_STATES[info['state']].lower())) - print("%sCreated: %s" % (indent, time.asctime(time.localtime(info['create_ts'])))) - if info.get('start_ts'): - print("%sStarted: %s" % (indent, time.asctime(time.localtime(info['start_ts'])))) - if info.get('completion_ts'): - print("%sFinished: %s" % (indent, time.asctime(time.localtime(info['completion_ts'])))) - if host_info: - print("%sHost: %s" % (indent, host_info['name'])) - if build_info: - print("%sBuild: %s (%d)" % (indent, build_info[0]['nvr'], build_info[0]['build_id'])) - if buildroot_infos: - print("%sBuildroots:" % indent) - for root in buildroot_infos: - print("%s %s/%s-%d-%d/" % (indent, BUILDDIR, root['tag_name'], root['id'], root['repo_id'])) - if logs: - print("%sLog Files:" % indent) - for log_path in logs: - print("%s %s" % (indent, log_path)) - if output: - print("%sOutput:" % indent) - for file_path in output: - print("%s %s" % (indent, file_path)) - - # white space - print('') - - if recurse: - level += 1 - children = session.getTaskChildren(task_id, request=True) - children.sort(cmp=lambda a, b: cmp(a['id'], b['id'])) - for child in children: - _printTaskInfo(session, child['id'], level, verbose=verbose) - -def anon_handle_taskinfo(options, session, args): - """[info] Show information about a task""" - usage = _("usage: %prog taskinfo [options] taskID [taskID...]") - usage += _("\n(Specify the --help global option for a list of other help options)") - parser = OptionParser(usage=usage) - parser.add_option("-r", "--recurse", action="store_true", help=_("Show children of this task as well")) - parser.add_option("-v", "--verbose", action="store_true", help=_("Be verbose")) - (options, args) = parser.parse_args(args) - if len(args) < 1: - parser.error(_("You must specify at least one task ID")) - assert False # pragma: no cover - - activate_session(session) - - for arg in args: - task_id = int(arg) - _printTaskInfo(session, task_id, 0, options.recurse, options.verbose) - -def anon_handle_taginfo(options, session, args): - "[info] Print basic information about a tag" - usage = _("usage: %prog taginfo [options] [ ...]") - usage += _("\n(Specify the --help global option for a list of other help options)") - parser = OptionParser(usage=usage) - parser.add_option("--event", type='int', metavar="EVENT#", help=_("query at event")) - parser.add_option("--ts", type='int', metavar="TIMESTAMP", help=_("query at timestamp")) - parser.add_option("--repo", type='int', metavar="REPO#", help=_("query at event for a repo")) - (options, args) = parser.parse_args(args) - if len(args) < 1: - parser.error(_("Please specify a tag")) - assert False # pragma: no cover - activate_session(session) - event = koji.util.eventFromOpts(session, options) - event_opts = {} - if event: - event['timestr'] = time.asctime(time.localtime(event['ts'])) - print("Querying at event %(id)i (%(timestr)s)" % event) - event_opts['event'] = event['id'] - perms = dict([(p['id'], p['name']) for p in session.getAllPerms()]) - - tags = [] - for tag in args: - info = session.getTag(tag, **event_opts) - if info is None: - print("No such tag: %s" % tag) - sys.exit(1) - tags.append(info) - - for n, info in enumerate(tags): - if n > 0: - print('') - print("Tag: %(name)s [%(id)d]" %info) - print("Arches: %(arches)s" %info) - group_list = [x['name'] for x in session.getTagGroups(info['id'], **event_opts)] - group_list.sort() - print("Groups: " + ', '.join(group_list)) - if info.get('locked'): - print('LOCKED') - if info.get('perm_id') is not None: - perm_id = info['perm_id'] - print("Required permission: %r" % perms.get(perm_id, perm_id)) - if session.mavenEnabled(): - print("Maven support?: %s" % (info['maven_support'] and 'yes' or 'no')) - print("Include all Maven archives?: %s" % (info['maven_include_all'] and 'yes' or 'no')) - if 'extra' in info: - print("Tag options:") - keys = list(info['extra'].keys()) - keys.sort() - for key in keys: - print(" %s : %s" % (key, pprint.pformat(info['extra'][key]))) - dest_targets = session.getBuildTargets(destTagID=info['id'], **event_opts) - build_targets = session.getBuildTargets(buildTagID=info['id'], **event_opts) - repos = {} - if not event: - for target in dest_targets + build_targets: - if target['build_tag'] not in repos: - repo = session.getRepo(target['build_tag']) - if repo is None: - repos[target['build_tag']] = "no active repo" - else: - repos[target['build_tag']] = "repo#%(id)i: %(creation_time)s" % repo - if dest_targets: - print("Targets that build into this tag:") - for target in dest_targets: - if event: - print(" %s (%s)" % (target['name'], target['build_tag_name'])) - else: - print(" %s (%s, %s)" % (target['name'], target['build_tag_name'], repos[target['build_tag']])) - if build_targets: - print("This tag is a buildroot for one or more targets") - if not event: - print("Current repo: %s" % repos[info['id']]) - print("Targets that build from this tag:") - for target in build_targets: - print(" %s" % target['name']) - external_repos = session.getTagExternalRepos(tag_info=info['id'], **event_opts) - if external_repos: - print("External repos:") - for rinfo in external_repos: - print(" %(priority)3i %(external_repo_name)s (%(url)s)" % rinfo) - print("Inheritance:") - for parent in session.getInheritanceData(tag, **event_opts): - flags = '' - for code,expr in ( - ('M',parent['maxdepth'] is not None), - ('F',parent['pkg_filter']), - ('I',parent['intransitive']), - ('N',parent['noconfig']),): - if expr: - flags += code - else: - flags += '.' - parent['flags'] = flags - print(" %(priority)-4d %(flags)s %(name)s [%(parent_id)s]" % parent) - if parent['maxdepth'] is not None: - print(" maxdepth: %(maxdepth)s" % parent) - if parent['pkg_filter']: - print(" package filter: %(pkg_filter)s" % parent) - - -def handle_add_tag(options, session, args): - "[admin] Add a new tag to the database" - usage = _("usage: %prog add-tag [options] name") - usage += _("\n(Specify the --help global option for a list of other help options)") - parser = OptionParser(usage=usage) - parser.add_option("--parent", help=_("Specify parent")) - parser.add_option("--arches", help=_("Specify arches")) - parser.add_option("--maven-support", action="store_true", help=_("Enable creation of Maven repos for this tag")) - parser.add_option("--include-all", action="store_true", help=_("Include all packages in this tag when generating Maven repos")) - parser.add_option("-x", "--extra", action="append", default=[], metavar="key=value", - help=_("Set tag extra option")) - (options, args) = parser.parse_args(args) - if len(args) != 1: - parser.error(_("Please specify a name for the tag")) - assert False # pragma: no cover - activate_session(session) - if not session.hasPerm('admin'): - print("This action requires admin privileges") - return - opts = {} - if options.parent: - opts['parent'] = options.parent - if options.arches: - opts['arches'] = parse_arches(options.arches) - if options.maven_support: - opts['maven_support'] = True - if options.include_all: - opts['maven_include_all'] = True - if options.extra: - extra = {} - for xopt in options.extra: - key, value = xopt.split('=', 1) - value = arg_filter(value) - extra[key] = value - opts['extra'] = extra - session.createTag(args[0],**opts) - -def handle_edit_tag(options, session, args): - "[admin] Alter tag information" - usage = _("usage: %prog edit-tag [options] name") - usage += _("\n(Specify the --help global option for a list of other help options)") - parser = OptionParser(usage=usage) - parser.add_option("--arches", help=_("Specify arches")) - parser.add_option("--perm", help=_("Specify permission requirement")) - parser.add_option("--no-perm", action="store_true", help=_("Remove permission requirement")) - parser.add_option("--lock", action="store_true", help=_("Lock the tag")) - parser.add_option("--unlock", action="store_true", help=_("Unlock the tag")) - parser.add_option("--rename", help=_("Rename the tag")) - parser.add_option("--maven-support", action="store_true", help=_("Enable creation of Maven repos for this tag")) - parser.add_option("--no-maven-support", action="store_true", help=_("Disable creation of Maven repos for this tag")) - parser.add_option("--include-all", action="store_true", help=_("Include all packages in this tag when generating Maven repos")) - parser.add_option("--no-include-all", action="store_true", help=_("Do not include all packages in this tag when generating Maven repos")) - parser.add_option("-x", "--extra", action="append", default=[], metavar="key=value", - help=_("Set tag extra option")) - parser.add_option("-r", "--remove-extra", action="append", default=[], metavar="key", - help=_("Remove tag extra option")) - (options, args) = parser.parse_args(args) - if len(args) != 1: - parser.error(_("Please specify a name for the tag")) - assert False # pragma: no cover - activate_session(session) - tag = args[0] - opts = {} - if options.arches: - opts['arches'] = parse_arches(options.arches) - if options.no_perm: - opts['perm_id'] = None - elif options.perm: - opts['perm'] = options.perm - if options.unlock: - opts['locked'] = False - if options.lock: - opts['locked'] = True - if options.rename: - opts['name'] = options.rename - if options.maven_support: - opts['maven_support'] = True - if options.no_maven_support: - opts['maven_support'] = False - if options.include_all: - opts['maven_include_all'] = True - if options.no_include_all: - opts['maven_include_all'] = False - if options.extra: - extra = {} - for xopt in options.extra: - key, value = xopt.split('=', 1) - value = arg_filter(value) - extra[key] = value - opts['extra'] = extra - if options.remove_extra: - opts['remove_extra'] = options.remove_extra - #XXX change callname - session.editTag2(tag, **opts) - -def handle_lock_tag(options, session, args): - "[admin] Lock a tag" - usage = _("usage: %prog lock-tag [options] [ ...] ") - usage += _("\n(Specify the --help global option for a list of other help options)") - parser = OptionParser(usage=usage) - parser.add_option("--perm", help=_("Specify permission requirement")) - parser.add_option("--glob", action="store_true", help=_("Treat args as glob patterns")) - parser.add_option("--master", action="store_true", help=_("Lock the master lock")) - parser.add_option("-n", "--test", action="store_true", help=_("Test mode")) - (options, args) = parser.parse_args(args) - if len(args) < 1: - parser.error(_("Please specify a tag")) - assert False # pragma: no cover - activate_session(session) - pdata = session.getAllPerms() - perm_ids = dict([(p['name'], p['id']) for p in pdata]) - perm = options.perm - if perm is None: - perm = 'admin' - perm_id = perm_ids[perm] - if options.glob: - selected = [] - for tag in session.listTags(): - for pattern in args: - if fnmatch.fnmatch(tag['name'], pattern): - selected.append(tag) - break - if not selected: - print(_("No tags matched")) - else: - selected = [session.getTag(name) for name in args] - for tag in selected: - if options.master: - #set the master lock - if tag['locked']: - print(_("Tag %s: master lock already set") % tag['name']) - continue - elif options.test: - print(_("Would have set master lock for: %s") % tag['name']) - continue - session.editTag2(tag['id'], locked=True) - else: - if tag['perm_id'] == perm_id: - print(_("Tag %s: %s permission already required") % (tag['name'], perm)) - continue - elif options.test: - print(_("Would have set permission requirement %s for tag %s") % (perm, tag['name'])) - continue - session.editTag2(tag['id'], perm=perm_id) - -def handle_unlock_tag(options, session, args): - "[admin] Unlock a tag" - usage = _("usage: %prog unlock-tag [options] [ ...]") - usage += _("\n(Specify the --help global option for a list of other help options)") - parser = OptionParser(usage=usage) - parser.add_option("--glob", action="store_true", help=_("Treat args as glob patterns")) - parser.add_option("-n", "--test", action="store_true", help=_("Test mode")) - (options, args) = parser.parse_args(args) - if len(args) < 1: - parser.error(_("Please specify a tag")) - assert False # pragma: no cover - activate_session(session) - if options.glob: - selected = [] - for tag in session.listTags(): - for pattern in args: - if fnmatch.fnmatch(tag['name'], pattern): - selected.append(tag) - break - if not selected: - print(_("No tags matched")) - else: - selected = [] - for name in args: - tag = session.getTag(name) - if tag is None: - parser.error(_("No such tag: %s") % name) - assert False # pragma: no cover - selected.append(tag) - selected = [session.getTag(name) for name in args] - for tag in selected: - opts = {} - if tag['locked']: - opts['locked'] = False - if tag['perm_id']: - opts['perm'] = None - if not opts: - print("Tag %(name)s: not locked" % tag) - continue - if options.test: - print("Tag %s: skipping changes: %r" % (tag['name'], opts)) - else: - session.editTag2(tag['id'], locked=False, perm_id=None) - -def handle_add_tag_inheritance(options, session, args): - """[admin] Add to a tag's inheritance""" - usage = _("usage: %prog add-tag-inheritance [options] tag parent-tag") - usage += _("\n(Specify the --help global option for a list of other help options)") - parser = OptionParser(usage=usage) - parser.add_option("--priority", help=_("Specify priority")) - parser.add_option("--maxdepth", help=_("Specify max depth")) - parser.add_option("--intransitive", action="store_true", help=_("Set intransitive")) - parser.add_option("--noconfig", action="store_true", help=_("Set to packages only")) - parser.add_option("--pkg-filter", help=_("Specify the package filter")) - parser.add_option("--force", help=_("Force adding a parent to a tag that already has that parent tag")) - (options, args) = parser.parse_args(args) - - if len(args) != 2: - parser.error(_("This command takes exctly two argument: a tag name or ID and that tag's new parent name or ID")) - assert False # pragma: no cover - - activate_session(session) - - tag = session.getTag(args[0]) - if not tag: - parser.error(_("Invalid tag: %s" % args[0])) - - parent = session.getTag(args[1]) - if not parent: - parser.error(_("Invalid tag: %s" % args[1])) - - inheritanceData = session.getInheritanceData(tag['id']) - priority = options.priority and int(options.priority) or 0 - sameParents = [datum for datum in inheritanceData if datum['parent_id'] == parent['id']] - samePriority = [datum for datum in inheritanceData if datum['priority'] == priority] - - if sameParents and not options.force: - print(_("Error: You are attempting to add %s as %s's parent even though it already is %s's parent.") - % (parent['name'], tag['name'], tag['name'])) - print(_("Please use --force if this is what you really want to do.")) - return - if samePriority: - print(_("Error: There is already an active inheritance with that priority on %s, please specify a different priority with --priority." % tag['name'])) - return - - new_data = {} - new_data['parent_id'] = parent['id'] - new_data['priority'] = options.priority or 0 - if options.maxdepth and options.maxdepth.isdigit(): - new_data['maxdepth'] = int(options.maxdepth) - else: - new_data['maxdepth'] = None - new_data['intransitive'] = options.intransitive or False - new_data['noconfig'] = options.noconfig or False - new_data['pkg_filter'] = options.pkg_filter or '' - - inheritanceData.append(new_data) - session.setInheritanceData(tag['id'], inheritanceData) - - -def handle_edit_tag_inheritance(options, session, args): - """[admin] Edit tag inheritance""" - usage = _("usage: %prog edit-tag-inheritance [options] tag ") - usage += _("\n(Specify the --help global option for a list of other help options)") - parser = OptionParser(usage=usage) - parser.add_option("--priority", help=_("Specify a new priority")) - parser.add_option("--maxdepth", help=_("Specify max depth")) - parser.add_option("--intransitive", action="store_true", help=_("Set intransitive")) - parser.add_option("--noconfig", action="store_true", help=_("Set to packages only")) - parser.add_option("--pkg-filter", help=_("Specify the package filter")) - (options, args) = parser.parse_args(args) - - if len(args) < 1: - parser.error(_("This command takes at lease one argument: a tag name or ID")) - assert False # pragma: no cover - - if len(args) > 3: - parser.error(_("This command takes at most three argument: a tag name or ID, a parent tag name or ID, and a priority")) - assert False # pragma: no cover - - activate_session(session) - - tag = session.getTag(args[0]) - if not tag: - parser.error(_("Invalid tag: %s" % args[0])) - - parent = None - priority = None - if len(args) > 1: - parent = session.getTag(args[1]) - if not parent: - parser.error(_("Invalid tag: %s" % args[1])) - if len(args) > 2: - priority = args[2] - - data = session.getInheritanceData(tag['id']) - if parent and data: - data = [datum for datum in data if datum['parent_id'] == parent['id']] - if priority and data: - data = [datum for datum in data if datum['priority'] == priority] - - if len(data) == 0: - print(_("No inheritance link found to remove. Please check your arguments")) - return 1 - elif len(data) > 1: - print(_("Multiple matches for tag.")) - if not parent: - print(_("Please specify a parent on the command line.")) - return 1 - if not priority: - print(_("Please specify a priority on the command line.")) - return 1 - print(_("Error: Key constraints may be broken. Exiting.")) - return 1 - - # len(data) == 1 - data = data[0] - - inheritanceData = session.getInheritanceData(tag['id']) - samePriority = [datum for datum in inheritanceData if datum['priority'] == options.priority] - if samePriority: - print(_("Error: There is already an active inheritance with that priority on %s, please specify a different priority with --priority.") % tag['name']) - return 1 - - new_data = data.copy() - if options.priority is not None and options.priority.isdigit(): - new_data['priority'] = int(options.priority) - if options.maxdepth is not None: - if options.maxdepth.isdigit(): - new_data['maxdepth'] = int(options.maxdepth) - elif options.maxdepth.lower() == "none": - new_data['maxdepth'] = None - else: - print(_("Invalid maxdepth: %s") % options.maxdepth) - return 1 - if options.intransitive: - new_data['intransitive'] = options.intransitive - if options.noconfig: - new_data['noconfig'] = options.noconfig - if options.pkg_filter: - new_data['pkg_filter'] = options.pkg_filter - - # find the data we want to edit and replace it - index = inheritanceData.index(data) - inheritanceData[index] = new_data - session.setInheritanceData(tag['id'], inheritanceData) - -def handle_remove_tag_inheritance(options, session, args): - """[admin] Remove a tag inheritance link""" - usage = _("usage: %prog remove-tag-inheritance tag ") - 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) < 1: - parser.error(_("This command takes at lease one argument: a tag name or ID")) - assert False # pragma: no cover - - if len(args) > 3: - parser.error(_("This command takes at most three argument: a tag name or ID, a parent tag name or ID, and a priority")) - assert False # pragma: no cover - - activate_session(session) - - tag = session.getTag(args[0]) - if not tag: - parser.error(_("Invalid tag: %s" % args[0])) - - parent = None - priority = None - if len(args) > 1: - parent = session.getTag(args[1]) - if not parent: - parser.error(_("Invalid tag: %s" % args[1])) - if len(args) > 2: - priority = args[2] - - data = session.getInheritanceData(tag['id']) - if parent and data: - data = [datum for datum in data if datum['parent_id'] == parent['id']] - if priority and data: - data = [datum for datum in data if datum['priority'] == priority] - - if len(data) == 0: - print(_("No inheritance link found to remove. Please check your arguments")) - return - elif len(data) > 1: - print(_("Multiple matches for tag.")) - if not parent: - print(_("Please specify a parent on the command line.")) - return - if not priority: - print(_("Please specify a priority on the command line.")) - return - print(_("Error: Key constrainsts may be broken. Exiting.")) - return - - # len(data) == 1 - data = data[0] - - inheritanceData = session.getInheritanceData(tag['id']) - - new_data = data.copy() - new_data['delete link'] = True - - # find the data we want to edit and replace it - index = inheritanceData.index(data) - inheritanceData[index] = new_data - session.setInheritanceData(tag['id'], inheritanceData) - -def anon_handle_show_groups(options, session, args): - "[info] Show groups data for a tag" - usage = _("usage: %prog show-groups [options] ") - usage += _("\n(Specify the --help global option for a list of other help options)") - parser = OptionParser(usage=usage) - parser.add_option("--comps", action="store_true", help=_("Print in comps format")) - parser.add_option("-x", "--expand", action="store_true", default=False, - help=_("Expand groups in comps format")) - parser.add_option("--spec", action="store_true", help=_("Print build spec")) - (options, args) = parser.parse_args(args) - if len(args) != 1: - parser.error(_("Incorrect number of arguments")) - assert False # pragma: no cover - activate_session(session) - tag = args[0] - groups = session.getTagGroups(tag) - if options.comps: - print(koji.generate_comps(groups, expand_groups=options.expand)) - elif options.spec: - print(koji.make_groups_spec(groups,name='buildgroups',buildgroup='build')) - else: - pprint.pprint(groups) - -def anon_handle_list_external_repos(options, session, args): - "[info] List external repos" - usage = _("usage: %prog list-external-repos [options]") - usage += _("\n(Specify the --help global option for a list of other help options)") - parser = OptionParser(usage=usage) - parser.add_option("--url", help=_("Select by url")) - parser.add_option("--name", help=_("Select by name")) - parser.add_option("--id", type="int", help=_("Select by id")) - parser.add_option("--tag", help=_("Select by tag")) - parser.add_option("--used", action='store_true', help=_("List which tags use the repo(s)")) - parser.add_option("--inherit", action='store_true', help=_("Follow tag inheritance when selecting by tag")) - parser.add_option("--event", type='int', metavar="EVENT#", help=_("Query at event")) - parser.add_option("--ts", type='int', metavar="TIMESTAMP", help=_("Query at timestamp")) - parser.add_option("--repo", type='int', metavar="REPO#", - help=_("Query at event corresponding to (nonexternal) repo")) - parser.add_option("--quiet", action="store_true", default=options.quiet, - help=_("Do not display the column headers")) - (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) - opts = {} - event = koji.util.eventFromOpts(session, options) - if event: - opts['event'] = event['id'] - event['timestr'] = time.asctime(time.localtime(event['ts'])) - print("Querying at event %(id)i (%(timestr)s)" % event) - if options.tag: - format = "tag" - opts['tag_info'] = options.tag - opts['repo_info'] = options.id or options.name or None - if opts['repo_info']: - if options.inherit: - parser.error(_("Can't select by repo when using --inherit")) - assert False # pragma: no cover - if options.inherit: - del opts['repo_info'] - data = session.getExternalRepoList(**opts) - format = "multitag" - else: - data = session.getTagExternalRepos(**opts) - elif options.used: - format = "multitag" - opts['repo_info'] = options.id or options.name or None - data = session.getTagExternalRepos(**opts) - else: - format = "basic" - opts['info'] = options.id or options.name or None - opts['url'] = options.url or None - data = session.listExternalRepos (**opts) - - # There are three different output formats - # 1) Listing just repo data (name, url) - # 2) Listing repo data for a tag (priority, name, url) - # 3) Listing repo data for multiple tags (tag, priority, name, url) - if format == "basic": - format = "%(name)-25s %(url)s" - header1 = "%-25s %s" % ("External repo name", "URL") - header2 = "%s %s" % ("-"*25, "-"*40) - elif format == "tag": - format = "%(priority)-3i %(external_repo_name)-25s %(url)s" - header1 = "%-3s %-25s %s" % ("Pri", "External repo name", "URL") - header2 = "%s %s %s" % ("-"*3, "-"*25, "-"*40) - elif format == "multitag": - format = "%(tag_name)-20s %(priority)-3i %(external_repo_name)s" - header1 = "%-20s %-3s %s" % ("Tag", "Pri", "External repo name") - header2 = "%s %s %s" % ("-"*20, "-"*3, "-"*25) - if not options.quiet: - print(header1) - print(header2) - for rinfo in data: - print(format % rinfo) - -def _pick_external_repo_priority(session, tag): - """pick priority after current ones, leaving space for later insertions""" - repolist = session.getTagExternalRepos(tag_info=tag) - #ordered by priority - if not repolist: - priority = 5 - else: - priority = (repolist[-1]['priority'] + 7) // 5 * 5 - #at least 3 higher than current max and a multiple of 5 - return priority - -def _parse_tagpri(tagpri): - parts = tagpri.rsplit('::', 1) - tag = parts[0] - if len(parts) == 1: - return tag, None - elif parts[1] in ('auto', '-1'): - return tag, None - else: - try: - pri = int(parts[1]) - except ValueError: - raise koji.GenericError("Invalid priority: %s" % parts[1]) - return tag, pri - -def handle_add_external_repo(options, session, args): - "[admin] Create an external repo and/or add one to a tag" - usage = _("usage: %prog add-external-repo [options] name [url]") - usage += _("\n(Specify the --help global option for a list of other help options)") - parser = OptionParser(usage=usage) - parser.add_option("-t", "--tag", action="append", metavar="TAG", - help=_("Also add repo to tag. Use tag::N to set priority")) - parser.add_option("-p", "--priority", type='int', - help=_("Set priority (when adding to tag)")) - (options, args) = parser.parse_args(args) - activate_session(session) - if len(args) == 1: - name = args[0] - rinfo = session.getExternalRepo(name, strict=True) - if not options.tag: - parser.error(_("A url is required to create an external repo entry")) - elif len(args) == 2: - name, url = args - rinfo = session.createExternalRepo(name, url) - print("Created external repo %(id)i" % rinfo) - else: - parser.error(_("Incorrect number of arguments")) - assert False # pragma: no cover - if options.tag: - for tagpri in options.tag: - tag, priority = _parse_tagpri(tagpri) - if priority is None: - if options.priority is not None: - priority = options.priority - else: - priority = _pick_external_repo_priority(session, tag) - session.addExternalRepoToTag(tag, rinfo['name'], priority) - print("Added external repo %s to tag %s (priority %i)" \ - % (rinfo['name'], tag, priority)) - -def handle_edit_external_repo(options, session, args): - "[admin] Edit data for an external repo" - usage = _("usage: %prog edit-external-repo name") - usage += _("\n(Specify the --help global option for a list of other help options)") - parser = OptionParser(usage=usage) - parser.add_option("--url", help=_("Change the url")) - parser.add_option("--name", help=_("Change the name")) - (options, args) = parser.parse_args(args) - if len(args) != 1: - parser.error(_("Incorrect number of arguments")) - parser.error(_("This command takes no arguments")) - assert False # pragma: no cover - opts = {} - if options.url: - opts['url'] = options.url - if options.name: - opts['name'] = options.name - if not opts: - parser.error(_("No changes specified")) - activate_session(session) - session.editExternalRepo(args[0], **opts) - -def handle_remove_external_repo(options, session, args): - "[admin] Remove an external repo from a tag or tags, or remove entirely" - usage = _("usage: %prog remove-external-repo repo [tag ...]") - usage += _("\n(Specify the --help global option for a list of other help options)") - parser = OptionParser(usage=usage) - parser.add_option("--alltags", action="store_true", help=_("Remove from all tags")) - parser.add_option("--force", action='store_true', help=_("Force action")) - (options, args) = parser.parse_args(args) - if len(args) < 1: - parser.error(_("Incorrect number of arguments")) - assert False # pragma: no cover - activate_session(session) - repo = args[0] - tags = args[1:] - delete = not bool(tags) - data = session.getTagExternalRepos(repo_info=repo) - current_tags = [d['tag_name'] for d in data] - if options.alltags: - delete = False - if tags: - parser.error(_("Do not specify tags when using --alltags")) - assert False # pragma: no cover - if not current_tags: - print(_("External repo %s not associated with any tags") % repo) - return 0 - tags = current_tags - if delete: - #removing entirely - if current_tags and not options.force: - print(_("Error: external repo %s used by tag(s): %s") % (repo, ', '.join(current_tags))) - print(_("Use --force to remove anyway")) - return 1 - session.deleteExternalRepo(args[0]) - else: - for tag in tags: - if not tag in current_tags: - print(_("External repo %s not associated with tag %s") % (repo, tag)) - continue - session.removeExternalRepoFromTag(tag, repo) - -# This handler is for spinning livecd images -# -def handle_spin_livecd(options, session, args): - """[build] Create a live CD image given a kickstart file""" - - # Usage & option parsing. - usage = _("usage: %prog spin-livecd [options] " + - " ") - usage += _("\n(Specify the --help global option for a list of other " + - "help options)") - parser = OptionParser(usage=usage) - parser.add_option("--wait", action="store_true", - help=_("Wait on the livecd creation, even if running in the background")) - parser.add_option("--nowait", action="store_false", dest="wait", - help=_("Don't wait on livecd creation")) - 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 livecd creation task at a lower priority")) - parser.add_option("--ksurl", metavar="SCMURL", - help=_("The URL to the SCM containing the kickstart file")) - parser.add_option("--ksversion", metavar="VERSION", - help=_("The syntax version used in the kickstart file")) - parser.add_option("--scratch", action="store_true", - help=_("Create a scratch LiveCD image")) - parser.add_option("--repo", action="append", - help=_("Specify a repo that will override the repo used to install " + - "RPMs in the LiveCD. May be used multiple times. The " + - "build tag repo associated with the target is the default.")) - parser.add_option("--release", help=_("Forcibly set the release field")) - parser.add_option("--specfile", metavar="URL", - help=_("SCM URL to spec file fragment to use to generate wrapper RPMs")) - parser.add_option("--skip-tag", action="store_true", - help=_("Do not attempt to tag package")) - (task_options, args) = parser.parse_args(args) - - # Make sure the target and kickstart is specified. - print('spin-livecd is deprecated and will be replaced with spin-livemedia') - if len(args) != 5: - parser.error(_("Five arguments are required: a name, a version, an" + - " architecture, a build target, and a relative path to" + - " a kickstart file.")) - assert False # pragma: no cover - _build_image(options, task_options, session, args, 'livecd') - - -# This handler is for spinning livemedia images -def handle_spin_livemedia(options, session, args): - """[admin] Create a livemedia image given a kickstart file""" - - # Usage & option parsing. - usage = _("usage: %prog spin-livemedia [options] " + - " ") - usage += _("\n(Specify the --help global option for a list of other " + - "help options)") - parser = OptionParser(usage=usage) - parser.add_option("--wait", action="store_true", - help=_("Wait on the livemedia creation, even if running in the background")) - parser.add_option("--nowait", action="store_false", dest="wait", - help=_("Don't wait on livemedia creation")) - 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 livemedia creation task at a lower priority")) - parser.add_option("--ksurl", metavar="SCMURL", - help=_("The URL to the SCM containing the kickstart file")) - parser.add_option("--install-tree-url", metavar="URL", - help=_("Provide the URL for the install tree")) - parser.add_option("--ksversion", metavar="VERSION", - help=_("The syntax version used in the kickstart file")) - parser.add_option("--scratch", action="store_true", - help=_("Create a scratch LiveMedia image")) - parser.add_option("--repo", action="append", - help=_("Specify a repo that will override the repo used to install " + - "RPMs in the LiveMedia. May be used multiple times. The " + - "build tag repo associated with the target is the default.")) - parser.add_option("--release", help=_("Forcibly set the release field")) - parser.add_option("--title", help=_("Set the image title (defaults to )")) - parser.add_option("--specfile", metavar="URL", - help=_("SCM URL to 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("--can-fail", action="store", dest="optional_arches", - metavar="ARCH1,ARCH2,...", default="", - help=_("List of archs which are not blocking for build (separated by commas.")) - (task_options, args) = parser.parse_args(args) - - # Make sure the target and kickstart is specified. - if len(args) != 5: - parser.error(_("Five arguments are required: a name, a version, a" + - " build target, an architecture, and a relative path to" + - " a kickstart file.")) - assert False # pragma: no cover - _build_image(options, task_options, session, args, 'livemedia') - -# This handler is for spinning appliance images -# -def handle_spin_appliance(options, session, args): - """[build] Create an appliance given a kickstart file""" - - # Usage & option parsing - usage = _("usage: %prog spin-appliance [options] " + - " ") - usage += _("\n(Specify the --help global option for a list of other " + - "help options)") - parser = OptionParser(usage=usage) - parser.add_option("--wait", action="store_true", - help=_("Wait on the appliance creation, even if running in the background")) - parser.add_option("--nowait", action="store_false", dest="wait", - help=_("Don't wait on appliance creation")) - 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 appliance creation task at a lower priority")) - parser.add_option("--ksurl", metavar="SCMURL", - help=_("The URL to the SCM containing the kickstart file")) - parser.add_option("--ksversion", metavar="VERSION", - help=_("The syntax version used in the kickstart file")) - parser.add_option("--scratch", action="store_true", - help=_("Create a scratch appliance")) - parser.add_option("--repo", action="append", - help=_("Specify a repo that will override the repo used to install " + - "RPMs in the appliance. May be used multiple times. The " + - "build tag repo associated with the target is the default.")) - parser.add_option("--release", help=_("Forcibly set the release field")) - parser.add_option("--specfile", metavar="URL", - help=_("SCM URL to 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("--vmem", metavar="VMEM", default=None, - help=_("Set the amount of virtual memory in the appliance in MB, " + - "default is 512")) - parser.add_option("--vcpu", metavar="VCPU", default=None, - help=_("Set the number of virtual cpus in the appliance, " + - "default is 1")) - parser.add_option("--format", metavar="DISK_FORMAT", default='raw', - help=_("Disk format, default is raw. Other options are qcow, " + - "qcow2, and vmx.")) - - (task_options, args) = parser.parse_args(args) - - # Make sure the target and kickstart is specified. - print('spin-appliance is deprecated and will be replaced with image-build') - if len(args) != 5: - parser.error(_("Five arguments are required: a name, a version, " + - "an architecture, a build target, and a relative path" + - " to a kickstart file.")) - assert False # pragma: no cover - _build_image(options, task_options, session, args, 'appliance') - -def handle_image_build_indirection(options, session, args): - """[build] Create a disk image using other disk images via the Indirection plugin""" - usage = _("usage: %prog image-build-indirection [base_image] " + - "[utility_image] [indirection_build_template]") - usage += _("\n %prog image-build --config FILE") - usage += _("\n\n(Specify the --help global option for a list of other " + - "help options)") - parser = OptionParser(usage=usage) - parser.add_option("--config", - help=_("Use a configuration file to define image-build options " + - "instead of command line options (they will be ignored).")) - parser.add_option("--background", action="store_true", - help=_("Run the image creation task at a lower priority")) - parser.add_option("--name", - help=_("Name of the output image")) - parser.add_option("--version", - help=_("Version of the output image")) - parser.add_option("--release", - help=_("Release of the output image")) - parser.add_option("--arch", - help=_("Architecture of the output image and input images")) - parser.add_option("--target", - help=_("Build target to use for the indirection build")) - parser.add_option("--skip-tag", action="store_true", - help=_("Do not tag the resulting build")) - parser.add_option("--base-image-task", - help=_("ID of the createImage task of the base image to be used")) - parser.add_option("--base-image-build", - help=_("NVR or build ID of the base image to be used")) - parser.add_option("--utility-image-task", - help=_("ID of the createImage task of the utility image to be used")) - parser.add_option("--utility-image-build", - help=_("NVR or build ID of the utility image to be used")) - parser.add_option("--indirection-template", - help=_("Name of the local file, or SCM file containing the template used to drive the indirection plugin")) - parser.add_option("--indirection-template-url", - help=_("SCM URL containing the template used to drive the indirection plugin")) - parser.add_option("--results-loc", - help=_("Relative path inside the working space image where the results should be extracted from")) - parser.add_option("--scratch", action="store_true", - help=_("Create a scratch image")) - parser.add_option("--wait", action="store_true", - help=_("Wait on the image creation, even if running in the background")) - parser.add_option("--noprogress", action="store_true", - help=_("Do not display progress of the upload")) - - - (task_options, args) = parser.parse_args(args) - _build_image_indirection(options, task_options, session, args) - - -def _build_image_indirection(options, task_opts, session, args): - """ - A private helper function for builds using the indirection plugin of ImageFactory - """ - - # Do some sanity checks before even attempting to create the session - if not (bool(task_opts.utility_image_task) != - bool(task_opts.utility_image_build)): - raise koji.GenericError(_("You must specify either a utility-image task or build ID/NVR")) - - if not (bool(task_opts.base_image_task) != - bool(task_opts.base_image_build)): - raise koji.GenericError(_("You must specify either a base-image task or build ID/NVR")) - - required_opts = [ 'name', 'version', 'arch', 'target', 'indirection_template', 'results_loc' ] - optional_opts = [ 'indirection_template_url', 'scratch', 'utility_image_task', 'utility_image_build', - 'base_image_task', 'base_image_build', 'release', 'skip_tag' ] - - missing = [ ] - for opt in required_opts: - if not getattr(task_opts, opt, None): - missing.append(opt) - - if len(missing) > 0: - print("Missing the following required options: %s" % ' '.join(['--%s' % o.replace('_','-') for o in missing])) - raise koji.GenericError(_("Missing required options specified above")) - - activate_session(session) - - # Set the task's priority. Users can only lower it with --background. - priority = None - if task_opts.background: - # relative to koji.PRIO_DEFAULT; higher means a "lower" priority. - priority = 5 - if _running_in_bg() or task_opts.noprogress: - callback = None - else: - callback = _progress_callback - - # We do some early sanity checking of the given target. - # Kojid gets these values again later on, but we check now as a convenience - # for the user. - - tmp_target = session.getBuildTarget(task_opts.target) - if not tmp_target: - raise koji.GenericError(_("Unknown build target: %s" % tmp_target)) - dest_tag = session.getTag(tmp_target['dest_tag']) - if not dest_tag: - raise koji.GenericError(_("Unknown destination tag: %s" % - tmp_target['dest_tag_name'])) - - # Set the architecture - task_opts.arch = koji.canonArch(task_opts.arch) - - - # Upload the indirection template file to the staging area. - # If it's a URL, it's kojid's job to go get it when it does the checkout. - if not task_opts.indirection_template_url: - if not task_opts.scratch: - # only scratch builds can omit indirection_template_url - raise koji.GenericError(_("Non-scratch builds must provide a URL for the indirection template")) - templatefile = task_opts.indirection_template - serverdir = _unique_path('cli-image-indirection') - session.uploadWrapper(templatefile, serverdir, callback=callback) - task_opts.indirection_template = os.path.join('work', serverdir, - os.path.basename(templatefile)) - print('') - - hub_opts = { } - # Just pass everything in as opts. No posiitonal arguments at all. Why not? - for opt in required_opts + optional_opts: - val = getattr(task_opts, opt, None) - # We pass these through even if they are None - # The builder code can then check if they are set without using getattr - hub_opts[opt] = val - - # finally, create the task. - task_id = session.buildImageIndirection(opts=hub_opts, - priority=priority) - - if not options.quiet: - print("Created task: %d" % task_id) - print("Task info: %s/taskinfo?taskID=%s" % (options.weburl, task_id)) - #if task_opts.wait or (task_opts.wait is None and not _running_in_bg()): - # session.logout() - # return watch_tasks(session, [task_id], quiet=options.quiet) - #else: - # return - - -def handle_image_build(options, session, args): - """[build] Create a disk image given an install tree""" - formats = ('vmdk', 'qcow', 'qcow2', 'vdi', 'vpc', 'rhevm-ova', - 'vsphere-ova', 'vagrant-virtualbox', 'vagrant-libvirt', - 'vagrant-vmware-fusion', 'vagrant-hyperv', 'docker', 'raw-xz', - 'liveimg-squashfs', 'tar-gz') - usage = _("usage: %prog image-build [options] " + - " [...]") - usage += _("\n %prog image-build --config FILE") - usage += _("\n\n(Specify the --help global option for a list of other " + - "help options)") - parser = OptionParser(usage=usage) - parser.add_option("--background", action="store_true", - help=_("Run the image creation task at a lower priority")) - parser.add_option("--config", - help=_("Use a configuration file to define image-build options " + - "instead of command line options (they will be ignored).")) - parser.add_option("--disk-size", default=10, - help=_("Set the disk device size in gigabytes")) - parser.add_option("--distro", - help=_("specify the RPM based distribution the image will be based " + - "on with the format RHEL-X.Y, CentOS-X.Y, SL-X.Y, or Fedora-NN. " + - "The packages for the Distro you choose must have been built " + - "in this system.")) - parser.add_option("--format", default=[], action="append", - help=_("Convert results to one or more formats " + - "(%s), this option may be used " % ', '.join(formats) + - "multiple times. By default, specifying this option will " + - "omit the raw disk image (which is 10G in size) from the " + - "build results. If you really want it included with converted " + - "images, pass in 'raw' as an option.")) - parser.add_option("--kickstart", help=_("Path to a local kickstart file")) - parser.add_option("--ksurl", metavar="SCMURL", - help=_("The URL to the SCM containing the kickstart file")) - parser.add_option("--ksversion", metavar="VERSION", - help=_("The syntax version used in the kickstart file")) - parser.add_option("--noprogress", action="store_true", - help=_("Do not display progress of the upload")) - parser.add_option("--nowait", action="store_false", dest="wait", - help=_("Don't wait on image creation")) - parser.add_option("--ova-option", action="append", - help=_("Override a value in the OVA description XML. Provide a value " + - "in a name=value format, such as 'ovf_memory_mb=6144'")) - parser.add_option("--factory-parameter", nargs=2, action="append", - help=_("Pass a parameter to Image Factory. The results are highly specific " + - "to the image format being created. This is a two argument parameter " + - "that can be specified an arbitrary number of times. For example: " - "--factory-parameter docker_cmd '[ \"/bin/echo Hello World\" ]'")) - parser.add_option("--release", help=_("Forcibly set the release field")) - parser.add_option("--repo", action="append", - help=_("Specify a repo that will override the repo used to install " + - "RPMs in the image. May be used multiple times. The " + - "build tag repo associated with the target is the default.")) - parser.add_option("--scratch", action="store_true", - help=_("Create a scratch image")) - parser.add_option("--skip-tag", action="store_true", - help=_("Do not attempt to tag package")) - parser.add_option("--can-fail", action="store", dest="optional_arches", - metavar="ARCH1,ARCH2,...", default="", - help=_("List of archs which are not blocking for build (separated by commas.")) - parser.add_option("--specfile", metavar="URL", - help=_("SCM URL to spec file fragment to use to generate wrapper RPMs")) - parser.add_option("--wait", action="store_true", - help=_("Wait on the image creation, even if running in the background")) - - (task_options, args) = parser.parse_args(args) - - if task_options.config: - if not os.path.exists(task_options.config): - parser.error(_("%s not found!" % task_options.config)) - section = 'image-build' - config = six.moves.configparser.ConfigParser() - conf_fd = open(task_options.config) - config.readfp(conf_fd) - conf_fd.close() - if not config.has_section(section): - parser.error(_("single section called [%s] is required" % section)) - # pluck out the positional arguments first - args = [] - for arg in ('name', 'version', 'target', 'install_tree'): - args.append(config.get(section, arg)) - config.remove_option(section, arg) - args.extend(config.get(section, 'arches').split(',')) - config.remove_option(section, 'arches') - # turn comma-separated options into lists - for arg in ('repo', 'format'): - if config.has_option(section, arg): - setattr(task_options, arg, config.get(section, arg).split(',')) - config.remove_option(section, arg) - if config.has_option(section, 'can_fail'): - setattr(task_options, 'optional_arches', config.get(section, 'can_fail').split(',')) - config.remove_option(section, 'can_fail') - # handle everything else - for k, v in config.items(section): - setattr(task_options, k, v) - - # ova-options belong in their own section - section = 'ova-options' - if config.has_section(section): - task_options.ova_option = [] - for k, v in config.items(section): - task_options.ova_option.append('%s=%s' % (k, v)) - - # as do factory-parameters - section = 'factory-parameters' - if config.has_section(section): - task_options.factory_parameter = [ ] - for k, v in config.items(section): - # We do this, rather than a dict, to match what argparse spits out - task_options.factory_parameter.append( (k, v) ) - - else: - if len(args) < 5: - parser.error(_("At least five arguments are required: a name, " + - "a version, a build target, a URL to an " + - "install tree, and 1 or more architectures.")) - if not task_options.ksurl and not task_options.kickstart: - parser.error(_('You must specify --kickstart')) - if not task_options.distro: - parser.error( - _("You must specify --distro. Examples: Fedora-16, RHEL-6.4, " + - "SL-6.4 or CentOS-6.4")) - _build_image_oz(options, task_options, session, args) - -def _build_image(options, task_opts, session, args, img_type): - """ - A private helper function that houses common CLI code for building - images with chroot-based tools. - """ - - if img_type not in ('livecd', 'appliance', 'livemedia'): - raise koji.GenericError('Unrecognized image type: %s' % img_type) - activate_session(session) - - # Set the task's priority. Users can only lower it with --background. - priority = None - if task_opts.background: - # relative to koji.PRIO_DEFAULT; higher means a "lower" priority. - priority = 5 - if _running_in_bg() or task_opts.noprogress: - callback = None - else: - callback = _progress_callback - - # We do some early sanity checking of the given target. - # Kojid gets these values again later on, but we check now as a convenience - # for the user. - target = args[2] - tmp_target = session.getBuildTarget(target) - if not tmp_target: - raise koji.GenericError(_("Unknown build target: %s" % target)) - dest_tag = session.getTag(tmp_target['dest_tag']) - if not dest_tag: - raise koji.GenericError(_("Unknown destination tag: %s" % - tmp_target['dest_tag_name'])) - - # Set the architecture - if img_type == 'livemedia': - # livemedia accepts multiple arches - arch = [koji.canonArch(a) for a in args[3].split(",")] - else: - arch = koji.canonArch(args[3]) - - # Upload the KS file to the staging area. - # If it's a URL, it's kojid's job to go get it when it does the checkout. - ksfile = args[4] - - if not task_opts.ksurl: - serverdir = _unique_path('cli-' + img_type) - session.uploadWrapper(ksfile, serverdir, callback=callback) - ksfile = os.path.join(serverdir, os.path.basename(ksfile)) - print('') - - hub_opts = {} - if hasattr(task_opts, 'optional_arches'): - hub_opts['optional_arches'] = task_opts.optional_arches.split(',') - passthru_opts = [ - 'format', 'install_tree_url', 'isoname', 'ksurl', - 'ksversion', 'release', 'repo', 'scratch', 'skip_tag', - 'specfile', 'title', 'vcpu', 'vmem', 'optional_arches', - ] - for opt in passthru_opts: - val = getattr(task_opts, opt, None) - if val is not None: - hub_opts[opt] = val - - # finally, create the task. - task_id = session.buildImage(args[0], args[1], arch, target, ksfile, - img_type, opts=hub_opts, priority=priority) - - if not options.quiet: - print("Created task: %d" % task_id) - print("Task info: %s/taskinfo?taskID=%s" % (options.weburl, task_id)) - if task_opts.wait or (task_opts.wait is None and not _running_in_bg()): - session.logout() - return watch_tasks(session, [task_id], quiet=options.quiet) - else: - return - -def _build_image_oz(options, task_opts, session, args): - """ - A private helper function that houses common CLI code for building - images with Oz and ImageFactory - """ - activate_session(session) - - # Set the task's priority. Users can only lower it with --background. - priority = None - if task_opts.background: - # relative to koji.PRIO_DEFAULT; higher means a "lower" priority. - priority = 5 - if _running_in_bg() or task_opts.noprogress: - callback = None - else: - callback = _progress_callback - - # We do some early sanity checking of the given target. - # Kojid gets these values again later on, but we check now as a convenience - # for the user. - target = args[2] - tmp_target = session.getBuildTarget(target) - if not tmp_target: - raise koji.GenericError(_("Unknown build target: %s" % target)) - dest_tag = session.getTag(tmp_target['dest_tag']) - if not dest_tag: - raise koji.GenericError(_("Unknown destination tag: %s" % - tmp_target['dest_tag_name'])) - - # Set the architectures - arches = [] - for arch in args[4:]: - arches.append(koji.canonArch(arch)) - - # Upload the KS file to the staging area. - # If it's a URL, it's kojid's job to go get it when it does the checkout. - if not task_opts.ksurl: - if not task_opts.scratch: - # only scratch builds can omit ksurl - raise koji.GenericError(_("Non-scratch builds must provide ksurl")) - ksfile = task_opts.kickstart - serverdir = _unique_path('cli-image') - session.uploadWrapper(ksfile, serverdir, callback=callback) - task_opts.kickstart = os.path.join('work', serverdir, - os.path.basename(ksfile)) - print('') - - hub_opts = {} - for opt in ('ksurl', 'ksversion', 'kickstart', 'scratch', 'repo', - 'release', 'skip_tag', 'specfile', 'distro', 'format', - 'disk_size', 'ova_option', 'factory_parameter', - 'optional_arches'): - val = getattr(task_opts, opt, None) - if val is not None: - hub_opts[opt] = val - - # finally, create the task. - task_id = session.buildImageOz(args[0], args[1], arches, target, args[3], - opts=hub_opts, priority=priority) - - if not options.quiet: - print("Created task: %d" % task_id) - print("Task info: %s/taskinfo?taskID=%s" % (options.weburl, task_id)) - if task_opts.wait or (task_opts.wait is None and not _running_in_bg()): - session.logout() - return watch_tasks(session, [task_id], quiet=options.quiet) - else: - return - -def handle_win_build(options, session, args): - """[build] Build a Windows package from source""" - # Usage & option parsing - usage = _("usage: %prog win-build [options] target URL VM") - usage += _("\n(Specify the --help global option for a list of other " + - "help options)") - parser = OptionParser(usage=usage) - parser.add_option("--winspec", metavar="URL", - help=_("SCM URL to retrieve the build descriptor from. " + \ - "If not specified, the winspec must be in the root directory " + \ - "of the source repository.")) - parser.add_option("--patches", metavar="URL", - help=_("SCM URL of a directory containing patches to apply " + \ - "to the sources before building")) - parser.add_option("--cpus", type="int", - help=_("Number of cpus to allocate to the build VM " + \ - "(requires admin access)")) - parser.add_option("--mem", type="int", - help=_("Amount of memory (in megabytes) to allocate to the build VM " + \ - "(requires admin access)")) - parser.add_option("--static-mac", action="store_true", - help=_("Retain the original MAC address when cloning the VM")) - parser.add_option("--specfile", metavar="URL", - help=_("SCM URL of a spec file fragment to use to generate wrapper RPMs")) - parser.add_option("--scratch", action="store_true", - help=_("Perform a scratch build")) - parser.add_option("--repo-id", type="int", help=_("Use a specific repo")) - parser.add_option("--skip-tag", action="store_true", - help=_("Do not attempt to tag package")) - parser.add_option("--background", action="store_true", - help=_("Run the build at a lower priority")) - 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) - (build_opts, args) = parser.parse_args(args) - if len(args) != 3: - parser.error(_("Exactly three arguments (a build target, a SCM URL, and a VM name) are required")) - assert False # pragma: no cover - 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'])) - scmurl = args[1] - vm_name = args[2] - opts = {} - for key in ('winspec', 'patches', 'cpus', 'mem', 'static_mac', - 'specfile', 'scratch', 'repo_id', 'skip_tag'): - 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 - task_id = session.winBuild(vm_name, scmurl, 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_free_task(options, session, args): - "[admin] Free a task" - usage = _("usage: %prog free-task [options] [ ...]") - usage += _("\n(Specify the --help global option for a list of other help options)") - parser = OptionParser(usage=usage) - (options, args) = parser.parse_args(args) - activate_session(session) - tlist = [] - for task_id in args: - try: - tlist.append(int(task_id)) - except ValueError: - parser.error(_("task-id must be an integer")) - assert False # pragma: no cover - for task_id in tlist: - session.freeTask(task_id) - -def handle_cancel(options, session, args): - "[build] Cancel tasks and/or builds" - usage = _("usage: %prog cancel [options] [ ...]") - usage += _("\n(Specify the --help global option for a list of other help options)") - parser = OptionParser(usage=usage) - parser.add_option("--justone", action="store_true", help=_("Do not cancel subtasks")) - parser.add_option("--full", action="store_true", help=_("Full cancellation (admin only)")) - parser.add_option("--force", action="store_true", help=_("Allow subtasks with --full")) - (options, args) = parser.parse_args(args) - if len(args) == 0: - parser.error(_("You must specify at least one task id or build")) - assert False # pragma: no cover - activate_session(session) - tlist = [] - blist = [] - for arg in args: - try: - tlist.append(int(arg)) - except ValueError: - try: - koji.parse_NVR(arg) - blist.append(arg) - except koji.GenericError: - parser.error(_("please specify only task ids (integer) or builds (n-v-r)")) - assert False # pragma: no cover - if tlist: - opts = {} - remote_fn = session.cancelTask - if options.justone: - opts['recurse'] = False - elif options.full: - remote_fn = session.cancelTaskFull - if options.force: - opts['strict'] = False - for task_id in tlist: - remote_fn(task_id, **opts) - for build in blist: - session.cancelBuild(build) - -def handle_set_task_priority(options, session, args): - "[admin] Set task priority" - usage = _("usage: %prog set-task-priority [options] --priority= [task-id]...") - usage += _("\n(Specify the --help global option for a list of other help options)") - parser = OptionParser(usage=usage) - parser.add_option("--priority", type="int", help=_("New priority")) - parser.add_option("--recurse", action="store_true", default=False, help=_("Change priority of child tasks as well")) - (options, args) = parser.parse_args(args) - if len(args) == 0: - parser.error(_("You must specify at least one task id")) - assert False # pragma: no cover - - if options.priority is None: - parser.error(_("You must specify --priority")) - assert False # pragma: no cover - try: - tasks = [int(a) for a in args] - except ValueError: - parser.error(_("Task numbers must be integers")) - - activate_session(session) - - for task_id in tasks: - session.setTaskPriority(task_id, options.priority, options.recurse) - -def _list_tasks(options, session): - "Retrieve a list of tasks" - - callopts = { - 'state' : [koji.TASK_STATES[s] for s in ('FREE', 'OPEN', 'ASSIGNED')], - 'decode' : True, - } - - if options.mine: - user = session.getLoggedInUser() - if not user: - print("Unable to determine user") - sys.exit(1) - callopts['owner'] = user['id'] - if options.user: - user = session.getUser(options.user) - if not user: - print("No such user: %s" % options.user) - sys.exit(1) - callopts['owner'] = user['id'] - if options.arch: - callopts['arch'] = parse_arches(options.arch, to_list=True) - if options.method: - callopts['method'] = options.method - if options.channel: - chan = session.getChannel(options.channel) - if not chan: - print("No such channel: %s" % options.channel) - sys.exit(1) - callopts['channel_id'] = chan['id'] - if options.host: - host = session.getHost(options.host) - if not host: - print("No such host: %s" % options.host) - sys.exit(1) - callopts['host_id'] = host['id'] - - qopts = {'order' : 'priority,create_time'} - tasklist = session.listTasks(callopts, qopts) - tasks = dict([(x['id'], x) for x in tasklist]) - - #thread the tasks - for t in tasklist: - if t['parent'] is not None: - parent = tasks.get(t['parent']) - if parent: - parent.setdefault('children',[]) - parent['children'].append(t) - t['sub'] = True - - return tasklist - - -def handle_list_tasks(options, session, args): - "[info] Print the list of tasks" - usage = _("usage: %prog list-tasks [options]") - usage += _("\n(Specify the --help global option for a list of other help options)") - parser = OptionParser(usage=usage) - parser.add_option("--mine", action="store_true", help=_("Just print your tasks")) - parser.add_option("--user", help=_("Only tasks for this user")) - parser.add_option("--arch", help=_("Only tasks for this architecture")) - parser.add_option("--method", help=_("Only tasks of this method")) - parser.add_option("--channel", help=_("Only tasks in this channel")) - parser.add_option("--host", help=_("Only tasks for this host")) - parser.add_option("--quiet", action="store_true", help=_("Do not display the column headers"), default=options.quiet) - (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) - tasklist = _list_tasks(options, session) - if not tasklist: - print("(no tasks)") - return - if not options.quiet: - print_task_headers() - for t in tasklist: - if t.get('sub'): - # this subtask will appear under another task - continue - print_task_recurse(t) - -def handle_set_pkg_arches(options, session, args): - "[admin] Set the list of extra arches for a package" - usage = _("usage: %prog set-pkg-arches [options] arches 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=_("Force operation")) - (options, args) = parser.parse_args(args) - if len(args) < 3: - parser.error(_("Please specify an archlist, a tag, and at least one package")) - assert False # pragma: no cover - activate_session(session) - arches = parse_arches(args[0]) - tag = args[1] - for package in args[2:]: - #really should implement multicall... - session.packageListSetArches(tag,package,arches,force=options.force) - -def handle_set_pkg_owner(options, session, args): - "[admin] Set the owner for a package" - usage = _("usage: %prog set-pkg-owner [options] owner 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=_("Force operation")) - (options, args) = parser.parse_args(args) - if len(args) < 3: - parser.error(_("Please specify an owner, a tag, and at least one package")) - assert False # pragma: no cover - activate_session(session) - owner = args[0] - tag = args[1] - for package in args[2:]: - #really should implement multicall... - session.packageListSetOwner(tag,package,owner,force=options.force) - -def handle_set_pkg_owner_global(options, session, args): - "[admin] Set the owner for a package globally" - usage = _("usage: %prog set-pkg-owner-global [options] owner package [package2 ...]") - usage += _("\n(Specify the --help global option for a list of other help options)") - parser = OptionParser(usage=usage) - parser.add_option("--verbose", action='store_true', help=_("List changes")) - parser.add_option("--test", action='store_true', help=_("Test mode")) - parser.add_option("--old-user", "--from", action="store", help=_("Only change ownership for packages belonging to this user")) - (options, args) = parser.parse_args(args) - if options.old_user: - if len(args) < 1: - parser.error(_("Please specify an owner")) - assert False # pragma: no cover - elif len(args) < 2: - parser.error(_("Please specify an owner and at least one package")) - assert False # pragma: no cover - activate_session(session) - owner = args[0] - packages = args[1:] - user = session.getUser(owner) - if not user: - print("No such user: %s" % owner) - return 1 - opts = {'with_dups' : True} - old_user = None - if options.old_user: - old_user = session.getUser(options.old_user) - if not old_user: - print("No such user: %s" % options.old_user) - return 1 - opts['userID'] = old_user['id'] - to_change = [] - for package in packages: - entries = session.listPackages(pkgID=package, **opts) - if not entries: - print("No data for package %s" % package) - continue - to_change.extend(entries) - if not packages and options.old_user: - entries = session.listPackages(**opts) - if not entries: - print("No data for user %s" % old_user['name']) - return 1 - to_change.extend(entries) - for entry in to_change: - if user['id'] == entry['owner_id']: - if options.verbose: - print("Preserving owner=%s for package %s in tag %s" \ - % (user['name'], package, entry['tag_name'])) - else: - if options.test: - print("Would have changed owner for %s in tag %s: %s -> %s" \ - % (entry['package_name'], entry['tag_name'], entry['owner_name'], user['name'])) - continue - if options.verbose: - print("Changing owner for %s in tag %s: %s -> %s" \ - % (entry['package_name'], entry['tag_name'], entry['owner_name'], user['name'])) - session.packageListSetOwner(entry['tag_id'], entry['package_name'], user['id']) - -def anon_handle_watch_task(options, session, args): - "[monitor] Track progress of particular tasks" - usage = _("usage: %prog watch-task [options] [...]") - usage += _("\n(Specify the --help global option for a list of other help options)") - parser = OptionParser(usage=usage) - parser.add_option("--quiet", action="store_true", - help=_("Do not print the task information"), default=options.quiet) - parser.add_option("--mine", action="store_true", help=_("Just watch your tasks")) - parser.add_option("--user", help=_("Only tasks for this user")) - parser.add_option("--arch", help=_("Only tasks for this architecture")) - parser.add_option("--method", help=_("Only tasks of this method")) - parser.add_option("--channel", help=_("Only tasks in this channel")) - parser.add_option("--host", help=_("Only tasks for this host")) - (options, args) = parser.parse_args(args) - selection = (options.mine or - options.user or - options.arch or - options.method or - options.channel or - options.host) - if args and selection: - parser.error(_("Selection options cannot be combined with a task list")) - - activate_session(session) - if selection: - tasks = [task['id'] for task in _list_tasks(options, session)] - if not tasks: - print("(no tasks)") - return - else: - tasks = [] - for task in args: - try: - tasks.append(int(task)) - except ValueError: - parser.error(_("task id must be an integer")) - if not tasks: - parser.error(_("at least one task id must be specified")) - - return watch_tasks(session, tasks, quiet=options.quiet) - -def anon_handle_watch_logs(options, session, args): - "[monitor] Watch logs in realtime" - usage = _("usage: %prog watch-logs [options] [...]") - usage += _("\n(Specify the --help global option for a list of other help options)") - parser = OptionParser(usage=usage) - parser.add_option("--log", help=_("Watch only a specific log")) - (options, args) = parser.parse_args(args) - activate_session(session) - - tasks = [] - for task in args: - try: - tasks.append(int(task)) - except ValueError: - parser.error(_("task id must be an integer")) - if not tasks: - parser.error(_("at least one task id must be specified")) - - watch_logs(session, tasks, options) - -def handle_make_task(opts, session, args): - "[admin] Create an arbitrary task" - usage = _("usage: %prog make-task [options] [...]") - usage += _("\n(Specify the --help global option for a list of other help options)") - parser = OptionParser(usage=usage) - parser.add_option("--channel", help=_("set channel")) - parser.add_option("--priority", help=_("set priority")) - parser.add_option("--watch", action="store_true", help=_("watch the task")) - parser.add_option("--arch", help=_("set arch")) - (options, args) = parser.parse_args(args) - activate_session(session) - - taskopts = {} - for key in ('channel','priority','arch'): - value = getattr(options,key,None) - if value is not None: - taskopts[key] = value - task_id = session.makeTask(method=args[0], - arglist=list(map(arg_filter,args[1:])), - **taskopts) - print("Created task id %d" % task_id) - if _running_in_bg() or not options.watch: - return - else: - session.logout() - return watch_tasks(session, [task_id], quiet=opts.quiet) - -def handle_tag_build(opts, session, args): - "[bind] Apply a tag to one or more builds" - usage = _("usage: %prog tag-build [options] [...]") - 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 operation")) - parser.add_option("--nowait", action="store_true", help=_("Do not wait on task")) - (options, args) = parser.parse_args(args) - if len(args) < 2: - parser.error(_("This command takes at least two arguments: a tag name/ID and one or more package n-v-r's")) - assert False # pragma: no cover - activate_session(session) - tasks = [] - for pkg in args[1:]: - task_id = session.tagBuild(args[0], pkg, force=options.force) - #XXX - wait on task - tasks.append(task_id) - print("Created task %d" % task_id) - if _running_in_bg() or options.nowait: - return - else: - session.logout() - return watch_tasks(session,tasks,quiet=opts.quiet) - -def handle_move_build(opts, session, args): - "[bind] 'Move' one or more builds between tags" - usage = _("usage: %prog move-build [options] [...]") - 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 operation")) - parser.add_option("--nowait", action="store_true", help=_("do not wait on tasks")) - parser.add_option("--all", action="store_true", help=_("move all instances of a package, 's are package names")) - (options, args) = parser.parse_args(args) - if len(args) < 3: - if options.all: - parser.error(_("This command, with --all, takes at least three arguments: two tags and one or more package names")) - else: - parser.error(_("This command takes at least three arguments: two tags and one or more package n-v-r's")) - assert False # pragma: no cover - activate_session(session) - tasks = [] - builds = [] - - if options.all: - for arg in args[2:]: - pkg = session.getPackage(arg) - if not pkg: - print(_("Invalid package name %s, skipping." % arg)) - continue - tasklist = session.moveAllBuilds(args[0], args[1], arg, options.force) - tasks.extend(tasklist) - else: - for arg in args[2:]: - build = session.getBuild(arg) - if not build: - print(_("Invalid build %s, skipping." % arg)) - continue - if not build in builds: - builds.append(build) - - for build in builds: - task_id = session.moveBuild(args[0], args[1], build['id'], options.force) - tasks.append(task_id) - print("Created task %d, moving %s" % (task_id, koji.buildLabel(build))) - if _running_in_bg() or options.nowait: - return - else: - session.logout() - return watch_tasks(session, tasks, quiet=opts.quiet) - -def handle_untag_build(options, session, args): - "[bind] Remove a tag from one or more builds" - usage = _("usage: %prog untag-build [options] [...]") - usage += _("\n(Specify the --help global option for a list of other help options)") - parser = OptionParser(usage=usage) - parser.add_option("--all", action="store_true", help=_("untag all versions of the package in this tag")) - parser.add_option("--non-latest", action="store_true", help=_("untag all versions of the package in this tag except the latest")) - parser.add_option("-n", "--test", action="store_true", help=_("test mode")) - parser.add_option("-v", "--verbose", action="store_true", help=_("print details")) - parser.add_option("--force", action="store_true", help=_("force operation")) - (options, args) = parser.parse_args(args) - if options.non_latest and options.force: - if len(args) < 1: - parser.error(_("Please specify a tag")) - assert False # pragma: no cover - elif len(args) < 2: - parser.error(_("This command takes at least two arguments: a tag name/ID and one or more package n-v-r's")) - assert False # pragma: no cover - activate_session(session) - tag = session.getTag(args[0]) - if not tag: - parser.error(_("Invalid tag: %s" % args[0])) - if options.all: - builds = [] - for pkg in args[1:]: - builds.extend(session.listTagged(args[0], package=pkg)) - elif options.non_latest: - if options.force and len(args) == 1: - tagged = session.listTagged(args[0]) - else: - tagged = [] - for pkg in args[1:]: - tagged.extend(session.listTagged(args[0], package=pkg)) - # listTagged orders entries latest first - seen_pkg = {} - builds = [] - for binfo in tagged: - if binfo['name'] not in seen_pkg: - #latest for this package - if options.verbose: - print(_("Leaving latest build for package %(name)s: %(nvr)s") % binfo) - else: - builds.append(binfo) - seen_pkg[binfo['name']] = 1 - else: - tagged = session.listTagged(args[0]) - idx = dict([(b['nvr'], b) for b in tagged]) - builds = [] - for nvr in args[1:]: - binfo = idx.get(nvr) - if binfo: - builds.append(binfo) - else: - # not in tag, see if it even exists - binfo = session.getBuild(nvr) - if not binfo: - print(_("No such build: %s") % nvr) - else: - print(_("Build %s not in tag %s") % (nvr, tag['name'])) - if not options.force: - return 1 - builds.reverse() - for binfo in builds: - if options.test: - print(_("would have untagged %(nvr)s") % binfo) - else: - if options.verbose: - print(_("untagging %(nvr)s") % binfo) - session.untagBuild(tag['name'], binfo['nvr'], force=options.force) - -def handle_unblock_pkg(options, session, args): - "[admin] Unblock a package in the listing for tag" - usage = _("usage: %prog unblock-pkg [options] tag package [package2 ...]") - 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] - for package in args[1:]: - #really should implement multicall... - session.packageListUnblock(tag,package) - -def anon_handle_download_build(options, session, args): - "[download] Download a built package" - usage = _("usage: %prog download-build [options] ") - usage += _("\n(Specify the --help global option for a list of other help options)") - parser = OptionParser(usage=usage) - parser.add_option("--arch", "-a", dest="arches", metavar="ARCH", action="append", default=[], - help=_("Only download packages for this arch (may be used multiple times)")) - parser.add_option("--type", help=_("Download archives of the given type, rather than rpms (maven, win, or image)")) - parser.add_option("--latestfrom", dest="latestfrom", help=_("Download the latest build from this tag")) - parser.add_option("--debuginfo", action="store_true", help=_("Also download -debuginfo rpms")) - parser.add_option("--task-id", action="store_true", help=_("Interperet id as a task id")) - parser.add_option("--rpm", action="store_true", help=_("Download the given rpm")) - parser.add_option("--key", help=_("Download rpms signed with the given key")) - parser.add_option("--topurl", metavar="URL", default=options.topurl, - help=_("URL under which Koji files are accessible")) - parser.add_option("-q", "--quiet", action="store_true", help=_("Do not display progress meter"), - default=options.quiet) - (suboptions, args) = parser.parse_args(args) - if len(args) < 1: - parser.error(_("Please specify a package N-V-R or build ID")) - assert False # pragma: no cover - elif len(args) > 1: - parser.error(_("Only a single package N-V-R or build ID may be specified")) - assert False # pragma: no cover - - activate_session(session) - build = args[0] - - if build.isdigit(): - if suboptions.latestfrom: - print("--latestfrom not compatible with build IDs, specify a package name.") - return 1 - build = int(build) - if suboptions.task_id: - builds = session.listBuilds(taskID=build) - if not builds: - print("No associated builds for task %s" % build) - return 1 - build = builds[0]['build_id'] - - if suboptions.latestfrom: - # We want the latest build, not a specific build - try: - builds = session.listTagged(suboptions.latestfrom, latest=True, package=build, type=suboptions.type) - except koji.GenericError as data: - print("Error finding latest build: %s" % data) - return 1 - if not builds: - print("%s has no builds of %s" % (suboptions.latestfrom, build)) - return 1 - info = builds[0] - elif suboptions.rpm: - rpminfo = session.getRPM(build) - if rpminfo is None: - print("No such rpm: %s" % build) - return 1 - info = session.getBuild(rpminfo['build_id']) - else: - # if we're given an rpm name without --rpm, download the containing build - try: - nvra = koji.parse_NVRA(build) - rpminfo = session.getRPM(build) - build = rpminfo['build_id'] - except Exception: - pass - info = session.getBuild(build) - - if info is None: - print("No such build: %s" % build) - return 1 - - if not suboptions.topurl: - print("You must specify --topurl to download files") - return 1 - pathinfo = koji.PathInfo(topdir=suboptions.topurl) - - urls = [] - if suboptions.type: - archives = session.listArchives(buildID=info['id'], type=suboptions.type) - if not archives: - print("No %s archives available for %s" % (suboptions.type, koji.buildLabel(info))) - return 1 - if suboptions.type == 'maven': - for archive in archives: - url = pathinfo.mavenbuild(info) + '/' + pathinfo.mavenfile(archive) - urls.append((url, pathinfo.mavenfile(archive))) - elif suboptions.type == 'win': - for archive in archives: - url = pathinfo.winbuild(info) + '/' + pathinfo.winfile(archive) - urls.append((url, pathinfo.winfile(archive))) - elif suboptions.type == 'image': - if not suboptions.topurl: - print("You must specify --topurl to download images") - return 1 - pi = koji.PathInfo(topdir=suboptions.topurl) - for archive in archives: - url = '%s/%s' % (pi.imagebuild(info), archive['filename']) - urls.append((url, archive['filename'])) - else: - # can't happen - assert False # pragma: no cover - else: - arches = suboptions.arches - if len(arches) == 0: - arches = None - if suboptions.rpm: - rpms = [rpminfo] - else: - rpms = session.listRPMs(buildID=info['id'], arches=arches) - if not rpms: - if arches: - print("No %s packages available for %s" % (" or ".join(arches), koji.buildLabel(info))) - else: - print("No packages available for %s" % koji.buildLabel(info)) - return 1 - for rpm in rpms: - if not suboptions.debuginfo and koji.is_debuginfo(rpm['name']): - continue - if suboptions.key: - fname = pathinfo.signed(rpm, suboptions.key) - else: - fname = pathinfo.rpm(rpm) - url = pathinfo.build(info) + '/' + fname - urls.append((url, os.path.basename(fname))) - - def _progress(download_t, download_d, upload_t, upload_d): - if download_t == 0: - percent_done = 0.0 - else: - percent_done = float(download_d)/float(download_t) - percent_done_str = "%02d%%" % (percent_done * 100) - data_done = _format_size(download_d) - - sys.stdout.write("[% -36s] % 4s % 10s\r" % ('='*(int(percent_done * 36)), percent_done_str, data_done)) - sys.stdout.flush() - - for url, relpath in urls: - if '/' in relpath: - koji.ensuredir(os.path.dirname(relpath)) - print(relpath) - c = pycurl.Curl() - c.setopt(c.URL, url) - c.setopt(c.WRITEDATA, open(relpath, 'wb')) - if not suboptions.quiet: - c.setopt(c.NOPROGRESS, False) - c.setopt(c.XFERINFOFUNCTION, _progress) - c.perform() - print('') - - -def anon_handle_download_logs(options, session, args): - "[download] Download a logs for package" - - FAIL_LOG = "task_failed.log" - usage = _("usage: %prog download-logs [options] [ ...]") - usage += _("\n %prog download-logs [options] --nvr [ ...]") - usage += _("\n(Specify the --help global option for a list of other help options)") - usage += _("\nCreates special log with name %s if task failed." % FAIL_LOG) - parser = OptionParser(usage=usage) - parser.add_option("-r", "--recurse", action="store_true", - help=_("Process children of this task as well")) - parser.add_option("--nvr", action="store_true", - help=_("Get logs from n-v-r")) - parser.add_option("-m", "--match", action="append", metavar="PATTERN", - help=_("Get only log matching PATTERN. May be used multiple times.")) - parser.add_option("-c", "--continue", action="store_true", dest="cont", - help=_("Continue previous download")) - parser.add_option("-d", "--dir", metavar="DIRECTORY", default='kojilogs', - help=_("Write logs to DIRECTORY")) - (suboptions, args) = parser.parse_args(args) - - if len(args) < 1: - parser.error(_("Please specify at least one task id or n-v-r")) - - def write_fail_log(task_log_dir, task_id): - """Gets output only from failed tasks""" - try: - result = session.getTaskResult(task_id) - # with current code, failed task results should always be faults, - # but that could change in the future - content = pprint.pformat(result) - except (xmlrpclib.Fault, koji.GenericError): - etype, e = sys.exc_info()[:2] - content = ''.join(traceback.format_exception_only(etype, e)) - full_filename = os.path.normpath(os.path.join(task_log_dir, FAIL_LOG)) - koji.ensuredir(os.path.dirname(full_filename)) - sys.stdout.write("Writing: %s\n" % full_filename) - open(full_filename, 'w').write(content) - - def download_log(task_log_dir, task_id, filename, blocksize=102400, volume=None): - # Create directories only if there is any log file to write to - # For each non-default volume create special sub-directory - if volume not in (None, 'DEFAULT'): - full_filename = os.path.normpath(os.path.join(task_log_dir, volume, filename)) - else: - full_filename = os.path.normpath(os.path.join(task_log_dir, filename)) - koji.ensuredir(os.path.dirname(full_filename)) - contents = 'IGNORE ME!' - if suboptions.cont and os.path.exists(full_filename): - sys.stdout.write("Continuing: %s\n" % full_filename) - fd = open(full_filename, 'ab') - offset = fd.tell() - else: - sys.stdout.write("Downloading: %s\n" % full_filename) - fd = open(full_filename, 'wb') - offset = 0 - try: - while contents: - contents = session.downloadTaskOutput(task_id, filename, offset=offset, size=blocksize, volume=volume) - offset += len(contents) - if contents: - fd.write(contents) - finally: - fd.close() - - def save_logs(task_id, match, parent_dir='.', recurse=True): - assert task_id == int(task_id), "Task id must be number: %r" % task_id - task_info = session.getTaskInfo(task_id) - if task_info is None: - error(_("No such task id: %i" % task_id)) - files = list_task_output_all_volumes(session, task_id) - logs = [] # list of tuples (filename, volume) - for filename in files: - if not filename.endswith(".log"): - continue - if match and not koji.util.multi_fnmatch(filename, match): - continue - logs += [(filename, volume) for volume in files[filename]] - - task_log_dir = os.path.join(parent_dir, - "%s-%s" % (task_info["arch"], task_id)) - - count = 0 - state = koji.TASK_STATES[task_info['state']] - if state == 'FAILED': - if not match or koji.util.multi_fnmatch(FAIL_LOG, match): - write_fail_log(task_log_dir, task_id) - count += 1 - elif state not in ['CLOSED', 'CANCELED']: - sys.stderr.write(_("Warning: task %s is %s\n") % (task_id, state)) - - for log_filename, log_volume in logs: - download_log(task_log_dir, task_id, log_filename, volume=log_volume) - count += 1 - - if count == 0 and not recurse: - sys.stderr.write(_("No logs found for task %i. Perhaps try --recurse?\n") % task_id) - - if recurse: - child_tasks = session.getTaskChildren(task_id) - for child_task in child_tasks: - save_logs(child_task['id'], match, task_log_dir, recurse) - - for arg in args: - if suboptions.nvr: - suboptions.recurse = True - binfo = session.getBuild(arg) - if binfo is None: - error(_("There is no build with n-v-r: %s" % arg)) - assert binfo['task_id'], binfo - task_id = binfo['task_id'] - sys.stdout.write("Using task ID: %s\n" % task_id) - else: - try: - task_id = int(arg) - except ValueError: - error(_("Task id must be number: %r") % task_id) - continue - save_logs(task_id, suboptions.match, suboptions.dir, suboptions.recurse) - - -def anon_handle_download_task(options, session, args): - "[download] Download the output of a build task " - usage = _("usage: %prog download-task ") - usage += _("\n(Specify the --help global option for a list of other help options)") - parser = OptionParser(usage=usage) - parser.add_option("--arch", dest="arches", metavar="ARCH", action="append", default=[], - help=_("Only download packages for this arch (may be used multiple times)")) - parser.add_option("--logs", dest="logs", action="store_true", default=False, help=_("Also download build logs")) - - (suboptions, args) = parser.parse_args(args) - if len(args) == 0: - parser.error(_("Please specify a task ID")) - elif len(args) > 1: - parser.error(_("Only one task ID may be specified")) - - base_task_id = int(args.pop()) - if len(suboptions.arches) > 0: - suboptions.arches = ",".join(suboptions.arches).split(",") - - # get downloadable tasks - - base_task = session.getTaskInfo(base_task_id) - - check_downloadable = lambda task: task["method"] == "buildArch" - downloadable_tasks = [] - - if check_downloadable(base_task): - downloadable_tasks.append(base_task) - else: - subtasks = session.getTaskChildren(base_task_id) - downloadable_tasks.extend(list(filter(check_downloadable, subtasks))) - - # get files for download - - downloads = [] - - for task in downloadable_tasks: - files = list_task_output_all_volumes(session, task["id"]) - for filename in files: - if filename.endswith(".log") and suboptions.logs: - for volume in files[filename]: - # rename logs, they would conflict - new_filename = "%s.%s.log" % (filename.rstrip(".log"), task["arch"]) - downloads.append((task, filename, volume, new_filename)) - continue - - if filename.endswith(".rpm"): - for volume in files[filename]: - filearch = filename.split(".")[-2] - if len(suboptions.arches) == 0 or filearch in suboptions.arches: - downloads.append((task, filename, volume, filename)) - continue - - if len(downloads) == 0: - error(_("No files for download found.")) - - required_tasks = {} - for (task, nop, nop, nop) in downloads: - if task["id"] not in required_tasks: - required_tasks[task["id"]] = task - - for task_id in required_tasks: - if required_tasks[task_id]["state"] != koji.TASK_STATES.get("CLOSED"): - if task_id == base_task_id: - error(_("Task %d has not finished yet.") % task_id) - else: - error(_("Child task %d has not finished yet.") % task_id) - - # perform the download - - number = 0 - for (task, filename, volume, new_filename) in downloads: - number += 1 - if volume not in (None, 'DEFAULT'): - koji.ensuredir(volume) - new_filename = os.path.join(volume, new_filename) - print(_("Downloading [%d/%d]: %s") % (number, len(downloads), new_filename)) - output_file = open(new_filename, "wb") - output_file.write(session.downloadTaskOutput(task["id"], filename, volume=volume)) - output_file.close() - -def anon_handle_wait_repo(options, session, args): - "[monitor] Wait for a repo to be regenerated" - usage = _("usage: %prog wait-repo [options] ") - usage += _("\n(Specify the --help global option for a list of other help options)") - parser = OptionParser(usage=usage) - parser.add_option("--build", metavar="NVR", dest="builds", action="append", default=[], - help=_("Check that the given build is in the newly-generated repo (may be used multiple times)")) - parser.add_option("--target", action="store_true", help=_("Interpret the argument as a build target name")) - parser.add_option("--timeout", type="int", help=_("Amount of time to wait (in minutes) before giving up (default: 120)"), default=120) - parser.add_option("--quiet", action="store_true", help=_("Suppress output, success or failure will be indicated by the return value only"), default=options.quiet) - (suboptions, args) = parser.parse_args(args) - - start = time.time() - - builds = [koji.parse_NVR(build) for build in suboptions.builds] - if len(args) < 1: - parser.error(_("Please specify a tag name")) - elif len(args) > 1: - parser.error(_("Only one tag may be specified")) - - tag = args[0] - - if suboptions.target: - target_info = session.getBuildTarget(tag) - if not target_info: - parser.error("Invalid build target: %s" % tag) - tag = target_info['build_tag_name'] - tag_id = target_info['build_tag'] - else: - tag_info = session.getTag(tag) - if not tag_info: - parser.error("Invalid tag: %s" % tag) - targets = session.getBuildTargets(buildTagID=tag_info['id']) - if not targets: - print("%(name)s is not a build tag for any target" % tag_info) - targets = session.getBuildTargets(destTagID=tag_info['id']) - if targets: - maybe = {}.fromkeys([t['build_tag_name'] for t in targets]) - maybe = list(maybe.keys()) - maybe.sort() - print("Suggested tags: %s" % ', '.join(maybe)) - return 1 - tag_id = tag_info['id'] - - - for nvr in builds: - data = session.getLatestBuilds(tag_id, package=nvr["name"]) - if len(data) == 0: - print("Warning: package %s is not in tag %s" % (nvr["name"], tag)) - else: - present_nvr = [x["nvr"] for x in data][0] - if present_nvr != "%s-%s-%s" % (nvr["name"], nvr["version"], nvr["release"]): - print("Warning: nvr %s-%s-%s is not current in tag %s\n latest build in %s is %s" % (nvr["name"], nvr["version"], nvr["release"], tag, tag, present_nvr)) - - last_repo = None - repo = session.getRepo(tag_id) - - while True: - if builds and repo and repo != last_repo: - if koji.util.checkForBuilds(session, tag_id, builds, repo['create_event'], latest=True): - if not suboptions.quiet: - print("Successfully waited %s for %s to appear in the %s repo" % (koji.util.duration(start), koji.util.printList(suboptions.builds), tag)) - return - - if (time.time() - start) >= (suboptions.timeout * 60.0): - if not suboptions.quiet: - if builds: - print("Unsuccessfully waited %s for %s to appear in the %s repo" % (koji.util.duration(start), koji.util.printList(suboptions.builds), tag)) - else: - print("Unsuccessfully waited %s for a new %s repo" % (koji.util.duration(start), tag)) - return 1 - - time.sleep(options.poll_interval) - last_repo = repo - repo = session.getRepo(tag_id) - - if not builds: - if repo != last_repo: - if not suboptions.quiet: - print("Successfully waited %s for a new %s repo" % (koji.util.duration(start), tag)) - return - -_search_types = ('package', 'build', 'tag', 'target', 'user', 'host', 'rpm', 'maven', 'win') - -def handle_regen_repo(options, session, args): - "[admin] Force a repo to be regenerated" - usage = _("usage: %prog regen-repo [options] ") - usage += _("\n(Specify the --help global option for a list of other help options)") - parser = OptionParser(usage=usage) - parser.add_option("--target", action="store_true", help=_("Interpret the argument as a build target name")) - parser.add_option("--nowait", action="store_true", help=_("Don't wait on for regen to finish")) - parser.add_option("--debuginfo", action="store_true", help=_("Include debuginfo rpms in repo")) - parser.add_option("--source", "--src", action="store_true", help=_("Include source rpms in the repo")) - (suboptions, args) = parser.parse_args(args) - if len(args) == 0: - parser.error(_("A tag name must be specified")) - assert False # pragma: no cover - elif len(args) > 1: - if suboptions.target: - parser.error(_("Only a single target may be specified")) - else: - parser.error(_("Only a single tag name may be specified")) - assert False # pragma: no cover - activate_session(session) - tag = args[0] - repo_opts = {} - if suboptions.target: - info = session.getBuildTarget(tag) - if not info: - parser.error(_("No matching build target: " + tag)) - assert False # pragma: no cover - tag = info['build_tag_name'] - info = session.getTag(tag) - else: - info = session.getTag(tag) - if not info: - parser.error(_("No matching tag: " + tag)) - assert False # pragma: no cover - tag = info['name'] - targets = session.getBuildTargets(buildTagID=info['id']) - if not targets: - print("Warning: %s is not a build tag" % tag) - if not info['arches']: - print("Warning: tag %s has an empty arch list" % info['name']) - if suboptions.debuginfo: - repo_opts['debuginfo'] = True - if suboptions.source: - repo_opts['src'] = True - task_id = session.newRepo(tag, **repo_opts) - print("Regenerating repo for tag: %s" % tag) - print("Created task: %d" % task_id) - print("Task info: %s/taskinfo?taskID=%s" % (options.weburl, task_id)) - if _running_in_bg() or suboptions.nowait: - return - else: - session.logout() - return watch_tasks(session, [task_id], quiet=options.quiet) - -def handle_dist_repo(options, session, args): - """Create a yum repo with distribution options""" - usage = _("usage: %prog dist-repo [options] tag keyID [keyID...]") - usage += _("\n(Specify the --help option for a list of other options)") - parser = OptionParser(usage=usage) - parser.add_option('--allow-missing-signatures', action='store_true', - default=False, - help=_('For RPMs not signed with a desired key, fall back to the ' - 'primary copy')) - parser.add_option("--arch", action='append', default=[], - help=_("Indicate an architecture to consider. The default is all " + - "architectures associated with the given tag. This option may " + - "be specified multiple times.")) - parser.add_option('--comps', help='Include a comps file in the repodata') - parser.add_option('--delta-rpms', metavar='REPO',default=[], - action='append', - help=_('Create delta rpms. REPO can be the id of another dist repo ' - 'or the name of a tag that has a dist repo. May be specified ' - 'multiple times.')) - parser.add_option('--event', type='int', - help=_('create a dist repository based on a Brew event')) - parser.add_option('--non-latest', dest='latest', default=True, - action='store_false', help='Include older builds, not just the latest') - parser.add_option('--multilib', default=None, metavar="CONFIG", - help=_('Include multilib packages in the repository using the given ' - 'config file')) - parser.add_option("--noinherit", action='store_true', default=False, - help=_('Do not consider tag inheritance')) - parser.add_option("--nowait", action='store_true', default=False, - help=_('Do not wait for the task to complete')) - parser.add_option('--skip-missing-signatures', action='store_true', default=False, - help=_('Skip RPMs not signed with the desired key(s)')) - task_opts, args = parser.parse_args(args) - if len(args) < 1: - parser.error(_('You must provide a tag to generate the repo from')) - if len(args) < 2 and not task_opts.allow_missing_signatures: - parser.error(_('Please specify one or more GPG key IDs (or ' - '--allow-missing-signatures)')) - if task_opts.allow_missing_signatures and task_opts.skip_missing_signatures: - parser.error(_('allow_missing_signatures and skip_missing_signatures ' - 'are mutually exclusive')) - activate_session(session) - stuffdir = _unique_path('cli-dist-repo') - if task_opts.comps: - if not os.path.exists(task_opts.comps): - parser.error(_('could not find %s') % task_opts.comps) - session.uploadWrapper(task_opts.comps, stuffdir, - callback=_progress_callback) - print('') - task_opts.comps = os.path.join(stuffdir, - os.path.basename(task_opts.comps)) - old_repos = [] - if len(task_opts.delta_rpms) > 0: - for repo in task_opts.delta_rpms: - if repo.isdigit(): - rinfo = session.repoInfo(int(repo), strict=True) - else: - # get dist repo for tag - rinfo = session.getRepo(repo, dist=True) - if not rinfo: - # maybe there is an expired one - rinfo = session.getRepo(repo, - state=koji.REPO_STATES['EXPIRED'], dist=True) - if not rinfo: - parser.errpr(_("Can't find repo for tag: %s") % repo) - old_repos.append(rinfo['id']) - tag = args[0] - keys = args[1:] - taginfo = session.getTag(tag) - if not taginfo: - parser.error(_('unknown tag %s') % tag) - if len(task_opts.arch) == 0: - arches = taginfo['arches'] or '' - task_opts.arch = arches.split() - if task_opts.arch == None: - parser.error(_('No arches given and no arches associated with tag')) - else: - for a in task_opts.arch: - if not taginfo['arches'] or a not in taginfo['arches']: - print(_('Warning: %s is not in the list of tag arches') % a) - if task_opts.multilib: - if not os.path.exists(task_opts.multilib): - parser.error(_('could not find %s') % task_opts.multilib) - if 'x86_64' in task_opts.arch and not 'i686' in task_opts.arch: - parser.error(_('The multilib arch (i686) must be included')) - if 's390x' in task_opts.arch and not 's390' in task_opts.arch: - parser.error(_('The multilib arch (s390) must be included')) - if 'ppc64' in task_opts.arch and not 'ppc' in task_opts.arch: - parser.error(_('The multilib arch (ppc) must be included')) - session.uploadWrapper(task_opts.multilib, stuffdir, - callback=_progress_callback) - task_opts.multilib = os.path.join(stuffdir, - os.path.basename(task_opts.multilib)) - print('') - try: - task_opts.arch.remove('noarch') # handled specifically - task_opts.arch.remove('src') # ditto - except ValueError: - pass - opts = { - 'arch': task_opts.arch, - 'comps': task_opts.comps, - 'delta': old_repos, - 'event': task_opts.event, - 'inherit': not task_opts.noinherit, - 'latest': task_opts.latest, - 'multilib': task_opts.multilib, - 'skip_missing_signatures': task_opts.skip_missing_signatures, - 'allow_missing_signatures': task_opts.allow_missing_signatures - } - task_id = session.distRepo(tag, keys, **opts) - print("Creating dist repo for tag " + tag) - if _running_in_bg() or task_opts.nowait: - return - else: - session.logout() - return watch_tasks(session, [task_id], quiet=options.quiet) - - -def anon_handle_search(options, session, args): - "[search] Search the system" - usage = _("usage: %prog search [options] search_type pattern") - usage += _('\nAvailable search types: %s') % ', '.join(_search_types) - usage += _("\n(Specify the --help global option for a list of other help options)") - parser = OptionParser(usage=usage) - parser.add_option("-r", "--regex", action="store_true", help=_("treat pattern as regex")) - parser.add_option("--exact", action="store_true", help=_("exact matches only")) - (options, args) = parser.parse_args(args) - if not args: - parser.print_help() - return - type = args[0] - if type not in _search_types: - parser.error(_("Unknown search type: %s") % type) - pattern = args[1] - matchType = 'glob' - if options.regex: - matchType = 'regexp' - elif options.exact: - matchType = 'exact' - data = session.search(pattern, type, matchType) - for row in data: - print(row['name']) - -def handle_moshimoshi(options, session, args): - "[misc] Introduce yourself" - usage = _("usage: %prog moshimoshi [options]") - parser = OptionParser(usage=usage) - (opts, args) = parser.parse_args(args) - if len(args) != 0: - parser.error(_("This command takes no arguments")) - assert False # pragma: no cover - activate_session(session) - u = session.getLoggedInUser() - if not u: - print("Not authenticated") - u = {'name' : 'anonymous user'} - print("%s, %s!" % (_printable_unicode(random.choice(greetings)), u["name"])) - print("") - print("You are using the hub at %s" % session.baseurl) - authtype = u.get('authtype', getattr(session, 'authtype', None)) - if authtype == koji.AUTHTYPE_NORMAL: - print("Authenticated via password") - elif authtype == koji.AUTHTYPE_GSSAPI: - print("Authenticated via GSSAPI") - elif authtype == koji.AUTHTYPE_KERB: - print("Authenticated via Kerberos principal %s" % u["krb_principal"]) - elif authtype == koji.AUTHTYPE_SSL: - print("Authenticated via client certificate %s" % options.cert) - -def handle_runroot(options, session, args): - "[admin] Run a command in a buildroot" - usage = _("usage: %prog runroot [options] ") - usage += _("\n(Specify the --help global option for a list of other help options)") - parser = OptionParser(usage=usage) - parser.disable_interspersed_args() - parser.add_option("-p", "--package", action="append", default=[], help=_("make sure this package is in the chroot")) - parser.add_option("-m", "--mount", action="append", default=[], help=_("mount this directory read-write in the chroot")) - parser.add_option("--skip-setarch", action="store_true", default=False, - help=_("bypass normal setarch in the chroot")) - parser.add_option("-w", "--weight", type='int', help=_("set task weight")) - parser.add_option("--channel-override", help=_("use a non-standard channel")) - parser.add_option("--task-id", action="store_true", default=False, - help=_("Print the ID of the runroot task")) - parser.add_option("--use-shell", action="store_true", default=False, - help=_("Run command through a shell, otherwise uses exec")) - parser.add_option("--new-chroot", action="store_true", default=False, - help=_("Run command with the --new-chroot (systemd-nspawn) option to mock")) - parser.add_option("--repo-id", type="int", help=_("ID of the repo to use")) - - (opts, args) = parser.parse_args(args) - - if len(args) < 3: - parser.error(_("Incorrect number of arguments")) - activate_session(session) - tag = args[0] - arch = args[1] - if opts.use_shell: - # everything must be correctly quoted - command = ' '.join(args[2:]) - else: - command = args[2:] - try: - kwargs = { 'channel': opts.channel_override, - 'packages': opts.package, - 'mounts': opts.mount, - 'repo_id': opts.repo_id, - 'skip_setarch': opts.skip_setarch, - 'weight': opts.weight } - # Only pass this kwarg if it is true - this prevents confusing older - # builders with a different function signature - if opts.new_chroot: - kwargs['new_chroot'] = True - - task_id = session.runroot(tag, arch, command, **kwargs) - except koji.GenericError as e: - if 'Invalid method' in str(e): - print("* The runroot plugin appears to not be installed on the" - " koji hub. Please contact the administrator.") - raise - if opts.task_id: - print(task_id) - - try: - while True: - # wait for the task to finish - if session.taskFinished(task_id): - break - time.sleep(options.poll_interval) - except KeyboardInterrupt: - # this is probably the right thing to do here - print("User interrupt: canceling runroot task") - session.cancelTask(task_id) - raise - output = list_task_output_all_volumes(session, task_id) - if 'runroot.log' in output: - for volume in output['runroot.log']: - log = session.downloadTaskOutput(task_id, 'runroot.log', volume=volume) - sys.stdout.write(log) - info = session.getTaskInfo(task_id) - if info is None: - sys.exit(1) - state = koji.TASK_STATES[info['state']] - if state in ('FAILED', 'CANCELED'): - sys.exit(1) - return - - -def handle_save_failed_tree(options, session, args): - "Create tarball with whole buildtree" - usage = _("usage: %prog save-failed-tree [options] ID") - usage += _("\n(Specify the --help global option for a list of other help options)") - parser = OptionParser(usage=usage) - parser.add_option("-f", "--full", action="store_true", default=False, - help=_("Download whole tree, if not specified, only builddir will be downloaded")) - parser.add_option("-t", "--task", action="store_const", dest="mode", - const="task", default="task", - help=_("Treat ID as a task ID (the default)")) - parser.add_option("-r", "--buildroot", action="store_const", dest="mode", - const="buildroot", - help=_("Treat ID as a buildroot ID")) - parser.add_option("--quiet", action="store_true", default=options.quiet, - help=_("Do not print the task information")) - parser.add_option("--nowait", action="store_true", - help=_("Don't wait on build")) - - (opts, args) = parser.parse_args(args) - - if len(args) != 1: - parser.error(_("List exactly one task or buildroot ID")) - - try: - id_val = int(args[0]) - except ValueError: - parser.error(_("ID must be an integer")) - - activate_session(session) - - if opts.mode == "buildroot": - br_id = id_val - else: - brs = [b['id'] for b in session.listBuildroots(taskID=id_val)] - if not brs: - print(_("No buildroots for task %s") % id_val) - return 1 - br_id = max(brs) - if len(brs) > 1: - print(_("Multiple buildroots for task. Choosing last one (%s)") % br_id) - - try: - task_id = session.saveFailedTree(br_id, opts.full) - except koji.GenericError as e: - m = str(e) - if 'Invalid method' in m: - print(_("* The save_failed_tree plugin appears to not be " - "installed on the koji hub. Please contact the " - "administrator.")) - return 1 - raise - - if not opts.quiet: - print(_("Created task %s for buildroot %s") % (task_id, br_id)) - print("Task info: %s/taskinfo?taskID=%s" - % (options.weburl, task_id)) - - if opts.nowait: - return - else: - session.logout() - watch_tasks(session, [task_id], quiet=opts.quiet) - - -def handle_help(options, session, args): - "[info] List available commands" - usage = _("usage: %prog help ...") - usage += _("\n(Specify the --help global option for a list of other help options)") - parser = OptionParser(usage=usage) - # 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) - - (options, args) = parser.parse_args(args) - - 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) - - if not chosen: - list_commands() - else: - list_commands(chosen) - - -def list_commands(categories_chosen=None): - if categories_chosen is None or "all" in categories_chosen: - categories_chosen = list(categories.keys()) - else: - # 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)) - - print("%s" % get_epilog_str().rstrip("\n")) - -def error(msg=None, code=1): - if msg: - sys.stderr.write(msg + "\n") - sys.stderr.flush() - sys.exit(code) - -def warn(msg): - sys.stderr.write(msg + "\n") - sys.stderr.flush() - -def has_krb_creds(): - if krbV is None: - return False - try: - ctx = krbV.default_context() - ccache = ctx.default_ccache() - princ = ccache.principal() - return True - except krbV.Krb5Error: - return False - -def activate_session(session): - """Test and login the session is applicable""" - global options - if options.authtype == "noauth" or options.noauth: - #skip authentication - pass - elif options.authtype == "ssl" or os.path.isfile(options.cert) and options.authtype is None: - # authenticate using SSL client cert - session.ssl_login(options.cert, None, options.serverca, proxyuser=options.runas) - elif options.authtype == "password" or options.user and options.authtype is None: - # authenticate using user/password - session.login() - elif options.authtype == "kerberos" or has_krb_creds() and options.authtype is None: - try: - if options.keytab and options.principal: - session.krb_login(principal=options.principal, keytab=options.keytab, proxyuser=options.runas) - else: - session.krb_login(proxyuser=options.runas) - except socket.error as e: - warn(_("Could not connect to Kerberos authentication service: %s") % e.args[1]) - except Exception as e: - if krbV is not None and isinstance(e, krbV.Krb5Error): - error(_("Kerberos authentication failed: %s (%s)") % (e.args[1], e.args[0])) - else: - raise - if not options.noauth and options.authtype != "noauth" and not session.logged_in: - error(_("Unable to log in, no authentication methods available")) - ensure_connection(session) - if options.debug: - print("successfully connected to hub") if __name__ == "__main__": global options diff --git a/cli/koji_cli/__init__.py b/cli/koji_cli/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/cli/koji_cli/__init__.py diff --git a/cli/koji_cli/commands.py b/cli/koji_cli/commands.py new file mode 100644 index 0000000..0eef519 --- /dev/null +++ b/cli/koji_cli/commands.py @@ -0,0 +1,6755 @@ +def _printable_unicode(s): + if six.PY2: + return s.encode('utf-8') + else: + return s +def handle_add_group(options, session, args): + "[admin] Add a group to a tag" + usage = _("usage: %prog add-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 + + 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 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]) + +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)")) + 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) + + +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 ...]") + 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) + +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 handle_build(options, session, args): + "[build] Build a package from source" + usage = _("usage: %prog build [options] target ") + 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.target: + if not options.arch: + print(_("Please specify an arch")) + return 1 + arch = options.arch + target = session.getBuildTarget(options.target) + if not target: + parser.error(_("Invalid target: %s" % options.target)) + opts['tag_name'] = target['build_tag_name'] + if options.latest: + opts['repoid'] = 'latest' + else: + repo = session.getRepo(target['build_tag']) + if not repo: + print(_("Could not get a repo for tag: %(name)s") % opts['tag_name']) + return 1 + opts['repoid'] = repo['id'] + else: + parser.error(_("Please specify one of: --tag, --target, --task, --buildroot")) + assert False # pragma: no cover + if options.name: + name = options.name + else: + name = "%(tag_name)s-repo_%(repoid)s" % opts + output = koji.genMockConfig(name, arch, **opts) + if options.ofile: + fo = open(options.ofile, 'w') + fo.write(output) + fo.close() + else: + print(output) + +def handle_disable_host(options, session, args): + "[admin] Mark one or more hosts as disabled" + usage = _("usage: %prog disable-host [options] hostname ...") + usage += _("\n(Specify the --help global option for a list of other help options)") + parser = OptionParser(usage=usage) + parser.add_option("--comment", help=_("Comment indicating why the host(s) are being disabled")) + (options, args) = parser.parse_args(args) + + activate_session(session) + session.multicall = True + for host in args: + session.getHost(host) + error = False + for host, [id] in zip(args, session.multiCall(strict=True)): + if not id: + 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.disableHost(host) + if options.comment is not None: + session.editHost(host, comment=options.comment) + session.multiCall(strict=True) + +def handle_enable_host(options, session, args): + "[admin] Mark one or more hosts as enabled" + usage = _("usage: %prog enable-host [options] hostname ...") + usage += _("\n(Specify the --help global option for a list of other help options)") + parser = OptionParser(usage=usage) + parser.add_option("--comment", help=_("Comment indicating why the host(s) are being enabled")) + (options, args) = parser.parse_args(args) + + activate_session(session) + session.multicall = True + for host in args: + session.getHost(host) + error = False + for host, [id] in zip(args, session.multiCall(strict=True)): + if not id: + 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.enableHost(host) + if options.comment is not None: + session.editHost(host, comment=options.comment) + session.multiCall(strict=True) + + +def handle_restart_hosts(options, session, args): + "[admin] Restart enabled hosts" + usage = _("usage: %prog restart-hosts [options]") + usage += _("\n(Specify the --help global option for a list of other help options)") + parser = OptionParser(usage=usage) + parser.add_option("--wait", action="store_true", + help=_("Wait on the task, even if running in the background")) + parser.add_option("--nowait", action="store_false", dest="wait", + help=_("Don't wait on task")) + parser.add_option("--quiet", action="store_true", + help=_("Do not print the task information"), default=options.quiet) + (my_opts, args) = parser.parse_args(args) + + activate_session(session) + task_id = session.restartHosts() + if my_opts.wait or (my_opts.wait is None and not _running_in_bg()): + session.logout() + return watch_tasks(session, [task_id], quiet=my_opts.quiet) + else: + return +def handle_import(options, session, args): + "[admin] Import externally built RPMs into the database" + usage = _("usage: %prog import [options] package [package...]") + usage += _("\n(Specify the --help global option for a list of other help options)") + parser = OptionParser(usage=usage) + parser.add_option("--link", action="store_true", help=_("Attempt to hardlink instead of uploading")) + parser.add_option("--test", action="store_true", help=_("Don't actually import")) + parser.add_option("--create-build", action="store_true", help=_("Auto-create builds as needed")) + parser.add_option("--src-epoch", help=_("When auto-creating builds, use this epoch")) + (options, args) = parser.parse_args(args) + if len(args) < 1: + parser.error(_("At least one package must be specified")) + assert False # pragma: no cover + if options.src_epoch in ('None', 'none', '(none)'): + options.src_epoch = None + elif options.src_epoch: + try: + options.src_epoch = int(options.src_epoch) + except (ValueError, TypeError): + parser.error(_("Invalid value for epoch: %s") % options.src_epoch) + assert False # pragma: no cover + activate_session(session) + to_import = {} + for path in args: + data = koji.get_header_fields(path, ('name','version','release','epoch', + 'arch','sigmd5','sourcepackage','sourcerpm')) + if data['sourcepackage']: + data['arch'] = 'src' + nvr = "%(name)s-%(version)s-%(release)s" % data + else: + nvr = "%(name)s-%(version)s-%(release)s" % koji.parse_NVRA(data['sourcerpm']) + to_import.setdefault(nvr,[]).append((path,data)) + builds_missing = False + nvrs = list(to_import.keys()) + nvrs.sort() + for nvr in nvrs: + to_import[nvr].sort() + for path, data in to_import[nvr]: + if data['sourcepackage']: + break + else: + #no srpm included, check for build + binfo = session.getBuild(nvr) + if not binfo: + print(_("Missing build or srpm: %s") % nvr) + builds_missing = True + if builds_missing and not options.create_build: + print(_("Aborting import")) + return + + #local function to help us out below + def do_import(path, data): + rinfo = dict([(k,data[k]) for k in ('name','version','release','arch')]) + prev = session.getRPM(rinfo) + if prev and not prev.get('external_repo_id', 0): + if prev['payloadhash'] == koji.hex_string(data['sigmd5']): + print(_("RPM already imported: %s") % path) + else: + print(_("WARNING: md5sum mismatch for %s") % path) + print(_(" A different rpm with the same name has already been imported")) + print(_(" Existing sigmd5 is %r, your import has %r") % ( + prev['payloadhash'], koji.hex_string(data['sigmd5']))) + print(_("Skipping import")) + return + if options.test: + print(_("Test mode -- skipping import for %s") % path) + return + serverdir = _unique_path('cli-import') + if options.link: + linked_upload(path, serverdir) + else: + sys.stdout.write(_("uploading %s... ") % path) + sys.stdout.flush() + session.uploadWrapper(path, serverdir) + print(_("done")) + sys.stdout.flush() + sys.stdout.write(_("importing %s... ") % path) + sys.stdout.flush() + try: + session.importRPM(serverdir, os.path.basename(path)) + except koji.GenericError as e: + print(_("\nError importing: %s" % str(e).splitlines()[-1])) + sys.stdout.flush() + else: + print(_("done")) + sys.stdout.flush() + + for nvr in nvrs: + # check for existing build + need_build = True + binfo = session.getBuild(nvr) + if binfo: + b_state = koji.BUILD_STATES[binfo['state']] + if b_state == 'COMPLETE': + need_build = False + elif b_state in ['FAILED', 'CANCELED']: + if not options.create_build: + print(_("Build %s state is %s. Skipping import") % (nvr, b_state)) + continue + else: + print(_("Build %s exists with state=%s. Skipping import") % (nvr, b_state)) + continue + + # import srpms first, if any + for path, data in to_import[nvr]: + if data['sourcepackage']: + if binfo and b_state != 'COMPLETE': + # need to fix the state + print(_("Creating empty build: %s") % nvr) + b_data = koji.util.dslice(binfo, ['name', 'version', 'release']) + b_data['epoch'] = data['epoch'] + session.createEmptyBuild(**b_data) + binfo = session.getBuild(nvr) + do_import(path, data) + need_build = False + + if need_build: + # if we're doing this here, we weren't given the matching srpm + if not options.create_build: + if binfo: + # should have caught this earlier, but just in case... + b_state = koji.BUILD_STATES[binfo['state']] + print(_("Build %s state is %s. Skipping import") % (nvr, b_state)) + continue + else: + print(_("No such build: %s (include matching srpm or use " + "--create-build option to add it)") % nvr) + continue + else: + # let's make a new build + b_data = koji.parse_NVR(nvr) + if options.src_epoch: + b_data['epoch'] = options.src_epoch + else: + # pull epoch from first rpm + data = to_import[nvr][0][1] + b_data['epoch'] = data['epoch'] + if options.test: + print(_("Test mode -- would have created empty build: %s") % nvr) + else: + print(_("Creating empty build: %s") % nvr) + session.createEmptyBuild(**b_data) + binfo = session.getBuild(nvr) + + for path, data in to_import[nvr]: + if data['sourcepackage']: + continue + do_import(path, data) + + +def handle_import_cg(options, session, args): + "[admin] Import external builds with rich metadata" + usage = _("usage: %prog import-cg [options] metadata_file files_dir") + usage += _("\n(Specify the --help global option for a list of other help options)") + parser = OptionParser(usage=usage) + parser.add_option("--noprogress", action="store_true", + help=_("Do not display progress of the upload")) + parser.add_option("--link", action="store_true", help=_("Attempt to hardlink instead of uploading")) + parser.add_option("--test", action="store_true", help=_("Don't actually import")) + (options, args) = parser.parse_args(args) + if len(args) < 2: + parser.error(_("Please specify metadata files directory")) + assert False # pragma: no cover + if json is None: + parser.error(_("Unable to find json module")) + assert False # pragma: no cover + activate_session(session) + metadata = json.load(open(args[0], 'r')) + if 'output' not in metadata: + print(_("Metadata contains no output")) + sys.exit(1) + localdir = args[1] + + to_upload = [] + for info in metadata['output']: + if info.get('metadata_only', False): + continue + localpath = os.path.join(localdir, info.get('relpath', ''), info['filename']) + if not os.path.exists(localpath): + parser.error(_("No such file: %s") % localpath) + to_upload.append([localpath, info]) + + if options.test: + return + + # get upload path + # XXX - need a better way + serverdir = _unique_path('cli-import') + + for localpath, info in to_upload: + relpath = os.path.join(serverdir, info.get('relpath', '')) + if _running_in_bg() or options.noprogress: + callback = None + else: + callback = _progress_callback + if options.link: + linked_upload(localpath, relpath) + else: + print("Uploading %s" % localpath) + session.uploadWrapper(localpath, relpath, callback=callback) + if callback: + print('') + + session.CGImport(metadata, serverdir) + + +def handle_import_comps(options, session, args): + "Import group/package information from a comps file" + usage = _("usage: %prog import-comps [options] ") + 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 import")) + (local_options, args) = parser.parse_args(args) + if len(args) != 2: + parser.error(_("Incorrect number of arguments")) + assert False # pragma: no cover + activate_session(session) + # check if the tag exists + dsttag = session.getTag(args[1]) + if dsttag is None: + print("No such tag: %s" % args[1]) + return 1 + if libcomps is not None: + _import_comps(session, args[0], args[1], local_options) + elif yumcomps is not None: + _import_comps_alt(session, args[0], args[1], local_options) + else: + print("comps module not available") + return 1 + + +def _import_comps(session, filename, tag, options): + """Import comps data using libcomps module""" + comps = libcomps.Comps() + comps.fromxml_f(filename) + force = options.force + ptypes = { + libcomps.PACKAGE_TYPE_DEFAULT : 'default', + libcomps.PACKAGE_TYPE_OPTIONAL : 'optional', + libcomps.PACKAGE_TYPE_CONDITIONAL : 'conditional', + libcomps.PACKAGE_TYPE_MANDATORY : 'mandatory', + libcomps.PACKAGE_TYPE_UNKNOWN : 'unknown', + } + for group in comps.groups: + print("Group: %s (%s)" % (group.id, group.name)) + session.groupListAdd( + tag, group.id, force=force, display_name=group.name, + is_default=bool(group.default), + uservisible=bool(group.uservisible), + description=group.desc, + langonly=group.lang_only, + biarchonly=bool(group.biarchonly)) + for pkg in group.packages: + pkgopts = {'type' : ptypes[pkg.type], + 'basearchonly' : bool(pkg.basearchonly), + } + if pkg.type == libcomps.PACKAGE_TYPE_CONDITIONAL: + pkgopts['requires'] = pkg.requires + for k in pkgopts.keys(): + if six.PY2 and isinstance(pkgopts[k], unicode): + pkgopts[k] = str(pkgopts[k]) + s_opts = ', '.join(["'%s': %r" % (k, pkgopts[k]) for k in sorted(list(pkgopts.keys()))]) + print(" Package: %s: {%s}" % (pkg.name, s_opts)) + session.groupPackageListAdd(tag, group.id, pkg.name, force=force, **pkgopts) + # libcomps does not support group dependencies + # libcomps does not support metapkgs + + +def _import_comps_alt(session, filename, tag, options): + """Import comps data using yum.comps module""" + print('WARN: yum.comps does not support the biarchonly of group and basearchonly of package') + comps = yumcomps.Comps() + comps.add(filename) + force = options.force + for group in comps.groups: + print("Group: %(groupid)s (%(name)s)" % vars(group)) + session.groupListAdd(tag, group.groupid, force=force, display_name=group.name, + is_default=bool(group.default), + uservisible=bool(group.user_visible), + description=group.description, + langonly=group.langonly) + #yum.comps does not support the biarchonly field + for ptype, pdata in [('mandatory', group.mandatory_packages), + ('default', group.default_packages), + ('optional', group.optional_packages), + ('conditional', group.conditional_packages)]: + for pkg in pdata: + #yum.comps does not support basearchonly + pkgopts = {'type' : ptype} + if ptype == 'conditional': + pkgopts['requires'] = pdata[pkg] + for k in pkgopts.keys(): + if six.PY2 and isinstance(pkgopts[k], unicode): + pkgopts[k] = str(pkgopts[k]) + s_opts = ', '.join(["'%s': %r" % (k, pkgopts[k]) for k in sorted(list(pkgopts.keys()))]) + print(" Package: %s: {%s}" % (pkg, s_opts)) + session.groupPackageListAdd(tag, group.groupid, pkg, force=force, **pkgopts) + #yum.comps does not support group dependencies + #yum.comps does not support metapkgs + + +def handle_import_sig(options, session, args): + "[admin] Import signatures into the database" + usage = _("usage: %prog import-sig [options] package [package...]") + usage += _("\n(Specify the --help global option for a list of other help options)") + parser = OptionParser(usage=usage) + parser.add_option("--with-unsigned", action="store_true", + help=_("Also import unsigned sig headers")) + parser.add_option("--write", action="store_true", + help=_("Also write the signed copies")) + parser.add_option("--test", action="store_true", + help=_("Test mode -- don't actually import")) + (options, args) = parser.parse_args(args) + if len(args) < 1: + parser.error(_("At least one package must be specified")) + assert False # pragma: no cover + for path in args: + if not os.path.exists(path): + parser.error(_("No such file: %s") % path) + activate_session(session) + for path in args: + data = koji.get_header_fields(path, ('name','version','release','arch','siggpg','sigpgp','sourcepackage')) + if data['sourcepackage']: + data['arch'] = 'src' + sigkey = data['siggpg'] + if not sigkey: + sigkey = data['sigpgp'] + if not sigkey: + sigkey = "" + if not options.with_unsigned: + print(_("Skipping unsigned package: %s" % path)) + continue + else: + sigkey = koji.get_sigpacket_key_id(sigkey) + del data['siggpg'] + del data['sigpgp'] + rinfo = session.getRPM(data) + if not rinfo: + print("No such rpm in system: %(name)s-%(version)s-%(release)s.%(arch)s" % data) + continue + if rinfo.get('external_repo_id'): + print("Skipping external rpm: %(name)s-%(version)s-%(release)s.%(arch)s@%(external_repo_name)s" % rinfo) + continue + sighdr = koji.rip_rpm_sighdr(path) + previous = session.queryRPMSigs(rpm_id=rinfo['id'], sigkey=sigkey) + assert len(previous) <= 1 + if previous: + sighash = md5_constructor(sighdr).hexdigest() + if previous[0]['sighash'] == sighash: + print(_("Signature already imported: %s") % path) + continue + else: + print(_("Warning: signature mismatch: %s") % path) + print(_(" The system already has a signature for this rpm with key %s") % sigkey) + print(_(" The two signature headers are not the same")) + continue + print(_("Importing signature [key %s] from %s...") % (sigkey, path)) + if not options.test: + session.addRPMSig(rinfo['id'], base64.encodestring(sighdr)) + print(_("Writing signed copy")) + if not options.test: + session.writeSignedRPM(rinfo['id'], sigkey) + + +def handle_write_signed_rpm(options, session, args): + "[admin] Write signed RPMs to disk" + usage = _("usage: %prog write-signed-rpm [options] n-v-r [n-v-r...]") + usage += _("\n(Specify the --help global option for a list of other help options)") + parser = OptionParser(usage=usage) + parser.add_option("--all", action="store_true", help=_("Write out all RPMs signed with this key")) + parser.add_option("--buildid", help=_("Specify a build id rather than an n-v-r")) + (options, args) = parser.parse_args(args) + if len(args) < 1: + parser.error(_("A signature key must be specified")) + assert False # pragma: no cover + if len(args) < 2 and not (options.all or options.buildid): + parser.error(_("At least one RPM must be specified")) + assert False # pragma: no cover + key = args.pop(0) + activate_session(session) + if options.all: + rpms = session.queryRPMSigs(sigkey=key) + rpms = [session.getRPM(r['rpm_id']) for r in rpms] + elif options.buildid: + rpms = session.listRPMs(int(options.buildid)) + else: + rpms = [] + bad = [] + for nvra in args: + try: + koji.parse_NVRA(nvra) + rinfo = session.getRPM(nvra, strict=True) + if rinfo: + rpms.append(rinfo) + except koji.GenericError: + bad.append(nvra) + # for historical reasons, we also accept nvrs + for nvr in bad: + build = session.getBuild(nvr) + if not build: + raise koji.GenericError("No such rpm or build: %s" % nvr) + rpms.extend(session.listRPMs(buildID=build['id'])) + for i, rpminfo in enumerate(rpms): + nvra = "%(name)s-%(version)s-%(release)s.%(arch)s" % rpminfo + print("[%d/%d] %s" % (i+1, len(rpms), nvra)) + session.writeSignedRPM(rpminfo['id'], key) + + +def handle_prune_signed_copies(options, session, args): + "[admin] Prune signed copies" + usage = _("usage: %prog prune-sigs [options]") + usage += _("\n(Specify the --help global option for a list of other help options)") + parser = OptionParser(usage=usage) + parser.add_option("-n", "--test", action="store_true", help=_("Test mode")) + parser.add_option("-v", "--verbose", action="store_true", help=_("Be more verbose")) + parser.add_option("--days", type="int", default=5, help=_("Timeout before clearing")) + parser.add_option("-p", "--package", "--pkg", help=_("Limit to a single package")) + parser.add_option("-b", "--build", help=_("Limit to a single build")) + parser.add_option("-i", "--ignore-tag", action="append", default=[], + help=_("Ignore these tags when considering whether a build is/was latest")) + parser.add_option("--ignore-tag-file", + help=_("File to read tag ignore patterns from")) + parser.add_option("-r", "--protect-tag", action="append", default=[], + help=_("Do not prune signed copies from matching tags")) + parser.add_option("--protect-tag-file", + help=_("File to read tag protect patterns from")) + parser.add_option("--trashcan-tag", default="trashcan", help=_("Specify trashcan tag")) + parser.add_option("--debug", action="store_true", help=_("Show debugging output")) + (options, args) = parser.parse_args(args) + # different ideas/modes + # 1) remove all signed copies of builds that are not latest for some tag + # 2) remove signed copies when a 'better' signature is available + # 3) for a specified tag, remove all signed copies that are not latest (w/ inheritance) + # 4) for a specified tag, remove all signed copies (no inheritance) + # (but skip builds that are multiply tagged) + + #for now, we're just implementing mode #1 + #(with the modification that we check to see if the build was latest within + #the last N days) + if options.ignore_tag_file: + fo = open(options.ignore_tag_file) + options.ignore_tag.extend([line.strip() for line in fo.readlines()]) + fo.close() + if options.protect_tag_file: + fo = open(options.protect_tag_file) + options.protect_tag.extend([line.strip() for line in fo.readlines()]) + fo.close() + if options.debug: + options.verbose = True + cutoff_ts = time.time() - options.days * 24 * 3600 + if options.debug: + print("Cutoff date: %s" % time.asctime(time.localtime(cutoff_ts))) + if not options.build: + if options.verbose: + print("Getting builds...") + qopts = {'state' : koji.BUILD_STATES['COMPLETE']} + if options.package: + pkginfo = session.getPackage(options.package) + qopts['packageID'] = pkginfo['id'] + builds = [(b['nvr'], b) for b in session.listBuilds(**qopts)] + if options.verbose: + print("...got %i builds" % len(builds)) + builds.sort() + else: + #single build + binfo = session.getBuild(options.build) + if not binfo: + parser.error('No such build: %s' % options.build) + assert False # pragma: no cover + builds = [("%(name)s-%(version)s-%(release)s" % binfo, binfo)] + total_files = 0 + total_space = 0 + def _histline(event_id, x): + if event_id == x['revoke_event']: + ts = x['revoke_ts'] + fmt = "Untagged %(name)s-%(version)s-%(release)s from %(tag_name)s" + elif event_id == x['create_event']: + ts = x['create_ts'] + fmt = "Tagged %(name)s-%(version)s-%(release)s with %(tag_name)s" + if x['active']: + fmt += " [still active]" + else: + raise koji.GenericError("unknown event: (%r, %r)" % (event_id, x)) + time_str = time.asctime(time.localtime(ts)) + return "%s: %s" % (time_str, fmt % x) + for nvr, binfo in builds: + #listBuilds returns slightly different data than normal + if 'id' not in binfo: + binfo['id'] = binfo['build_id'] + if 'name' not in binfo: + binfo['name'] = binfo['package_name'] + if options.debug: + print("DEBUG: %s" % nvr) + #see how recently this build was latest for a tag + is_latest = False + is_protected = False + last_latest = None + tags = {} + for entry in session.tagHistory(build=binfo['id']): + #we used tagHistory rather than listTags so we can consider tags + #that the build was recently untagged from + tags.setdefault(entry['tag_name'], 1) + if options.debug: + print("Tags: %s" % list(tags.keys())) + for tag_name in tags: + if tag_name == options.trashcan_tag: + if options.debug: + print("Ignoring trashcan tag for build %s" % nvr) + continue + ignore_tag = False + for pattern in options.ignore_tag: + if fnmatch.fnmatch(tag_name, pattern): + if options.debug: + print("Ignoring tag %s for build %s" % (tag_name, nvr)) + ignore_tag = True + break + if ignore_tag: + continue + #in order to determine how recently this build was latest, we have + #to look at the tagging history. + hist = session.tagHistory(tag=tag_name, package=binfo['name']) + if not hist: + #really shouldn't happen + raise koji.GenericError("No history found for %s in %s" % (nvr, tag_name)) + timeline = [] + for x in hist: + #note that for revoked entries, we're effectively splitting them into + #two parts: creation and revocation. + timeline.append((x['create_event'], 1, x)) + #at the same event, revokes happen first + if x['revoke_event'] is not None: + timeline.append((x['revoke_event'], 0, x)) + timeline.sort() + #find most recent creation entry for our build and crop there + latest_ts = None + for i in range(len(timeline)-1, -1, -1): + #searching in reverse cronological order + event_id, is_create, entry = timeline[i] + if entry['build_id'] == binfo['id'] and is_create: + latest_ts = event_id + break + if not latest_ts: + #really shouldn't happen + raise koji.GenericError("No creation event found for %s in %s" % (nvr, tag_name)) + our_entry = entry + if options.debug: + print(_histline(event_id, our_entry)) + #now go through the events since most recent creation entry + timeline = timeline[i+1:] + if not timeline: + is_latest = True + if options.debug: + print("%s is latest in tag %s" % (nvr, tag_name)) + break + #before we go any further, is this a protected tag? + protect_tag = False + for pattern in options.protect_tag: + if fnmatch.fnmatch(tag_name, pattern): + protect_tag = True + break + if protect_tag: + # we use the same time limit as for the latest calculation + # if this build was in this tag within that limit, then we will + # not prune its signed copies + if our_entry['revoke_event'] is None: + #we're still tagged with a protected tag + if options.debug: + print("Build %s has protected tag %s" % (nvr, tag_name)) + is_protected = True + break + elif our_entry['revoke_ts'] > cutoff_ts: + #we were still tagged here sometime before the cutoff + if options.debug: + print("Build %s had protected tag %s until %s" \ + % (nvr, tag_name, time.asctime(time.localtime(our_entry['revoke_ts'])))) + is_protected = True + break + replaced_ts = None + revoke_ts = None + others = {} + for event_id, is_create, entry in timeline: + #So two things can knock this build from the title of latest: + # - it could be untagged (entry revoked) + # - another build could become latest (replaced) + #Note however that if the superceding entry is itself revoked, then + #our build could become latest again + if options.debug: + print(_histline(event_id, entry)) + if entry['build_id'] == binfo['id']: + if is_create: + #shouldn't happen + raise koji.GenericError("Duplicate creation event found for %s in %s" \ + % (nvr, tag_name)) + else: + #we've been revoked + revoke_ts = entry['revoke_ts'] + break + else: + if is_create: + #this build has become latest + replaced_ts = entry['create_ts'] + if entry['active']: + #this entry not revoked yet, so we're done for this tag + break + #since this entry is revoked later, our build might eventually be + #uncovered, so we have to keep looking + others[entry['build_id']] = 1 + else: + #other build revoked + #see if our build has resurfaced + if entry['build_id'] in others: + del others[entry['build_id']] + if replaced_ts is not None and not others: + #we've become latest again + #(note: we're not revoked yet because that triggers a break above) + replaced_ts = None + latest_ts = entry['revoke_ts'] + if last_latest is None: + timestamps = [] + else: + timestamps = [last_latest] + if revoke_ts is None: + if replaced_ts is None: + #turns out we are still latest + is_latest = True + if options.debug: + print("%s is latest (again) in tag %s" % (nvr, tag_name)) + break + else: + #replaced (but not revoked) + timestamps.append(replaced_ts) + if options.debug: + print("tag %s: %s not latest (replaced %s)" \ + % (tag_name, nvr, time.asctime(time.localtime(replaced_ts)))) + elif replaced_ts is None: + #revoked but not replaced + timestamps.append(revoke_ts) + if options.debug: + print("tag %s: %s not latest (revoked %s)" \ + % (tag_name, nvr, time.asctime(time.localtime(revoke_ts)))) + else: + #revoked AND replaced + timestamps.append(min(revoke_ts, replaced_ts)) + if options.debug: + print("tag %s: %s not latest (revoked %s, replaced %s)" \ + % (tag_name, nvr, time.asctime(time.localtime(revoke_ts)), + time.asctime(time.localtime(replaced_ts)))) + last_latest = max(timestamps) + if last_latest > cutoff_ts: + if options.debug: + print("%s was latest past the cutoff" % nvr) + is_latest = True + break + if is_latest: + continue + if is_protected: + continue + #not latest anywhere since cutoff, so we can remove all signed copies + rpms = session.listRPMs(buildID=binfo['id']) + session.multicall = True + for rpminfo in rpms: + session.queryRPMSigs(rpm_id=rpminfo['id']) + by_sig = {} + #index by sig + for rpminfo, [sigs] in zip(rpms, session.multiCall()): + for sig in sigs: + sigkey = sig['sigkey'] + by_sig.setdefault(sigkey, []).append(rpminfo) + builddir = koji.pathinfo.build(binfo) + build_files = 0 + build_space = 0 + if not by_sig and options.debug: + print("(build has no signatures)") + for sigkey, rpms in six.iteritems(by_sig): + mycount = 0 + archdirs = {} + sigdirs = {} + for rpminfo in rpms: + signedpath = "%s/%s" % (builddir, koji.pathinfo.signed(rpminfo, sigkey)) + try: + st = os.lstat(signedpath) + except OSError: + continue + if not stat.S_ISREG(st.st_mode): + #warn about this + print("Skipping %s. Not a regular file" % signedpath) + continue + if st.st_mtime > cutoff_ts: + print("Skipping %s. File newer than cutoff" % signedpath) + continue + if options.test: + print("Would have unlinked: %s" % signedpath) + else: + if options.verbose: + print("Unlinking: %s" % signedpath) + try: + os.unlink(signedpath) + except OSError as e: + print("Error removing %s: %s" % (signedpath, e)) + print("This script needs write access to %s" % koji.BASEDIR) + continue + mycount +=1 + build_files += 1 + build_space += st.st_size + #XXX - this makes some layout assumptions, but + # pathinfo doesn't report what we need + mydir = os.path.dirname(signedpath) + archdirs[mydir] = 1 + sigdirs[os.path.dirname(mydir)] = 1 + for dir in archdirs: + if options.test: + print("Would have removed dir: %s" % dir) + else: + if options.verbose: + print("Removing dir: %s" % dir) + try: + os.rmdir(dir) + except OSError as e: + print("Error removing %s: %s" % (signedpath, e)) + if len(sigdirs) == 1: + dir = list(sigdirs.keys())[0] + if options.test: + print("Would have removed dir: %s" % dir) + else: + if options.verbose: + print("Removing dir: %s" % dir) + try: + os.rmdir(dir) + except OSError as e: + print("Error removing %s: %s" % (signedpath, e)) + elif len(sigdirs) > 1: + print("Warning: more than one signature dir for %s: %r" % (sigkey, sigdirs)) + if build_files: + total_files += build_files + total_space += build_space + if options.verbose: + print("Build: %s, Removed %i signed copies (%i bytes). Total: %i/%i" \ + % (nvr, build_files, build_space, total_files, total_space)) + elif options.debug and by_sig: + print("(build has no signed copies)") + print("--- Grand Totals ---") + print("Files: %i" % total_files) + print("Bytes: %i" % total_space) + +def handle_set_build_volume(options, session, args): + "[admin] Move a build to a different volume" + usage = _("usage: %prog set-build-volume volume n-v-r [n-v-r ...]") + usage += _("\n(Specify the --help global option for a list of other help options)") + parser = OptionParser(usage=usage) + parser.add_option("-v", "--verbose", action="store_true", help=_("Be verbose")) + (options, args) = parser.parse_args(args) + if len(args) < 2: + parser.error("You must provide a volume and at least one build") + volinfo = session.getVolume(args[0]) + if not volinfo: + print("No such volume: %s" % args[0]) + return 1 + activate_session(session) + builds = [] + for nvr in args[1:]: + binfo = session.getBuild(nvr) + if not binfo: + print("No such build: %s" % nvr) + elif binfo['volume_id'] == volinfo['id']: + print("Build %s already on volume %s" %(nvr, volinfo['name'])) + else: + builds.append(binfo) + if not builds: + print("No builds to move") + return 1 + for binfo in builds: + session.changeBuildVolume(binfo['id'], volinfo['id']) + if options.verbose: + print("%s: %s -> %s" % (binfo['nvr'], binfo['volume_name'], volinfo['name'])) + +def handle_add_volume(options, session, args): + "[admin] Add a new storage volume" + usage = _("usage: %prog add-volume volume-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) != 1: + parser.error("Command requires exactly one volume-name.") + name = args[0] + volinfo = session.getVolume(name) + if volinfo: + print("Volume %s already exists" % name) + return 1 + activate_session(session) + volinfo = session.addVolume(name) + print("Added volume %(name)s with id %(id)i" % volinfo) + +def handle_list_volumes(options, session, args): + "[info] List storage volumes" + usage = _("usage: %prog list-volumes") + usage += _("\n(Specify the --help global option for a list of other help options)") + parser = OptionParser(usage=usage) + (options, args) = parser.parse_args(args) + for volinfo in session.listVolumes(): + print(volinfo['name']) + +def handle_list_permissions(options, session, args): + "[info] List user permissions" + usage = _("usage: %prog list-permissions [options]") + usage += _("\n(Specify the --help global option for a list of other help options)") + parser = OptionParser(usage=usage) + parser.add_option("--user", help=_("List permissions for the given user")) + parser.add_option("--mine", action="store_true", help=_("List your permissions")) + (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) + if options.user: + user = session.getUser(options.user) + if not user: + print("User %s does not exist" % options.user) + return 1 + perms = session.getUserPerms(user['id']) + elif options.mine: + perms = session.getPerms() + else: + perms = [p['name'] for p in session.getAllPerms()] + for perm in perms: + print(perm) + +def handle_add_user(options, session, args): + "[admin] Add a user" + usage = _("usage: %prog add-user username [options]") + usage += _("\n(Specify the --help global option for a list of other help options)") + parser = OptionParser(usage=usage) + parser.add_option("--principal", help=_("The Kerberos principal for this user")) + parser.add_option("--disable", help=_("Prohibit logins by this user"), action="store_true") + (options, args) = parser.parse_args(args) + if len(args) < 1: + parser.error(_("You must specify the username of the user to add")) + elif len(args) > 1: + parser.error(_("This command only accepts one argument (username)")) + username = args[0] + if options.disable: + status = koji.USER_STATUS['BLOCKED'] + else: + status = koji.USER_STATUS['NORMAL'] + activate_session(session) + user_id = session.createUser(username, status=status, krb_principal=options.principal) + print("Added user %s (%i)" % (username, user_id)) + +def handle_enable_user(options, session, args): + "[admin] Enable logins by a user" + usage = _("usage: %prog enable-user username") + 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) < 1: + parser.error(_("You must specify the username of the user to enable")) + elif len(args) > 1: + parser.error(_("This command only accepts one argument (username)")) + username = args[0] + activate_session(session) + session.enableUser(username) + +def handle_disable_user(options, session, args): + "[admin] Disable logins by a user" + usage = _("usage: %prog disable-user username") + 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) < 1: + parser.error(_("You must specify the username of the user to disable")) + elif len(args) > 1: + parser.error(_("This command only accepts one argument (username)")) + username = args[0] + activate_session(session) + session.disableUser(username) + +def handle_list_signed(options, session, args): + "[admin] List signed copies of rpms" + usage = _("usage: %prog list-signed [options]") + usage += _("\n(Specify the --help global option for a list of other help options)") + parser = OptionParser(usage=usage) + parser.add_option("--debug", action="store_true") + parser.add_option("--key", help=_("Only list RPMs signed with this key")) + parser.add_option("--build", help=_("Only list RPMs from this build")) + parser.add_option("--rpm", help=_("Only list signed copies for this RPM")) + parser.add_option("--tag", help=_("Only list RPMs within this tag")) + (options, args) = parser.parse_args(args) + activate_session(session) + qopts = {} + build_idx = {} + rpm_idx = {} + if options.key: + qopts['sigkey'] = options.key + if options.rpm: + rinfo = session.getRPM(options.rpm) + rpm_idx[rinfo['id']] = rinfo + if rinfo is None: + parser.error(_("No such RPM: %s") % options.rpm) + if rinfo.get('external_repo_id'): + print("External rpm: %(name)s-%(version)s-%(release)s.%(arch)s@%(external_repo_name)s" % rinfo) + return 1 + qopts['rpm_id'] = rinfo['id'] + if options.build: + binfo = session.getBuild(options.build) + build_idx[binfo['id']] = binfo + if binfo is None: + parser.error(_("No such build: %s") % options.rpm) + sigs = [] + rpms = session.listRPMs(buildID=binfo['id']) + for rinfo in rpms: + rpm_idx[rinfo['id']] = rinfo + sigs += session.queryRPMSigs(rpm_id=rinfo['id'], **qopts) + else: + sigs = session.queryRPMSigs(**qopts) + if options.tag: + print("getting tag listing") + rpms, builds = session.listTaggedRPMS(options.tag, inherit=False, latest=False) + print("got tag listing") + tagged = {} + for binfo in builds: + build_idx.setdefault(binfo['id'], binfo) + for rinfo in rpms: + rpm_idx.setdefault(rinfo['id'], rinfo) + tagged[rinfo['id']] = 1 + #Now figure out which sig entries actually have live copies + for sig in sigs: + rpm_id = sig['rpm_id'] + sigkey = sig['sigkey'] + if options.tag: + if tagged.get(rpm_id) is None: + continue + rinfo = rpm_idx.get(rpm_id) + if not rinfo: + rinfo = session.getRPM(rpm_id) + rpm_idx[rinfo['id']] = rinfo + binfo = build_idx.get(rinfo['build_id']) + if not binfo: + binfo = session.getBuild(rinfo['build_id']) + build_idx[binfo['id']] = binfo + binfo['name'] = binfo['package_name'] + builddir = koji.pathinfo.build(binfo) + signedpath = "%s/%s" % (builddir, koji.pathinfo.signed(rinfo, sigkey)) + if not os.path.exists(signedpath): + if options.debug: + print("No copy: %s" % signedpath) + continue + print(signedpath) + +def handle_import_in_place(options, session, args): + "[admin] Import RPMs that are already in place" + usage = _("usage: %prog import-in-place [options] package [package...]") + 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) < 1: + parser.error(_("At least one package must be specified")) + assert False # pragma: no cover + activate_session(session) + for nvr in args: + data = koji.parse_NVR(nvr) + sys.stdout.write(_("importing %s... ") % nvr) + try: + session.importBuildInPlace(data) + except koji.GenericError as e: + print(_("\nError importing: %s" % str(e).splitlines()[-1])) + sys.stdout.flush() + else: + print(_("done")) + sys.stdout.flush() + +def handle_import_archive(options, session, args): + "[admin] Import an archive file and associate it with a build" + usage = _("usage: %prog import-archive build-id|n-v-r /path/to/archive...") + usage += _("\n(Specify the --help global option for a list of other help options)") + parser = OptionParser(usage=usage) + parser.add_option("--noprogress", action="store_true", + help=_("Do not display progress of the upload")) + parser.add_option("--create-build", action="store_true", help=_("Auto-create builds as needed")) + parser.add_option("--link", action="store_true", help=_("Attempt to hardlink instead of uploading")) + parser.add_option("--type", help=_("The type of archive being imported. Currently supported types: maven, win, image")) + parser.add_option("--type-info", help=_("Type-specific information to associate with the archives. " + "For Maven archives this should be a local path to a .pom file. " + "For Windows archives this should be relpath:platforms[:flags])) " + "Images need an arch")) + (suboptions, args) = parser.parse_args(args) + + if not len(args) > 1: + parser.error(_("You must specify a build ID or N-V-R and an archive to import")) + assert False # pragma: no cover + + activate_session(session) + + if not suboptions.type: + parser.error(_("You must specify an archive type")) + assert False # pragma: no cover + if suboptions.type == 'maven': + if not (session.hasPerm('maven-import') or session.hasPerm('admin')): + parser.error(_("This action requires the maven-import privilege")) + assert False # pragma: no cover + if not suboptions.type_info: + parser.error(_("--type-info must point to a .pom file when importing Maven archives")) + assert False # pragma: no cover + pom_info = koji.parse_pom(suboptions.type_info) + maven_info = koji.pom_to_maven_info(pom_info) + suboptions.type_info = maven_info + elif suboptions.type == 'win': + if not (session.hasPerm('win-import') or session.hasPerm('admin')): + parser.error(_("This action requires the win-import privilege")) + assert False # pragma: no cover + if not suboptions.type_info: + parser.error(_("--type-info must be specified")) + assert False # pragma: no cover + type_info = suboptions.type_info.split(':', 2) + if len(type_info) < 2: + parser.error(_("--type-info must be in relpath:platforms[:flags] format")) + win_info = {'relpath': type_info[0], 'platforms': type_info[1].split()} + if len(type_info) > 2: + win_info['flags'] = type_info[2].split() + else: + win_info['flags'] = [] + suboptions.type_info = win_info + elif suboptions.type == 'image': + if not (session.hasPerm('image-import') or session.hasPerm('admin')): + parser.error(_("This action requires the image-import privilege")) + assert False # pragma: no cover + if not suboptions.type_info: + parser.error(_("--type-info must be specified")) + assert False # pragma: no cover + image_info = {'arch': suboptions.type_info} + suboptions.type_info = image_info + else: + parser.error(_("Unsupported archive type: %s" % suboptions.type)) + assert False # pragma: no cover + + buildinfo = session.getBuild(arg_filter(args[0])) + if not buildinfo: + if not suboptions.create_build: + parser.error(_("No such build: %s") % args[0]) + buildinfo = koji.parse_NVR(args[0]) + if buildinfo['epoch'] == '': + buildinfo['epoch'] = None + else: + buildinfo['epoch'] = int(buildinfo['epoch']) + if suboptions.type == 'maven': + # --type-info should point to a local .pom file + session.createMavenBuild(buildinfo, suboptions.type_info) + elif suboptions.type == 'win': + # We're importing, so we don't know what platform the build + # was run on. Use "import" as a placeholder. + session.createWinBuild(buildinfo, {'platform': 'import'}) + elif suboptions.type == 'image': + # --type-info should have an arch of the image + session.createImageBuild(buildinfo) + else: + # should get caught above + assert False # pragma: no cover + + for filepath in args[1:]: + filename = os.path.basename(filepath) + print("Uploading archive: %s" % filename) + serverdir = _unique_path('cli-import') + if _running_in_bg() or suboptions.noprogress: + callback = None + else: + callback = _progress_callback + if suboptions.link: + linked_upload(filepath, serverdir) + else: + session.uploadWrapper(filepath, serverdir, callback=callback) + print('') + serverpath = "%s/%s" % (serverdir, filename) + session.importArchive(serverpath, buildinfo, suboptions.type, suboptions.type_info) + print("Imported: %s" % filename) + +def handle_grant_permission(options, session, args): + "[admin] Grant a permission to a user" + usage = _("usage: %prog grant-permission [ ...]") + usage += _("\n(Specify the --help global option for a list of other help options)") + parser = OptionParser(usage=usage) + parser.add_option("--new", action="store_true", help=_("Create a new permission")) + (options, args) = parser.parse_args(args) + if len(args) < 2: + parser.error(_("Please specify a permission and at least one user")) + assert False # pragma: no cover + activate_session(session) + perm = args[0] + names = args[1:] + users = [] + for n in names: + user = session.getUser(n) + if user is None: + parser.error(_("No such user: %s" % n)) + assert False # pragma: no cover + users.append(user) + kwargs = {} + if options.new: + kwargs['create'] = True + for user in users: + session.grantPermission(user['name'], perm, **kwargs) + +def handle_revoke_permission(options, session, args): + "[admin] Revoke a permission from a user" + usage = _("usage: %prog revoke-permission [ ...]") + 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 permission and at least one user")) + assert False # pragma: no cover + activate_session(session) + perm = args[0] + names = args[1:] + users = [] + for n in names: + user = session.getUser(n) + if user is None: + parser.error(_("No such user: %s" % n)) + assert False # pragma: no cover + users.append(user) + for user in users: + session.revokePermission(user['name'], perm) + + +def handle_grant_cg_access(options, session, args): + "[admin] Add a user to a content generator" + usage = _("usage: %prog grant-cg-access ") + usage += _("\n(Specify the --help global option for a list of other help options)") + parser = OptionParser(usage=usage) + parser.add_option("--new", action="store_true", help=_("Create a new content generator")) + (options, args) = parser.parse_args(args) + if len(args) != 2: + parser.error(_("Please specify a user and content generator")) + assert False # pragma: no cover + activate_session(session) + user = args[0] + cg = args[1] + uinfo = session.getUser(user) + if uinfo is None: + parser.error(_("No such user: %s" % user)) + assert False # pragma: no cover + kwargs = {} + if options.new: + kwargs['create'] = True + session.grantCGAccess(uinfo['name'], cg, **kwargs) + + +def handle_revoke_cg_access(options, session, args): + "[admin] Remove a user from a content generator" + usage = _("usage: %prog revoke-cg-access ") + 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 user and content generator")) + assert False # pragma: no cover + activate_session(session) + user = args[0] + cg = args[1] + uinfo = session.getUser(user) + if uinfo is None: + parser.error(_("No such user: %s" % user)) + assert False # pragma: no cover + session.revokeCGAccess(uinfo['name'], cg) + + +def anon_handle_latest_build(options, session, args): + "[info] Print the latest builds for a tag" + usage = _("usage: %prog latest-build [options] tag package [package...]") + usage += _("\n(Specify the --help global option for a list of other help options)") + parser = OptionParser(usage=usage) + parser.add_option("--arch", help=_("List all of the latest packages for this arch")) + parser.add_option("--all", action="store_true", help=_("List all of the latest packages for this tag")) + parser.add_option("--quiet", action="store_true", help=_("Do not print the header information"), default=options.quiet) + parser.add_option("--paths", action="store_true", help=_("Show the file paths")) + parser.add_option("--type", help=_("Show builds of the given type only. Currently supported types: maven")) + (options, args) = parser.parse_args(args) + if len(args) == 0: + parser.error(_("A tag name must be specified")) + assert False # pragma: no cover + activate_session(session) + if options.all: + if len(args) > 1: + parser.error(_("A package name may not be combined with --all")) + assert False # pragma: no cover + # Set None as the package argument + args.append(None) + else: + if len(args) < 2: + parser.error(_("A tag name and package name must be specified")) + assert False # pragma: no cover + pathinfo = koji.PathInfo() + + for pkg in args[1:]: + if options.arch: + rpms, builds = session.getLatestRPMS(args[0], package=pkg, arch=options.arch) + builds_hash = dict([(x['build_id'], x) for x in builds]) + data = rpms + if options.paths: + for x in data: + z = x.copy() + x['name'] = builds_hash[x['build_id']]['package_name'] + x['path'] = os.path.join(pathinfo.build(x), pathinfo.rpm(z)) + fmt = "%(path)s" + else: + fmt = "%(name)s-%(version)s-%(release)s.%(arch)s" + else: + kwargs = {'package': pkg} + if options.type: + kwargs['type'] = options.type + data = session.getLatestBuilds(args[0], **kwargs) + if options.paths: + if options.type == 'maven': + for x in data: + x['path'] = pathinfo.mavenbuild(x) + fmt = "%(path)-40s %(tag_name)-20s %(maven_group_id)-20s %(maven_artifact_id)-20s %(owner_name)s" + else: + for x in data: + x['path'] = pathinfo.build(x) + fmt = "%(path)-40s %(tag_name)-20s %(owner_name)s" + else: + if options.type == 'maven': + fmt = "%(nvr)-40s %(tag_name)-20s %(maven_group_id)-20s %(maven_artifact_id)-20s %(owner_name)s" + else: + fmt = "%(nvr)-40s %(tag_name)-20s %(owner_name)s" + if not options.quiet: + if options.type == 'maven': + print("%-40s %-20s %-20s %-20s %s" % ("Build", "Tag", "Group Id", "Artifact Id", "Built by")) + print("%s %s %s %s %s" % ("-"*40, "-"*20, "-"*20, "-"*20, "-"*16)) + else: + print("%-40s %-20s %s" % ("Build","Tag","Built by")) + print("%s %s %s" % ("-"*40, "-"*20, "-"*16)) + options.quiet = True + + output = [ fmt % x for x in data] + output.sort() + for line in output: + print(line) + + +def anon_handle_list_api(options, session, args): + "[info] Print the list of XML-RPC APIs" + usage = _("usage: %prog list-api [options]") + 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) != 0: + parser.error(_("This command takes no arguments")) + assert False # pragma: no cover + activate_session(session) + tmplist = [(x['name'], x) for x in session._listapi()] + tmplist.sort() + funcs = [x[1] for x in tmplist] + for x in funcs: + if 'argdesc' in x: + args = x['argdesc'] + elif x['args']: + # older servers may not provide argdesc + expanded = [] + for arg in x['args']: + if type(arg) is str: + expanded.append(arg) + else: + expanded.append('%s=%s' % (arg[0], arg[1])) + args = "(%s)" % ", ".join(expanded) + else: + args = "()" + print('%s%s' % (x['name'], args)) + if x['doc']: + print(" description: %s" % x['doc']) + +def anon_handle_list_tagged(options, session, args): + "[info] List the builds or rpms in a tag" + usage = _("usage: %prog list-tagged [options] tag [package]") + usage += _("\n(Specify the --help global option for a list of other help options)") + parser = OptionParser(usage=usage) + parser.add_option("--arch", help=_("List rpms for this arch")) + parser.add_option("--rpms", action="store_true", help=_("Show rpms instead of builds")) + parser.add_option("--inherit", action="store_true", help=_("Follow inheritance")) + parser.add_option("--latest", action="store_true", help=_("Only show the latest builds/rpms")) + parser.add_option("--latest-n", type='int', metavar="N", help=_("Only show the latest N builds/rpms")) + parser.add_option("--quiet", action="store_true", help=_("Do not print the header information"), default=options.quiet) + parser.add_option("--paths", action="store_true", help=_("Show the file paths")) + parser.add_option("--sigs", action="store_true", help=_("Show signatures")) + parser.add_option("--type", help=_("Show builds of the given type only. Currently supported types: maven, win, image")) + parser.add_option("--event", type='int', metavar="EVENT#", help=_("query at event")) + parser.add_option("--ts", type='int', metavar="TIMESTAMP", help=_("query at timestamp")) + parser.add_option("--repo", type='int', metavar="REPO#", help=_("query at event for a repo")) + (options, args) = parser.parse_args(args) + if len(args) == 0: + parser.error(_("A tag name must be specified")) + assert False # pragma: no cover + elif len(args) > 2: + parser.error(_("Only one package name may be specified")) + assert False # pragma: no cover + activate_session(session) + pathinfo = koji.PathInfo() + package = None + if len(args) > 1: + package = args[1] + tag = args[0] + opts = {} + for key in ('latest','inherit'): + opts[key] = getattr(options, key) + if options.latest_n is not None: + opts['latest'] = options.latest_n + if package: + opts['package'] = package + if options.arch: + options.rpms = True + opts['arch'] = options.arch + if options.sigs: + opts['rpmsigs'] = True + options.rpms = True + if options.type: + opts['type'] = options.type + event = koji.util.eventFromOpts(session, options) + if event: + opts['event'] = event['id'] + event['timestr'] = time.asctime(time.localtime(event['ts'])) + if not options.quiet: + print("Querying at event %(id)i (%(timestr)s)" % event) + + if options.rpms: + rpms, builds = session.listTaggedRPMS(tag, **opts) + data = rpms + if options.paths: + build_idx = dict([(b['id'],b) for b in builds]) + for rinfo in data: + build = build_idx[rinfo['build_id']] + builddir = pathinfo.build(build) + if options.sigs: + sigkey = rinfo['sigkey'] + signedpath = os.path.join(builddir, pathinfo.signed(rinfo, sigkey)) + if os.path.exists(signedpath): + rinfo['path'] = signedpath + else: + rinfo['path'] = os.path.join(builddir, pathinfo.rpm(rinfo)) + fmt = "%(path)s" + data = [x for x in data if 'path' in x] + else: + fmt = "%(name)s-%(version)s-%(release)s.%(arch)s" + if options.sigs: + fmt = "%(sigkey)s " + fmt + else: + data = session.listTagged(tag, **opts) + if options.paths: + if options.type == 'maven': + for x in data: + x['path'] = pathinfo.mavenbuild(x) + fmt = "%(path)-40s %(tag_name)-20s %(maven_group_id)-20s %(maven_artifact_id)-20s %(owner_name)s" + else: + for x in data: + x['path'] = pathinfo.build(x) + fmt = "%(path)-40s %(tag_name)-20s %(owner_name)s" + else: + if options.type == 'maven': + fmt = "%(nvr)-40s %(tag_name)-20s %(maven_group_id)-20s %(maven_artifact_id)-20s %(owner_name)s" + else: + fmt = "%(nvr)-40s %(tag_name)-20s %(owner_name)s" + if not options.quiet: + if options.type == 'maven': + print("%-40s %-20s %-20s %-20s %s" % ("Build", "Tag", "Group Id", "Artifact Id", "Built by")) + print("%s %s %s %s %s" % ("-"*40, "-"*20, "-"*20, "-"*20, "-"*16)) + else: + print("%-40s %-20s %s" % ("Build","Tag","Built by")) + print("%s %s %s" % ("-"*40, "-"*20, "-"*16)) + + output = [ fmt % x for x in data] + output.sort() + for line in output: + print(line) + +def anon_handle_list_buildroot(options, session, args): + "[info] List the rpms used in or built in a buildroot" + usage = _("usage: %prog list-buildroot [options] buildroot-id") + usage += _("\n(Specify the --help global option for a list of other help options)") + parser = OptionParser(usage=usage) + parser.add_option("--paths", action="store_true", help=_("Show the file paths")) + parser.add_option("--built", action="store_true", help=_("Show the built rpms")) + parser.add_option("--verbose", "-v", action="store_true", help=_("Show more information")) + (options, args) = parser.parse_args(args) + if len(args) != 1: + parser.error(_("Incorrect number of arguments")) + assert False # pragma: no cover + activate_session(session) + buildrootID = int(args[0]) + opts = {} + if options.built: + opts['buildrootID'] = buildrootID + else: + opts['componentBuildrootID'] = buildrootID + data = session.listRPMs(**opts) + + fmt = "%(nvr)s.%(arch)s" + order = [(fmt % x, x) for x in data] + order.sort() + for nvra, rinfo in order: + if options.verbose and rinfo.get('is_update'): + print(nvra, "[update]") + else: + print(nvra) + +def anon_handle_list_untagged(options, session, args): + "[info] List untagged builds" + usage = _("usage: %prog list-untagged [options] [package]") + usage += _("\n(Specify the --help global option for a list of other help options)") + parser = OptionParser(usage=usage) + parser.add_option("--paths", action="store_true", help=_("Show the file paths")) + parser.add_option("--show-references", action="store_true", help=_("Show build references")) + (options, args) = parser.parse_args(args) + if len(args) > 1: + parser.error(_("Only one package name may be specified")) + assert False # pragma: no cover + activate_session(session) + package = None + if len(args) > 0: + package = args[0] + opts = {} + if package: + opts['name'] = package + pathinfo = koji.PathInfo() + + data = session.untaggedBuilds(**opts) + if options.show_references: + print("(Showing build references)") + refs = {} + refs2 = {} #reverse map + for x in session.buildMap(): + refs.setdefault(x['used'], {}).setdefault(x['built'], 1) + refs2.setdefault(x['built'], {}).setdefault(x['used'], 1) + #XXX - need to ignore refs to unreferenced builds + for x in data: + builds = refs.get(x['id']) + if builds: + x['refs'] = "%s" % builds + else: + x['refs'] = '' + #data = [x for x in data if x['id'] not in refs)] + if options.paths: + for x in data: + x['path'] = pathinfo.build(x) + fmt = "%(path)s" + else: + fmt = "%(name)s-%(version)s-%(release)s" + if options.show_references: + fmt = fmt + " %(refs)s" + + output = [ fmt % x for x in data] + output.sort() + for line in output: + print(line) + +def print_group_list_req_group(group): + print(" @%(name)s [%(tag_name)s]" % group) + +def print_group_list_req_package(pkg): + print(" %(package)s: %(basearchonly)s, %(type)s [%(tag_name)s]" % pkg) + +def anon_handle_list_groups(options, session, args): + "[info] Print the group listings" + usage = _("usage: %prog list-groups [options] [group]") + usage += _("\n(Specify the --help global option for a list of other help options)") + parser = OptionParser(usage=usage) + parser.add_option("--event", type='int', metavar="EVENT#", help=_("query at event")) + parser.add_option("--ts", type='int', metavar="TIMESTAMP", help=_("query at timestamp")) + parser.add_option("--repo", type='int', metavar="REPO#", help=_("query at event for a repo")) + (options, args) = parser.parse_args(args) + if len(args) < 1 or len(args) > 2: + parser.error(_("Incorrect number of arguments")) + assert False # pragma: no cover + opts = {} + activate_session(session) + event = koji.util.eventFromOpts(session, options) + if event: + opts['event'] = event['id'] + event['timestr'] = time.asctime(time.localtime(event['ts'])) + print("Querying at event %(id)i (%(timestr)s)" % event) + tags = dict([(x['id'], x['name']) for x in session.listTags()]) + tmp_list = [(x['name'], x) for x in session.getTagGroups(args[0], **opts)] + tmp_list.sort() + groups = [x[1] for x in tmp_list] + for group in groups: + if len(args) > 1 and group['name'] != args[1]: + continue + print("%s [%s]" % (group['name'], tags.get(group['tag_id'], group['tag_id']))) + groups = [(x['name'], x) for x in group['grouplist']] + groups.sort() + for x in [x[1] for x in groups]: + x['tag_name'] = tags.get(x['tag_id'], x['tag_id']) + print_group_list_req_group(x) + pkgs = [(x['package'], x) for x in group['packagelist']] + pkgs.sort() + for x in [x[1] for x in pkgs]: + x['tag_name'] = tags.get(x['tag_id'], x['tag_id']) + print_group_list_req_package(x) + +def handle_add_group_pkg(options, session, args): + "[admin] Add a package to a group's package listing" + usage = _("usage: %prog add-group-pkg [options] [...]") + 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) < 3: + parser.error(_("You must specify a tag name, group name, and one or more package names")) + assert False # pragma: no cover + tag = args[0] + group = args[1] + activate_session(session) + for pkg in args[2:]: + session.groupPackageListAdd(tag, group, pkg) + +def handle_block_group_pkg(options, session, args): + "[admin] Block a package from a group's package listing" + usage = _("usage: %prog block-group-pkg [options] [...]") + 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) < 3: + parser.error(_("You must specify a tag name, group name, and one or more package names")) + assert False # pragma: no cover + tag = args[0] + group = args[1] + activate_session(session) + for pkg in args[2:]: + session.groupPackageListBlock(tag, group, pkg) + +def handle_unblock_group_pkg(options, session, args): + "[admin] Unblock a package from a group's package listing" + usage = _("usage: %prog unblock-group-pkg [options] [...]") + 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) < 3: + parser.error(_("You must specify a tag name, group name, and one or more package names")) + assert False # pragma: no cover + tag = args[0] + group = args[1] + activate_session(session) + for pkg in args[2:]: + session.groupPackageListUnblock(tag, group, pkg) + +def handle_add_group_req(options, session, args): + "[admin] Add a group to a group's required list" + usage = _("usage: %prog add-group-req [options] ") + 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) != 3: + parser.error(_("You must specify a tag name and two group names")) + assert False # pragma: no cover + tag = args[0] + group = args[1] + req = args[2] + activate_session(session) + session.groupReqListAdd(tag, group, req) + +def handle_block_group_req(options, session, args): + "[admin] Block a group's requirement listing" + usage = _("usage: %prog block-group-req [options] ") + 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) != 3: + parser.error(_("You must specify a tag name and two group names")) + assert False # pragma: no cover + tag = args[0] + group = args[1] + req = args[2] + activate_session(session) + session.groupReqListBlock(tag, group, req) + +def handle_unblock_group_req(options, session, args): + "[admin] Unblock a group's requirement listing" + usage = _("usage: %prog unblock-group-req [options] ") + 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) != 3: + parser.error(_("You must specify a tag name and two group names")) + assert False # pragma: no cover + tag = args[0] + group = args[1] + req = args[2] + activate_session(session) + session.groupReqListUnblock(tag, group, req) + +def anon_handle_list_channels(options, session, args): + "[info] Print channels listing" + usage = _("usage: %prog list-channels") + usage += _("\n(Specify the --help global option for a list of other help options)") + parser = OptionParser(usage=usage) + parser.add_option("--quiet", action="store_true", help=_("Do not print header information"), default=options.quiet) + (options, args) = parser.parse_args(args) + activate_session(session) + channels = session.listChannels() + session.multicall = True + for channel in channels: + session.listHosts(channelID=channel['id']) + for channel, hosts in zip(channels, session.multiCall()): + channel['hosts'] = len(hosts[0]) + if not options.quiet: + print('Channel Hosts') + for channel in channels: + print("%(name)-15s %(hosts) 5d" % channel) + +def anon_handle_list_hosts(options, session, args): + "[info] Print the host listing" + usage = _("usage: %prog list-hosts [options]") + usage += _("\n(Specify the --help global option for a list of other help options)") + parser = OptionParser(usage=usage) + parser.add_option("--arch", action="append", default=[], help=_("Specify an architecture")) + parser.add_option("--channel", help=_("Specify a channel")) + parser.add_option("--ready", action="store_true", help=_("Limit to ready hosts")) + parser.add_option("--not-ready", action="store_false", dest="ready", help=_("Limit to not ready hosts")) + parser.add_option("--enabled", action="store_true", help=_("Limit to enabled hosts")) + parser.add_option("--not-enabled", action="store_false", dest="enabled", help=_("Limit to not enabled hosts")) + parser.add_option("--quiet", action="store_true", help=_("Do not print header information"), default=options.quiet) + (options, args) = parser.parse_args(args) + opts = {} + activate_session(session) + if options.arch: + opts['arches'] = options.arch + if options.channel: + channel = session.getChannel(options.channel) + if not channel: + parser.error(_('Unknown channel: %s' % options.channel)) + assert False # pragma: no cover + opts['channelID'] = channel['id'] + if options.ready is not None: + opts['ready'] = options.ready + if options.enabled is not None: + opts['enabled'] = options.enabled + tmp_list = [(x['name'], x) for x in session.listHosts(**opts)] + tmp_list.sort() + hosts = [x[1] for x in tmp_list] + + def yesno(x): + if x: return 'Y' + else: return 'N' + + # pull in the last update using multicall to speed it up a bit + session.multicall = True + for host in hosts: + session.getLastHostUpdate(host['id']) + updateList = session.multiCall() + + for host, [update] in zip(hosts, updateList): + if update is None: + host['update'] = '-' + else: + host['update'] = update.split('.')[0] + host['enabled'] = yesno(host['enabled']) + host['ready'] = yesno(host['ready']) + host['arches'] = ','.join(host['arches'].split()) + + if not options.quiet: + print("Hostname Enb Rdy Load/Cap Arches Last Update") + for host in hosts: + print("%(name)-28s %(enabled)-3s %(ready)-3s %(task_load)4.1f/%(capacity)-3.1f %(arches)-16s %(update)s" % host) + +def anon_handle_list_pkgs(options, session, args): + "[info] Print the package listing for tag or for owner" + usage = _("usage: %prog list-pkgs [options]") + usage += _("\n(Specify the --help global option for a list of other help options)") + parser = OptionParser(usage=usage) + parser.add_option("--owner", help=_("Specify owner")) + parser.add_option("--tag", help=_("Specify tag")) + parser.add_option("--package", help=_("Specify package")) + parser.add_option("--quiet", action="store_true", help=_("Do not print header information"), default=options.quiet) + parser.add_option("--noinherit", action="store_true", help=_("Don't follow inheritance")) + parser.add_option("--show-blocked", action="store_true", help=_("Show blocked packages")) + parser.add_option("--show-dups", action="store_true", help=_("Show superseded owners")) + parser.add_option("--event", type='int', metavar="EVENT#", help=_("query at event")) + parser.add_option("--ts", type='int', metavar="TIMESTAMP", help=_("query at timestamp")) + parser.add_option("--repo", type='int', metavar="REPO#", help=_("query at event for a repo")) + (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) + opts = {} + if options.owner: + user = session.getUser(options.owner) + if user is None: + parser.error(_("Invalid user")) + assert False # pragma: no cover + opts['userID'] = user['id'] + if options.tag: + tag = session.getTag(options.tag) + if tag is None: + parser.error(_("Invalid tag")) + assert False # pragma: no cover + opts['tagID'] = tag['id'] + if options.package: + opts['pkgID'] = options.package + allpkgs = False + if not opts: + # no limiting clauses were specified + allpkgs = True + opts['inherited'] = not options.noinherit + #hiding dups only makes sense if we're querying a tag + if options.tag: + opts['with_dups'] = options.show_dups + else: + opts['with_dups'] = True + event = koji.util.eventFromOpts(session, options) + if event: + opts['event'] = event['id'] + event['timestr'] = time.asctime(time.localtime(event['ts'])) + print("Querying at event %(id)i (%(timestr)s)" % event) + data = session.listPackages(**opts) + if not data: + print("(no matching packages)") + return 1 + if not options.quiet: + if allpkgs: + print("Package") + print('-'*23) + else: + print("%-23s %-23s %-16s %-15s" % ('Package','Tag','Extra Arches','Owner')) + print("%s %s %s %s" % ('-'*23,'-'*23,'-'*16,'-'*15)) + for pkg in data: + if allpkgs: + print(pkg['package_name']) + else: + if not options.show_blocked and pkg.get('blocked',False): + continue + if 'tag_id' in pkg: + if pkg['extra_arches'] is None: + pkg['extra_arches'] = "" + fmt = "%(package_name)-23s %(tag_name)-23s %(extra_arches)-16s %(owner_name)-15s" + if pkg.get('blocked',False): + fmt += " [BLOCKED]" + else: + fmt = "%(package_name)s" + print(fmt % pkg) + +def anon_handle_rpminfo(options, session, args): + "[info] Print basic information about an RPM" + usage = _("usage: %prog rpminfo [options] [ ...]") + usage += _("\n(Specify the --help global option for a list of other help options)") + parser = OptionParser(usage=usage) + parser.add_option("--buildroots", action="store_true", help=_("show buildroots the rpm was used in")) + (options, args) = parser.parse_args(args) + if len(args) < 1: + parser.error(_("Please specify an RPM")) + assert False # pragma: no cover + activate_session(session) + for rpm in args: + info = session.getRPM(rpm) + if info is None: + print("No such rpm: %s\n" % rpm) + continue + if info['epoch'] is None: + info['epoch'] = "" + else: + info['epoch'] = str(info['epoch']) + ":" + if not info.get('external_repo_id', 0): + buildinfo = session.getBuild(info['build_id']) + buildinfo['name'] = buildinfo['package_name'] + buildinfo['arch'] = 'src' + if buildinfo['epoch'] is None: + buildinfo['epoch'] = "" + else: + buildinfo['epoch'] = str(buildinfo['epoch']) + ":" + print("RPM: %(epoch)s%(name)s-%(version)s-%(release)s.%(arch)s [%(id)d]" % info) + if info.get('external_repo_id'): + repo = session.getExternalRepo(info['external_repo_id']) + print("External Repository: %(name)s [%(id)i]" % repo) + print("External Repository url: %(url)s" % repo) + else: + print("RPM Path: %s" % os.path.join(koji.pathinfo.build(buildinfo), koji.pathinfo.rpm(info))) + print("SRPM: %(epoch)s%(name)s-%(version)s-%(release)s [%(id)d]" % buildinfo) + print("SRPM Path: %s" % os.path.join(koji.pathinfo.build(buildinfo), koji.pathinfo.rpm(buildinfo))) + print("Built: %s" % time.strftime('%a, %d %b %Y %H:%M:%S %Z', time.localtime(info['buildtime']))) + print("SIGMD5: %(payloadhash)s" % info) + print("Size: %(size)s" % info) + if not info.get('external_repo_id', 0): + print("Build ID: %(build_id)s" % info) + if info['buildroot_id'] is None: + print("No buildroot data available") + else: + br_info = session.getBuildroot(info['buildroot_id']) + if br_info['br_type'] == koji.BR_TYPES['STANDARD']: + print("Buildroot: %(id)i (tag %(tag_name)s, arch %(arch)s, repo %(repo_id)i)" % br_info) + print("Build Host: %(host_name)s" % br_info) + print("Build Task: %(task_id)i" % br_info) + else: + print("Content generator: %(cg_name)s" % br_info) + print("Buildroot: %(id)i" % br_info) + print("Build Host OS: %(host_os)s (%(host_arch)s)" % br_info) + if info.get('extra'): + print("Extra: %(extra)r" % info) + if options.buildroots: + br_list = session.listBuildroots(rpmID=info['id'], queryOpts={'order':'buildroot.id'}) + print("Used in %i buildroots:" % len(br_list)) + if len(br_list): + print(" %8s %-28s %-8s %-29s" % ('id','build tag','arch','build host')) + print(" %s %s %s %s" % ('-'*8, '-'*28, '-'*8, '-'*29)) + for br_info in br_list: + print(" %(id)8i %(tag_name)-28s %(arch)-8s %(host_name)-29s" % br_info) + + +def anon_handle_buildinfo(options, session, args): + "[info] Print basic information about a build" + usage = _("usage: %prog buildinfo [options] [ ...]") + usage += _("\n(Specify the --help global option for a list of other help options)") + parser = OptionParser(usage=usage) + parser.add_option("--changelog", action="store_true", help=_("Show the changelog for the build")) + (options, args) = parser.parse_args(args) + if len(args) < 1: + parser.error(_("Please specify a build")) + assert False # pragma: no cover + activate_session(session) + for build in args: + if build.isdigit(): + build = int(build) + info = session.getBuild(build) + if info is None: + print("No such build: %s\n" % build) + continue + task = None + if info['task_id']: + task = session.getTaskInfo(info['task_id'], request=True) + taglist = [] + for tag in session.listTags(build): + taglist.append(tag['name']) + info['arch'] = 'src' + info['state'] = koji.BUILD_STATES[info['state']] + print("BUILD: %(name)s-%(version)s-%(release)s [%(id)d]" % info) + print("State: %(state)s" % info) + print("Built by: %(owner_name)s" % info) + source = info.get('source') + if source is not None: + print("Source: %s" % source) + if 'volume_name' in info: + print("Volume: %(volume_name)s" % info) + if task: + print("Task: %s %s" % (task['id'], koji.taskLabel(task))) + else: + print("Task: none") + print("Finished: %s" % koji.formatTimeLong(info['completion_time'])) + maven_info = session.getMavenBuild(info['id']) + if maven_info: + print("Maven groupId: %s" % maven_info['group_id']) + print("Maven artifactId: %s" % maven_info['artifact_id']) + print("Maven version: %s" % maven_info['version']) + win_info = session.getWinBuild(info['id']) + if win_info: + print("Windows build platform: %s" % win_info['platform']) + print("Tags: %s" % ' '.join(taglist)) + if info.get('extra'): + print("Extra: %(extra)r" % info) + archives_seen = {} + maven_archives = session.listArchives(buildID=info['id'], type='maven') + if maven_archives: + print("Maven archives:") + for archive in maven_archives: + archives_seen.setdefault(archive['id'], 1) + print(os.path.join(koji.pathinfo.mavenbuild(info), koji.pathinfo.mavenfile(archive))) + win_archives = session.listArchives(buildID=info['id'], type='win') + if win_archives: + print("Windows archives:") + for archive in win_archives: + archives_seen.setdefault(archive['id'], 1) + print(os.path.join(koji.pathinfo.winbuild(info), koji.pathinfo.winfile(archive))) + rpms = session.listRPMs(buildID=info['id']) + image_info = session.getImageBuild(info['id']) + img_archives = session.listArchives(buildID=info['id'], type='image') + if img_archives: + print('Image archives:') + for archive in img_archives: + archives_seen.setdefault(archive['id'], 1) + print(os.path.join(koji.pathinfo.imagebuild(info), archive['filename'])) + archive_idx = {} + for archive in session.listArchives(buildID=info['id']): + if archive['id'] in archives_seen: + continue + archive_idx.setdefault(archive['btype'], []).append(archive) + for btype in archive_idx: + archives = archive_idx[btype] + print('%s Archives:' % btype.capitalize()) + for archive in archives: + print(os.path.join(koji.pathinfo.typedir(info, btype), archive['filename'])) + if rpms: + print("RPMs:") + for rpm in rpms: + print(os.path.join(koji.pathinfo.build(info), koji.pathinfo.rpm(rpm))) + if options.changelog: + changelog = session.getChangelogEntries(info['id']) + if changelog: + print("Changelog:") + print(koji.util.formatChangelog(changelog)) + +def anon_handle_hostinfo(options, session, args): + "[info] Print basic information about a host" + usage = _("usage: %prog hostinfo [options] [ ...]") + 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) < 1: + parser.error(_("Please specify a host")) + assert False # pragma: no cover + activate_session(session) + for host in args: + if host.isdigit(): + host = int(host) + info = session.getHost(host) + if info is None: + print("No such host: %s\n" % host) + continue + print("Name: %(name)s" % info) + print("ID: %(id)d" % info) + print("Arches: %(arches)s" % info) + print("Capacity: %(capacity)s" % info) + print("Task Load: %(task_load).2f" % info) + if info['description']: + description = info['description'].splitlines() + print("Description: %s" % description[0]) + for line in description[1:]: + print("%s%s" % (" "*13, line)) + else: + print("Description:") + if info['comment']: + comment = info['comment'].splitlines() + print("Comment: %s" % comment[0]) + for line in comment[1:]: + print("%s%s" % (" "*9, line)) + else: + print("Comment:") + print("Enabled: %s" % (info['enabled'] and 'yes' or 'no')) + print("Ready: %s" % (info['ready'] and 'yes' or 'no')) + update = session.getLastHostUpdate(info['id']) + if update is None: + update = "never" + else: + update = update[:update.find('.')] + print("Last Update: %s" % update) + print("Channels: %s" % ' '.join([c['name'] for c in session.listChannels(hostID=info['id'])])) + print("Active Buildroots:") + states = {0:"INIT", 1:"WAITING", 2:"BUILDING"} + rows = [('NAME', 'STATE', 'CREATION TIME')] + for s in range(0,3): + for b in session.listBuildroots(hostID=info['id'], state=s): + rows.append((("%s-%s-%s" % (b['tag_name'], b['id'], b['repo_id'])), states[s], + b['create_event_time'][:b['create_event_time'].find('.')])) + if len(rows) > 1: + for row in rows: + print("%-50s %-10s %-20s" % row) + else: + print("None") + +def handle_clone_tag(options, session, args): + "[admin] Duplicate the contents of one tag onto another tag" + usage = _("usage: %prog clone-tag [options] ") + usage += _("\n(Specify the --help global option for a list of other help options)") + parser = OptionParser(usage=usage) + parser.add_option('--config', action='store_true', + help=_("Copy config from the source to the dest tag")) + parser.add_option('--groups', action='store_true', + help=_("Copy group information")) + parser.add_option('--pkgs', action='store_true', + help=_("Copy package list from the source to the dest tag")) + parser.add_option('--builds', action='store_true', + help=_("Tag builds into the dest tag")) + parser.add_option('--all', action='store_true', + help=_("The same as --config --groups --pkgs --builds")) + parser.add_option('--latest-only', action='store_true', + help=_("Tag only the latest build of each package")) + parser.add_option('--inherit-builds', action='store_true', + help=_("Include all builds inherited into the source tag into " + "the dest tag")) + parser.add_option('--ts', type='int', + help=_('Clone tag at a specific timestamp')) + parser.add_option('--event', type='int', + help=_('Clone tag at a specific event')) + parser.add_option('--repo', type='int', + help=_('Clone tag at a specific repo event')) + parser.add_option("-v","--verbose", action="store_true", + help=_("show changes")) + parser.add_option("-f","--force", action="store_true", + help=_("override tag locks if necessary")) + parser.add_option("-n","--test", action="store_true", help=_("test mode")) + (options, args) = parser.parse_args(args) + + if len(args) != 2: + parser.error(_("This command takes two arguments: ")) + assert False # pragma: no cover + activate_session(session) + + if not session.hasPerm('admin') and not options.test: + print(_("This action requires admin privileges")) + return + + if args[0] == args[1]: + sys.stdout.write('Source and destination tags must be different.\n') + return + + if options.all: + options.config = options.groups = options.pkgs = options.builds = True + + event = koji.util.eventFromOpts(session, options) or {} + if event: + event['timestr'] = time.asctime(time.localtime(event['ts'])) + print(_("Cloning at event %(id)i (%(timestr)s)") % event) + + # store tags. + srctag = session.getTag(args[0]) + dsttag = session.getTag(args[1]) + if not srctag: + sys.stdout.write("Unknown src-tag: %s\n" % args[0]) + return + if (srctag['locked'] and not options.force) or (dsttag and dsttag['locked'] and not options.force): + print(_("Error: You are attempting to clone from or to a tag which is locked.")) + print(_("Please use --force if this is what you really want to do.")) + return + + # init debug lists. + chgpkglist=[] + chgbldlist=[] + chggrplist=[] + # case of brand new dst-tag. + if not dsttag: + if not options.config: + print(_('Cannot create tag without specifying --config')) + return + # create a new tag, copy srctag header. + if not options.test: + session.createTag(args[1], parent=None, arches=srctag['arches'], + perm=srctag['perm_id'], + locked=srctag['locked'], + maven_support=srctag['maven_support'], + maven_include_all=srctag['maven_include_all']) + newtag = session.getTag(args[1]) # store the new tag, need its asigned id. + # get pkglist of src-tag, including inherited packages. + if options.pkgs: + srcpkgs = session.listPackages(tagID=srctag['id'], inherited=True, event=event.get('id')) + srcpkgs.sort(key = lambda x: x['package_name']) + if not options.test: + session.multicall = True + for pkgs in srcpkgs: + # for each package add one entry in the new tag. + chgpkglist.append(('[new]',pkgs['package_name'],pkgs['blocked'],pkgs['owner_name'],pkgs['tag_name'])) + if not options.test: + # add packages. + session.packageListAdd(newtag['name'],pkgs['package_name'], + owner=pkgs['owner_name'],block=pkgs['blocked'], + extra_arches=pkgs['extra_arches']) + if not options.test: + session.multiCall() + if options.builds: + # get --all latest builds from src tag + builds = session.listTagged(srctag['id'], event=event.get('id'), + inherit=options.inherit_builds, + latest=options.latest_only) + if not options.test: + session.multicall = True + for build in builds: + build['name'] = build['package_name'] # add missing 'name' field. + chgbldlist.append(('[new]', build['package_name'], + build['nvr'], koji.BUILD_STATES[build['state']], + build['owner_name'], build['tag_name'])) + # copy latest builds into new tag + if not options.test: + session.tagBuildBypass(newtag['name'], build, force=options.force) + if not options.test: + session.multiCall() + if options.groups: + # Copy the group data + srcgroups = session.getTagGroups(srctag['name'], event=event.get('id')) + if not options.test: + session.multicall = True + for group in srcgroups: + if not options.test: + session.groupListAdd(newtag['name'], group['name']) + for pkg in group['packagelist']: + if not options.test: + session.groupPackageListAdd(newtag['name'], group['name'], + pkg['package'], block=pkg['blocked']) + chggrplist.append(('[new]', pkg['package'], group['name'])) + if not options.test: + session.multiCall() + # case of existing dst-tag. + if dsttag: + # get fresh list of packages & builds into maps. + srcpkgs = {} + dstpkgs = {} + srclblds = {} + dstlblds = {} + srcgroups = {} + dstgroups = {} + if options.pkgs: + for pkg in session.listPackages(tagID=srctag['id'], inherited=True, event=event.get('id')): + srcpkgs[pkg['package_name']] = pkg + for pkg in session.listPackages(tagID=dsttag['id'], inherited=True): + dstpkgs[pkg['package_name']] = pkg + if options.builds: + src_builds = session.listTagged(srctag['id'], + event=event.get('id'), + inherit=options.inherit_builds, + latest=options.latest_only) + for build in src_builds: + srclblds[build['nvr']] = build + for build in session.getLatestBuilds(dsttag['name']): + dstlblds[build['nvr']] = build + if options.groups: + for group in session.getTagGroups(srctag['name'], event=event.get('id')): + srcgroups[group['name']] = group + for group in session.getTagGroups(dsttag['name']): + dstgroups[group['name']] = group + #construct to-do lists. + paddlist = [] # list containing new packages to be added from src tag + for (package_name, pkg) in six.iteritems(srcpkgs): + if package_name not in dstpkgs: + paddlist.append(pkg) + paddlist.sort(key = lambda x: x['package_name']) + pdellist = [] # list containing packages no more present in dst tag + for (package_name, pkg) in six.iteritems(dstpkgs): + if package_name not in srcpkgs: + pdellist.append(pkg) + pdellist.sort(key = lambda x: x['package_name']) + baddlist = [] # list containing new builds to be added from src tag + for (nvr, lbld) in six.iteritems(srclblds): + if nvr not in dstlblds: + baddlist.append(lbld) + baddlist.sort(key = lambda x: x['package_name']) + bdellist = [] # list containing new builds to be removed from src tag + for (nvr, lbld) in six.iteritems(dstlblds): + if nvr not in srclblds: + bdellist.append(lbld) + bdellist.sort(key = lambda x: x['package_name']) + gaddlist = [] # list containing new groups to be added from src tag + for (grpname, group) in six.iteritems(srcgroups): + if grpname not in dstgroups: + gaddlist.append(group) + gdellist = [] # list containing groups to be removed from src tag + for (grpname, group) in six.iteritems(dstgroups): + if grpname not in srcgroups: + gdellist.append(group) + grpchanges = {} # dict of changes to make in shared groups + for (grpname, group) in six.iteritems(srcgroups): + if grpname in dstgroups: + grpchanges[grpname] = {'adds':[], 'dels':[]} + # Store whether group is inherited or not + grpchanges[grpname]['inherited'] = False + if group['tag_id'] != dsttag['id']: + grpchanges[grpname]['inherited'] = True + srcgrppkglist=[] + dstgrppkglist=[] + for pkg in group['packagelist']: + srcgrppkglist.append(pkg['package']) + for pkg in dstgroups[grpname]['packagelist']: + dstgrppkglist.append(pkg['package']) + for pkg in srcgrppkglist: + if not pkg in dstgrppkglist: + grpchanges[grpname]['adds'].append(pkg) + for pkg in dstgrppkglist: + if not pkg in srcgrppkglist: + grpchanges[grpname]['dels'].append(pkg) + # ADD new packages. + if not options.test: + session.multicall = True + for pkg in paddlist: + chgpkglist.append(('[add]', pkg['package_name'], + pkg['blocked'], pkg['owner_name'], + pkg['tag_name'])) + if not options.test: + session.packageListAdd(dsttag['name'], pkg['package_name'], + owner=pkg['owner_name'], + block=pkg['blocked'], + extra_arches=pkg['extra_arches']) + if not options.test: + session.multiCall() + # ADD builds. + if not options.test: + session.multicall = True + for build in baddlist: + build['name'] = build['package_name'] # add missing 'name' field. + chgbldlist.append(('[add]', build['package_name'], build['nvr'], + koji.BUILD_STATES[build['state']], + build['owner_name'], build['tag_name'])) + # copy latest builds into new tag. + if not options.test: + session.tagBuildBypass(dsttag['name'], build, force=options.force) + if not options.test: + session.multiCall() + # ADD groups. + if not options.test: + session.multicall = True + for group in gaddlist: + if not options.test: + session.groupListAdd(dsttag['name'], group['name'], force=options.force) + for pkg in group['packagelist']: + if not options.test: + session.groupPackageListAdd(dsttag['name'], group['name'], pkg['package'], force=options.force) + chggrplist.append(('[new]', pkg['package'], group['name'])) + if not options.test: + session.multiCall() + # ADD group pkgs. + if not options.test: + session.multicall = True + for group in grpchanges: + for pkg in grpchanges[group]['adds']: + chggrplist.append(('[new]', pkg, group)) + if not options.test: + session.groupPackageListAdd(dsttag['name'], group, pkg, force=options.force) + if not options.test: + session.multiCall() + # DEL builds. + if not options.test: + session.multicall = True + for build in bdellist: + # dont delete an inherited build. + if build['tag_name'] == dsttag['name']: + build['name'] = build['package_name'] # add missing 'name' field. + chgbldlist.append(('[del]', build['package_name'], build['nvr'], + koji.BUILD_STATES[build['state']], + build['owner_name'], build['tag_name'])) + # go on del builds from new tag. + if not options.test: + session.untagBuildBypass(dsttag['name'], build, force=options.force) + if not options.test: + session.multiCall() + # DEL packages. + if not options.test: + session.multicall = True + for pkg in pdellist: + # delete only non-inherited packages. + if build['tag_name'] == dsttag['name']: + # check if package have owned builds inside. + builds = session.listTagged(dsttag['name'], package=pkg['package_name'], inherit=False) + #remove all its builds first if there are any. + for build in builds: + build['name'] = build['package_name'] #add missing 'name' field. + chgbldlist.append(('[del]', build['package_name'], build['nvr'], + koji.BUILD_STATES[build['state']], + build['owner_name'], build['tag_name'])) + # so delete latest build(s) from new tag. + if not options.test: + session.untagBuildBypass(dsttag['name'], build, force=options.force) + # now safe to remove package itselfm since we resolved its builds. + chgpkglist.append(('[del]', pkg['package_name'], pkg['blocked'], + pkg['owner_name'], pkg['tag_name'])) + if not options.test: + session.packageListRemove(dsttag['name'], pkg['package_name'], force=False) + # mark as blocked inherited packages. + if build['tag_name'] != dsttag['name']: + chgpkglist.append(('[blk]', pkg['package_name'], pkg['blocked'], + pkg['owner_name'], pkg['tag_name'])) + if not options.test: + session.packageListBlock(dsttag['name'], pkg['package_name']) + if not options.test: + session.multiCall() + # DEL groups. + if not options.test: + session.multicall = True + for group in gdellist: + # Only delete a group that isn't inherited + if group['tag_id'] == dsttag['id']: + if not options.test: + session.groupListRemove(dsttag['name'], group['name'], force=options.force) + for pkg in group['packagelist']: + chggrplist.append(('[del]', pkg['package'], group['name'])) + # mark as blocked inherited groups. + else: + if not options.test: + session.groupListBlock(dsttag['name'], group['name']) + for pkg in group['packagelist']: + chggrplist.append(('[blk]', pkg['package'], group['name'])) + if not options.test: + session.multiCall() + # DEL group pkgs. + if not options.test: + session.multicall = True + for group in grpchanges: + for pkg in grpchanges[group]['dels']: + # Only delete a group that isn't inherited + if not grpchanges[group]['inherited']: + chggrplist.append(('[del]', pkg, group)) + if not options.test: + session.groupPackageListRemove(dsttag['name'], group, pkg, force=options.force) + else: + chggrplist.append(('[blk]', pkg, group)) + if not options.test: + session.groupPackageListBlock(dsttag['name'], group, pkg) + if not options.test: + session.multiCall() + # print final list of actions. + if options.verbose: + pfmt=' %-7s %-28s %-10s %-10s %-10s\n' + bfmt=' %-7s %-28s %-40s %-10s %-10s %-10s\n' + gfmt=' %-7s %-28s %-28s\n' + sys.stdout.write('\nList of changes:\n\n') + sys.stdout.write(pfmt % ('Action', 'Package', 'Blocked', 'Owner', 'From Tag')) + sys.stdout.write(pfmt % ('-'*7, '-'*28, '-'*10, '-'*10, '-'*10)) + for changes in chgpkglist: + sys.stdout.write(pfmt % changes) + sys.stdout.write('\n') + sys.stdout.write(bfmt % ('Action', 'From/To Package', 'Latest Build(s)', 'State', 'Owner', 'From Tag')) + sys.stdout.write(bfmt % ('-'*7, '-'*28, '-'*40, '-'*10, '-'*10, '-'*10)) + for changes in chgbldlist: + sys.stdout.write(bfmt % changes) + sys.stdout.write('\n') + sys.stdout.write(gfmt % ('Action', 'Package', 'Group')) + sys.stdout.write(gfmt % ('-'*7, '-'*28, '-'*28)) + for changes in chggrplist: + sys.stdout.write(gfmt % changes) + + +def handle_add_target(options, session, args): + "[admin] Create a new build target" + usage = _("usage: %prog add-target name build-tag ") + 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 target name, a build tag, and destination tag")) + assert False # pragma: no cover + elif len(args) > 3: + parser.error(_("Incorrect number of arguments")) + assert False # pragma: no cover + name = args[0] + build_tag = args[1] + if len(args) > 2: + dest_tag = args[2] + else: + #most targets have the same name as their destination + dest_tag = name + activate_session(session) + if not session.hasPerm('admin'): + print("This action requires admin privileges") + return 1 + + chkbuildtag = session.getTag(build_tag) + chkdesttag = session.getTag(dest_tag) + if not chkbuildtag: + print("Build tag does not exist: %s" % build_tag) + return 1 + if not chkbuildtag.get("arches", None): + print("Build tag has no arches: %s" % build_tag) + return 1 + if not chkdesttag: + print("Destination tag does not exist: %s" % dest_tag) + return 1 + + session.createBuildTarget(name, build_tag, dest_tag) + +def handle_edit_target(options, session, args): + "[admin] Set the name, build_tag, and/or dest_tag of an existing build target to new values" + usage = _("usage: %prog edit-target [options] name") + usage += _("\n(Specify the --help global option for a list of other help options)") + parser = OptionParser(usage=usage) + parser.add_option("--rename", help=_("Specify new name for target")) + parser.add_option("--build-tag", help=_("Specify a different build tag")) + parser.add_option("--dest-tag", help=_("Specify a different destination tag")) + + (options, args) = parser.parse_args(args) + + if len(args) != 1: + parser.error(_("Please specify a build target")) + assert False # pragma: no cover + activate_session(session) + + if not session.hasPerm('admin'): + print("This action requires admin privileges") + return + + targetInfo = session.getBuildTarget(args[0]) + if targetInfo == None: + raise koji.GenericError("No build target with the name or id '%s'" % args[0]) + + targetInfo['orig_name'] = targetInfo['name'] + + if options.rename: + targetInfo['name'] = options.rename + if options.build_tag: + targetInfo['build_tag_name'] = options.build_tag + chkbuildtag = session.getTag(options.build_tag) + if not chkbuildtag: + print("Build tag does not exist: %s" % options.build_tag) + return 1 + if not chkbuildtag.get("arches", None): + print("Build tag has no arches: %s" % options.build_tag) + return 1 + if options.dest_tag: + chkdesttag = session.getTag(options.dest_tag) + if not chkdesttag: + print("Destination tag does not exist: %s" % options.dest_tag) + return 1 + targetInfo['dest_tag_name'] = options.dest_tag + + session.editBuildTarget(targetInfo['orig_name'], targetInfo['name'], targetInfo['build_tag_name'], targetInfo['dest_tag_name']) + +def handle_remove_target(options, session, args): + "[admin] Remove a build target" + usage = _("usage: %prog remove-target [options] 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) != 1: + parser.error(_("Please specify a build target to remove")) + assert False # pragma: no cover + activate_session(session) + + if not session.hasPerm('admin'): + print("This action requires admin privileges") + return + + target = args[0] + target_info = session.getBuildTarget(target) + if not target_info: + print("Build target %s does not exist" % target) + return 1 + + session.deleteBuildTarget(target_info['id']) + +def handle_remove_tag(options, session, args): + "[admin] Remove a tag" + usage = _("usage: %prog remove-tag [options] 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) != 1: + parser.error(_("Please specify a tag to remove")) + assert False # pragma: no cover + activate_session(session) + + if not session.hasPerm('admin'): + print("This action requires admin privileges") + return + + tag = args[0] + tag_info = session.getTag(tag) + if not tag_info: + print("Tag %s does not exist" % tag) + return 1 + + session.deleteTag(tag_info['id']) + +def anon_handle_list_targets(options, session, args): + "[info] List the build targets" + usage = _("usage: %prog list-targets [options]") + usage += _("\n(Specify the --help global option for a list of other help options)") + parser = OptionParser(usage=usage) + parser.add_option("--name", help=_("Specify the build target name")) + parser.add_option("--quiet", action="store_true", help=_("Do not print the header information"), default=options.quiet) + (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) + + fmt = "%(name)-30s %(build_tag_name)-30s %(dest_tag_name)-30s" + if not options.quiet: + print("%-30s %-30s %-30s" % ('Name','Buildroot','Destination')) + print("-" * 93) + tmp_list = [(x['name'], x) for x in session.getBuildTargets(options.name)] + tmp_list.sort() + targets = [x[1] for x in tmp_list] + for target in targets: + print(fmt % target) + #pprint.pprint(session.getBuildTargets()) + +def _printInheritance(tags, sibdepths=None, reverse=False): + if len(tags) == 0: + return + if sibdepths == None: + sibdepths = [] + currtag = tags[0] + tags = tags[1:] + if reverse: + siblings = len([tag for tag in tags if tag['parent_id'] == currtag['parent_id']]) + else: + siblings = len([tag for tag in tags if tag['child_id'] == currtag['child_id']]) + + outdepth = 0 + for depth in sibdepths: + if depth < currtag['currdepth']: + outspacing = depth - outdepth + sys.stdout.write(' ' * (outspacing * 3 - 1)) + sys.stdout.write(_printable_unicode(u'\u2502')) + outdepth = depth + + sys.stdout.write(' ' * ((currtag['currdepth'] - outdepth) * 3 - 1)) + if siblings: + sys.stdout.write(_printable_unicode(u'\u251c')) + else: + sys.stdout.write(_printable_unicode(u'\u2514')) + sys.stdout.write(_printable_unicode(u'\u2500')) + if reverse: + sys.stdout.write('%(name)s (%(tag_id)i)\n' % currtag) + else: + sys.stdout.write('%(name)s (%(parent_id)i)\n' % currtag) + + if siblings: + if len(sibdepths) == 0 or sibdepths[-1] != currtag['currdepth']: + sibdepths.append(currtag['currdepth']) + else: + if len(sibdepths) > 0 and sibdepths[-1] == currtag['currdepth']: + sibdepths.pop() + + _printInheritance(tags, sibdepths, reverse) + +def anon_handle_list_tag_inheritance(options, session, args): + "[info] Print the inheritance information for a tag" + usage = _("usage: %prog list-tag-inheritance [options] ") + usage += _("\n(Specify the --help global option for a list of other help options)") + parser = OptionParser(usage=usage) + parser.add_option("--reverse", action="store_true", help=_("Process tag's children instead of its parents")) + parser.add_option("--stop", help=_("Stop processing inheritance at this tag")) + parser.add_option("--jump", help=_("Jump from one tag to another when processing inheritance")) + parser.add_option("--event", type='int', metavar="EVENT#", help=_("query at event")) + parser.add_option("--ts", type='int', metavar="TIMESTAMP", help=_("query at timestamp")) + parser.add_option("--repo", type='int', metavar="REPO#", help=_("query at event for a repo")) + (options, args) = parser.parse_args(args) + if len(args) != 1: + parser.error(_("This command takes exctly one argument: a tag name or ID")) + assert False # pragma: no cover + activate_session(session) + event = koji.util.eventFromOpts(session, options) + if event: + event['timestr'] = time.asctime(time.localtime(event['ts'])) + print("Querying at event %(id)i (%(timestr)s)" % event) + if event: + tag = session.getTag(args[0], event=event['id']) + else: + tag = session.getTag(args[0]) + if not tag: + parser.error(_("Unknown tag: %s" % args[0])) + + opts = {} + opts['reverse'] = options.reverse or False + opts['stops'] = {} + opts['jumps'] = {} + if event: + opts['event'] = event['id'] + + if options.jump: + match = re.match(r'^(.*)/(.*)$', options.jump) + if match: + tag1 = session.getTagID(match.group(1)) + if not tag1: + parser.error(_("Unknown tag: %s" % match.group(1))) + tag2 = session.getTagID(match.group(2)) + if not tag2: + parser.error(_("Unknown tag: %s" % match.group(2))) + opts['jumps'][str(tag1)] = tag2 + + if options.stop: + tag1 = session.getTagID(options.stop) + if not tag1: + parser.error(_("Unknown tag: %s" % options.stop)) + opts['stops'] = {str(tag1): 1} + + sys.stdout.write('%s (%i)\n' % (tag['name'], tag['id'])) + data = session.getFullInheritance(tag['id'], **opts) + _printInheritance(data, None, opts['reverse']) + +def anon_handle_list_tags(options, session, args): + "[info] Print the list of tags" + usage = _("usage: %prog list-tags [options] [pattern]") + usage += _("\n(Specify the --help global option for a list of other help options)") + parser = OptionParser(usage=usage) + parser.add_option("--show-id", action="store_true", help=_("Show tag ids")) + parser.add_option("--verbose", action="store_true", help=_("Show more information")) + parser.add_option("--unlocked", action="store_true", help=_("Only show unlocked tags")) + parser.add_option("--build", help=_("Show tags associated with a build")) + parser.add_option("--package", help=_("Show tags associated with a package")) + (options, args) = parser.parse_args(args) + activate_session(session) + + pkginfo = {} + buildinfo = {} + + if options.package: + pkginfo = session.getPackage(options.package) + if not pkginfo: + parser.error(_("Invalid package %s" % options.package)) + assert False # pragma: no cover + + if options.build: + buildinfo = session.getBuild(options.build) + if not buildinfo: + parser.error(_("Invalid build %s" % options.build)) + assert False # pragma: no cover + + tags = session.listTags(buildinfo.get('id',None), pkginfo.get('id',None)) + tags.sort(key=lambda x: x['name']) + #if options.verbose: + # fmt = "%(name)s [%(id)i] %(perm)s %(locked)s %(arches)s" + if options.show_id: + fmt = "%(name)s [%(id)i]" + else: + fmt = "%(name)s" + for tag in tags: + if args: + for pattern in args: + if fnmatch.fnmatch(tag['name'], pattern): + break + else: + continue + if options.unlocked: + if tag['locked'] or tag['perm']: + continue + if not options.verbose: + print(fmt % tag) + else: + sys.stdout.write(fmt % tag) + if tag['locked']: + sys.stdout.write(' [LOCKED]') + if tag['perm']: + sys.stdout.write(' [%(perm)s perm required]' % tag) + print('') + +def anon_handle_list_tag_history(options, session, args): + "[info] Print a history of tag operations" + usage = _("usage: %prog list-tag-history [options]") + usage += _("\n(Specify the --help global option for a list of other help options)") + parser = OptionParser(usage=usage) + parser.add_option("--debug", action="store_true") + parser.add_option("--build", help=_("Only show data for a specific build")) + parser.add_option("--package", help=_("Only show data for a specific package")) + parser.add_option("--tag", help=_("Only show data for a specific tag")) + parser.add_option("--all", action="store_true", help=_("Allows listing the entire global history")) + (options, args) = parser.parse_args(args) + if len(args) != 0: + parser.error(_("This command takes no arguments")) + assert False # pragma: no cover + kwargs = {} + limited = False + if options.package: + kwargs['package'] = options.package + limited = True + if options.tag: + kwargs['tag'] = options.tag + limited = True + if options.build: + kwargs['build'] = options.build + limited = True + if not limited and not options.all: + parser.error(_("Please specify an option to limit the query")) + + activate_session(session) + + hist = session.tagHistory(**kwargs) + timeline = [] + for x in hist: + event_id = x['revoke_event'] + if event_id is not None: + timeline.append((event_id, x)) + event_id = x['create_event'] + timeline.append((event_id, x)) + timeline.sort() + def _histline(event_id, x): + if event_id == x['revoke_event']: + ts = x['revoke_ts'] + fmt = "%(name)s-%(version)s-%(release)s untagged from %(tag_name)s" + if 'revoker_name' in x: + fmt += " by %(revoker_name)s" + elif event_id == x['create_event']: + ts = x['create_ts'] + fmt = "%(name)s-%(version)s-%(release)s tagged into %(tag_name)s" + if 'creator_name' in x: + fmt += " by %(creator_name)s" + if x['active']: + fmt += " [still active]" + else: + raise koji.GenericError("unknown event: (%r, %r)" % (event_id, x)) + time_str = time.asctime(time.localtime(ts)) + return "%s: %s" % (time_str, fmt % x) + for event_id, x in timeline: + if options.debug: + print("%r" % x) + print(_histline(event_id, x)) + +def _print_histline(entry, **kwargs): + options = kwargs['options'] + event_id, table, create, x = entry + who = None + edit = x.get('.related') + if edit: + del x['.related'] + bad_edit = None + if len(edit) != 1: + bad_edit = "%i elements" % len(edit)+1 + other = edit[0] + #check edit for sanity + if create or not other[2]: + bad_edit = "out of order" + if event_id != other[0]: + bad_edit = "non-matching" + if bad_edit: + print("Warning: unusual edit at event %i in table %s (%s)" % (event_id, table, bad_edit)) + #we'll simply treat them as separate events + pprint.pprint(entry) + pprint.pprint(edit) + _print_histline(entry, **kwargs) + for data in edit: + _print_histline(entry, **kwargs) + return + if create: + ts = x['create_ts'] + if 'creator_name' in x: + who = "by %(creator_name)s" + else: + ts = x['revoke_ts'] + if 'revoker_name' in x: + who = "by %(revoker_name)s" + if table == 'tag_listing': + if edit: + fmt = "%(name)s-%(version)s-%(release)s re-tagged into %(tag.name)s" + elif create: + fmt = "%(name)s-%(version)s-%(release)s tagged into %(tag.name)s" + else: + fmt = "%(name)s-%(version)s-%(release)s untagged from %(tag.name)s" + elif table == 'user_perms': + if edit: + fmt = "permission %(permission.name)s re-granted to %(user.name)s" + elif create: + fmt = "permission %(permission.name)s granted to %(user.name)s" + else: + fmt = "permission %(permission.name)s revoked for %(user.name)s" + elif table == 'user_groups': + if edit: + fmt = "user %(user.name)s re-added to group %(group.name)s" + elif create: + fmt = "user %(user.name)s added to group %(group.name)s" + else: + fmt = "user %(user.name)s removed from group %(group.name)s" + elif table == 'cg_users': + if edit: + fmt = "user %(user.name)s re-added to content generator %(content_generator.name)s" + elif create: + fmt = "user %(user.name)s added to content generator %(content_generator.name)s" + else: + fmt = "user %(user.name)s removed from content generator %(content_generator.name)s" + elif table == 'tag_packages': + if edit: + fmt = "package list entry for %(package.name)s in %(tag.name)s updated" + elif create: + fmt = "package list entry created: %(package.name)s in %(tag.name)s" + else: + fmt = "package list entry revoked: %(package.name)s in %(tag.name)s" + elif table == 'tag_inheritance': + if edit: + fmt = "inheritance line %(tag.name)s->%(parent.name)s updated" + elif create: + fmt = "inheritance line %(tag.name)s->%(parent.name)s added" + else: + fmt = "inheritance line %(tag.name)s->%(parent.name)s removed" + elif table == 'tag_config': + if edit: + fmt = "tag configuration for %(tag.name)s altered" + elif create: + fmt = "new tag: %(tag.name)s" + else: + fmt = "tag deleted: %(tag.name)s" + elif table == 'tag_extra': + if edit: + fmt = "tag option %(key)s for tag %(tag.name)s altered" + elif create: + fmt = "added tag option %(key)s for tag %(tag.name)s" + else: + fmt = "tag option %(key)s removed for %(tag.name)s" + elif table == 'build_target_config': + if edit: + fmt = "build target configuration for %(build_target.name)s updated" + elif create: + fmt = "new build target: %(build_target.name)s" + else: + fmt = "build target deleted: %(build_target.name)s" + elif table == 'external_repo_config': + if edit: + fmt = "external repo configuration for %(external_repo.name)s altered" + elif create: + fmt = "new external repo: %(external_repo.name)s" + else: + fmt = "external repo deleted: %(external_repo.name)s" + elif table == 'tag_external_repos': + if edit: + fmt = "external repo entry for %(external_repo.name)s in tag %(tag.name)s updated" + elif create: + fmt = "external repo entry for %(external_repo.name)s added to tag %(tag.name)s" + else: + fmt = "external repo entry for %(external_repo.name)s removed from tag %(tag.name)s" + elif table == 'group_config': + if edit: + fmt = "group %(group.name)s configuration for tag %(tag.name)s updated" + elif create: + fmt = "group %(group.name)s added to tag %(tag.name)s" + else: + fmt = "group %(group.name)s removed from tag %(tag.name)s" + elif table == 'group_req_listing': + if edit: + fmt = "group dependency %(group.name)s->%(req.name)s updated in tag %(tag.name)s" + elif create: + fmt = "group dependency %(group.name)s->%(req.name)s added in tag %(tag.name)s" + else: + fmt = "group dependency %(group.name)s->%(req.name)s dropped from tag %(tag.name)s" + elif table == 'group_package_listing': + if edit: + fmt = "package entry %(package)s in group %(group.name)s, tag %(tag.name)s updated" + elif create: + fmt = "package %(package)s added to group %(group.name)s in tag %(tag.name)s" + else: + fmt = "package %(package)s removed from group %(group.name)s in tag %(tag.name)s" + else: + if edit: + fmt = "%s entry updated" % table + elif create: + fmt = "%s entry created" % table + else: + fmt = "%s entry revoked" % table + time_str = time.asctime(time.localtime(ts)) + parts = [time_str, fmt % x] + if options.events or options.verbose: + parts.insert(1, "(eid %i)" % event_id) + if who: + parts.append(who % x) + if create and x['active']: + parts.append("[still active]") + print(' '.join(parts)) + hidden_fields = ['active', 'create_event', 'revoke_event', 'creator_id', 'revoker_id', + 'creator_name', 'revoker_name', 'create_ts', 'revoke_ts'] + def get_nkey(key): + if key == 'perm_id': + return 'permission.name' + elif key.endswith('_id'): + return '%s.name' % key[:-3] + else: + return '%s.name' % key + if edit: + keys = list(x.keys()) + keys.sort() + y = other[-1] + for key in keys: + if key in hidden_fields: + continue + if x[key] == y[key]: + continue + if key[0] == '_': + continue + nkey = get_nkey(key) + if nkey in x and nkey in y: + continue + print(" %s: %s -> %s" % (key, x[key], y[key])) + elif create and options.verbose and table != 'tag_listing': + keys = list(x.keys()) + keys.sort() + # the table keys have already been represented in the base format string + also_hidden = list(_table_keys[table]) + also_hidden.extend([get_nkey(k) for k in also_hidden]) + for key in keys: + if key in hidden_fields or key in also_hidden: + continue + nkey = get_nkey(key) + if nkey in x: + continue + if key[0] == '_': + continue + if x.get('blocked') and key != 'blocked': + continue + if key.endswith('.name'): + dkey = key[:-5] + else: + dkey = key + print(" %s: %s" % (dkey, x[key])) + +_table_keys = { + 'user_perms' : ['user_id', 'perm_id'], + 'user_groups' : ['user_id', 'group_id'], + 'cg_users' : ['user_id', 'cg_id'], + 'tag_inheritance' : ['tag_id', 'parent_id'], + 'tag_config' : ['tag_id'], + 'tag_extra' : ['tag_id', 'key'], + 'build_target_config' : ['build_target_id'], + 'external_repo_config' : ['external_repo_id'], + 'tag_external_repos' : ['tag_id', 'external_repo_id'], + 'tag_listing' : ['build_id', 'tag_id'], + 'tag_packages' : ['package_id', 'tag_id'], + 'group_config' : ['group_id', 'tag_id'], + 'group_req_listing' : ['group_id', 'tag_id', 'req_id'], + 'group_package_listing' : ['group_id', 'tag_id', 'package'], + } + +def anon_handle_list_history(options, session, args): + "[info] Display historical data" + usage = _("usage: %prog list-history [options]") + usage += _("\n(Specify the --help global option for a list of other help options)") + parser = OptionParser(usage=usage) + parser.add_option("--debug", action="store_true") + parser.add_option("--build", help=_("Only show data for a specific build")) + parser.add_option("--package", help=_("Only show data for a specific package")) + parser.add_option("--tag", help=_("Only show data for a specific tag")) + parser.add_option("--editor", "--by", metavar="USER", help=_("Only show entries modified by user")) + parser.add_option("--user", help=_("Only show entries affecting a user")) + parser.add_option("--permission", help=_("Only show entries relating to a given permission")) + parser.add_option("--cg", help=_("Only show entries relating to a given permission")) + parser.add_option("--external-repo", "--erepo", help=_("Only show entries relating to a given external repo")) + parser.add_option("--build-target", "--target", help=_("Only show entries relating to a given build target")) + parser.add_option("--group", help=_("Only show entries relating to a given group")) + parser.add_option("--before", metavar="TIMESTAMP", help=_("Only show entries before timestamp")) + parser.add_option("--after", metavar="TIMESTAMP", help=_("Only show entries after timestamp")) + parser.add_option("--before-event", metavar="EVENT_ID", type='int', help=_("Only show entries before event")) + parser.add_option("--after-event", metavar="EVENT_ID", type='int', help=_("Only show entries after event")) + parser.add_option("--watch", action="store_true", help=_("Monitor history data")) + parser.add_option("--active", action='store_true', help=_("Only show entries that are currently active")) + parser.add_option("--revoked", action='store_false', dest='active', + help=_("Only show entries that are currently revoked")) + parser.add_option("--context", action="store_true", help=_("Show related entries")) + parser.add_option("-s", "--show", action="append", help=_("Show data from selected tables")) + parser.add_option("-v", "--verbose", action="store_true", help=_("Show more detail")) + parser.add_option("-e", "--events", action="store_true", help=_("Show event ids")) + parser.add_option("--all", action="store_true", help=_("Allows listing the entire global history")) + global_options = options + (options, args) = parser.parse_args(args) + if len(args) != 0: + parser.error(_("This command takes no arguments")) + assert False # pragma: no cover + kwargs = {} + limited = False + 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) + for opt in ('package', 'tag', 'build', 'editor', 'user', 'permission', + 'cg', 'external_repo', 'build_target', 'group', 'before', + 'after'): + val = getattr(options, opt) + if val: + kwargs[opt] = val + limited = True + if options.before_event: + kwargs['beforeEvent'] = options.before_event + if options.after_event: + kwargs['afterEvent'] = options.after_event + if options.active is not None: + kwargs['active'] = options.active + tables = None + if options.show: + tables = [] + for arg in options.show: + tables.extend(arg.split(',')) + if not limited and not options.all: + parser.error(_("Please specify an option to limit the query")) + + activate_session(session) + + if options.watch: + if not kwargs.get('afterEvent') and not kwargs.get('after'): + kwargs['afterEvent'] = session.getLastEvent()['id'] + + while True: + histdata = session.queryHistory(tables=tables, **kwargs) + timeline = [] + def distinguish_match(x, name): + """determine if create or revoke event matched""" + if options.context: + return True + name = "_" + name + ret = True + for key in x: + if key.startswith(name): + ret = ret and x[key] + return ret + for table in histdata: + hist = histdata[table] + for x in hist: + if x['revoke_event'] is not None: + if distinguish_match(x, 'revoked'): + timeline.append((x['revoke_event'], table, 0, x.copy())) + #pprint.pprint(timeline[-1]) + if distinguish_match(x, 'created'): + timeline.append((x['create_event'], table, 1, x)) + timeline.sort() + #group edits together + new_timeline = [] + last_event = None + edit_index = {} + for entry in timeline: + event_id, table, create, x = entry + if event_id != last_event: + edit_index = {} + last_event = event_id + key = tuple([x[k] for k in _table_keys[table]]) + prev = edit_index.get((table, event_id), {}).get(key) + if prev: + prev[-1].setdefault('.related', []).append(entry) + else: + edit_index.setdefault((table, event_id), {})[key] = entry + new_timeline.append(entry) + for entry in new_timeline: + if options.debug: + print("%r" % list(entry)) + _print_histline(entry, options=options) + if not options.watch: + break + else: + time.sleep(global_options.poll_interval) + # repeat query for later events + if last_event: + kwargs['afterEvent'] = last_event + +def _handleMap(lines, data, prefix=''): + for key, val in data.items(): + if key != '__starstar': + lines.append(' %s%s: %s' % (prefix, key, val)) + +def _handleOpts(lines, opts, prefix=''): + if opts: + lines.append("%sOptions:" % prefix) + _handleMap(lines, opts, prefix) + + +def _parseTaskParams(session, method, task_id): + try: + return _do_parseTaskParams(session, method, task_id) + except Exception: + if logger.isEnabledFor(logging.DEBUG): + tb_str = ''.join(traceback.format_exception(*sys.exc_info())) + logger.debug(tb_str) + return ['Unable to parse task parameters'] + + +def _do_parseTaskParams(session, method, task_id): + """Parse the return of getTaskRequest()""" + params = session.getTaskRequest(task_id) + + lines = [] + + if method == 'buildSRPMFromCVS': + lines.append("CVS URL: %s" % params[0]) + elif method == 'buildSRPMFromSCM': + lines.append("SCM URL: %s" % params[0]) + elif method == 'buildArch': + lines.append("SRPM: %s/work/%s" % (options.topdir, params[0])) + lines.append("Build Tag: %s" % session.getTag(params[1])['name']) + lines.append("Build Arch: %s" % params[2]) + lines.append("SRPM Kept: %r" % params[3]) + if len(params) > 4: + _handleOpts(lines, params[4]) + elif method == 'tagBuild': + build = session.getBuild(params[1]) + lines.append("Destination Tag: %s" % session.getTag(params[0])['name']) + lines.append("Build: %s" % koji.buildLabel(build)) + elif method == 'buildNotification': + build = params[1] + buildTarget = params[2] + lines.append("Recipients: %s" % (", ".join(params[0]))) + lines.append("Build: %s" % koji.buildLabel(build)) + lines.append("Build Target: %s" % buildTarget['name']) + lines.append("Web URL: %s" % params[3]) + elif method == 'build': + lines.append("Source: %s" % params[0]) + lines.append("Build Target: %s" % params[1]) + if len(params) > 2: + _handleOpts(lines, params[2]) + elif method == 'maven': + lines.append("SCM URL: %s" % params[0]) + lines.append("Build Target: %s" % params[1]) + if len(params) > 2: + _handleOpts(lines, params[2]) + elif method == 'buildMaven': + lines.append("SCM URL: %s" % params[0]) + lines.append("Build Tag: %s" % params[1]['name']) + if len(params) > 2: + _handleOpts(lines, params[2]) + elif method == 'wrapperRPM': + lines.append("Spec File URL: %s" % params[0]) + lines.append("Build Tag: %s" % params[1]['name']) + if params[2]: + lines.append("Build: %s" % koji.buildLabel(params[2])) + if params[3]: + lines.append("Task: %s %s" % (params[3]['id'], koji.taskLabel(params[3]))) + if len(params) > 4: + _handleOpts(lines, params[4]) + elif method == 'chainmaven': + lines.append("Builds:") + for package, opts in params[0].items(): + lines.append(" " + package) + _handleMap(lines, opts, prefix=" ") + lines.append("Build Target: %s" % params[1]) + if len(params) > 2: + _handleOpts(lines, params[2]) + elif method == 'winbuild': + lines.append("VM: %s" % params[0]) + lines.append("SCM URL: %s" % params[1]) + lines.append("Build Target: %s" % params[2]) + if len(params) > 3: + _handleOpts(lines, params[3]) + elif method == 'vmExec': + lines.append("VM: %s" % params[0]) + lines.append("Exec Params:") + for info in params[1]: + if isinstance(info, dict): + _handleMap(lines, info, prefix=' ') + else: + lines.append(" %s" % info) + if len(params) > 2: + _handleOpts(lines, params[2]) + elif method in ('createLiveCD', 'createAppliance', 'createLiveMedia'): + argnames = ['Name', 'Version', 'Release', 'Arch', 'Target Info', 'Build Tag', 'Repo', 'Kickstart File'] + for n, v in zip(argnames, params): + lines.append("%s: %s" % (n, v)) + if len(params) > 8: + _handleOpts(lines, params[8]) + elif method in ('appliance', 'livecd', 'livemedia'): + argnames = ['Name', 'Version', 'Arches', 'Target', 'Kickstart'] + for n, v in zip(argnames, params): + lines.append("%s: %s" % (n, v)) + if len(params) > 5: + _handleOpts(lines, params[5]) + elif method == 'newRepo': + tag = session.getTag(params[0]) + lines.append("Tag: %s" % tag['name']) + elif method == 'prepRepo': + lines.append("Tag: %s" % params[0]['name']) + elif method == 'createrepo': + lines.append("Repo ID: %i" % params[0]) + lines.append("Arch: %s" % params[1]) + oldrepo = params[2] + if oldrepo: + lines.append("Old Repo ID: %i" % oldrepo['id']) + lines.append("Old Repo Creation: %s" % koji.formatTimeLong(oldrepo['creation_time'])) + if len(params) > 3: + lines.append("External Repos: %s" % ', '.join([ext['external_repo_name'] for ext in params[3]])) + elif method == 'tagNotification': + destTag = session.getTag(params[2]) + srcTag = None + if params[3]: + srcTag = session.getTag(params[3]) + build = session.getBuild(params[4]) + user = session.getUser(params[5]) + + lines.append("Recipients: %s" % ", ".join(params[0])) + lines.append("Successful?: %s" % (params[1] and 'yes' or 'no')) + lines.append("Tagged Into: %s" % destTag['name']) + if srcTag: + lines.append("Moved From: %s" % srcTag['name']) + lines.append("Build: %s" % koji.buildLabel(build)) + lines.append("Tagged By: %s" % user['name']) + lines.append("Ignore Success?: %s" % (params[6] and 'yes' or 'no')) + if params[7]: + lines.append("Failure Message: %s" % params[7]) + elif method == 'dependantTask': + lines.append("Dependant Tasks: %s" % ", ".join([str(depID) for depID in params[0]])) + lines.append("Subtasks:") + for subtask in params[1]: + lines.append(" Method: %s" % subtask[0]) + lines.append(" Parameters: %s" % ", ".join([str(subparam) for subparam in subtask[1]])) + if len(subtask) > 2 and subtask[2]: + subopts = subtask[2] + _handleOpts(lines, subopts, prefix=' ') + lines.append("") + elif method == 'chainbuild': + lines.append("Build Groups:") + group_num = 0 + for group_list in params[0]: + group_num += 1 + lines.append(" %i: %s" % (group_num, ', '.join(group_list))) + lines.append("Build Target: %s" % params[1]) + if len(params) > 2: + _handleOpts(lines, params[2]) + elif method == 'waitrepo': + lines.append("Build Target: %s" % params[0]) + if params[1]: + lines.append("Newer Than: %s" % params[1]) + if params[2]: + lines.append("NVRs: %s" % ', '.join(params[2])) + + return lines + +def _printTaskInfo(session, task_id, level=0, recurse=True, verbose=True): + """Recursive function to print information about a task + and its children.""" + + BUILDDIR = '/var/lib/mock' + indent = " "*2*level + + info = session.getTaskInfo(task_id) + + if info is None: + raise koji.GenericError("No such task: %d" % task_id) + + if info['host_id']: + host_info = session.getHost(info['host_id']) + else: + host_info = None + buildroot_infos = session.listBuildroots(taskID=task_id) + build_info = session.listBuilds(taskID=task_id) + + files = list_task_output_all_volumes(session, task_id) + logs = [] + output = [] + for filename in files: + if filename.endswith('.log'): + logs += [os.path.join(koji.pathinfo.work(volume=volume), + koji.pathinfo.taskrelpath(task_id), + filename) for volume in files[filename]] + else: + output += [os.path.join(koji.pathinfo.work(volume=volume), + koji.pathinfo.taskrelpath(task_id), + filename) for volume in files[filename]] + + owner = session.getUser(info['owner'])['name'] + + print("%sTask: %d" % (indent, task_id)) + print("%sType: %s" % (indent, info['method'])) + if verbose: + print("%sRequest Parameters:" % indent) + for line in _parseTaskParams(session, info['method'], task_id): + print("%s %s" % (indent, line)) + print("%sOwner: %s" % (indent, owner)) + print("%sState: %s" % (indent, koji.TASK_STATES[info['state']].lower())) + print("%sCreated: %s" % (indent, time.asctime(time.localtime(info['create_ts'])))) + if info.get('start_ts'): + print("%sStarted: %s" % (indent, time.asctime(time.localtime(info['start_ts'])))) + if info.get('completion_ts'): + print("%sFinished: %s" % (indent, time.asctime(time.localtime(info['completion_ts'])))) + if host_info: + print("%sHost: %s" % (indent, host_info['name'])) + if build_info: + print("%sBuild: %s (%d)" % (indent, build_info[0]['nvr'], build_info[0]['build_id'])) + if buildroot_infos: + print("%sBuildroots:" % indent) + for root in buildroot_infos: + print("%s %s/%s-%d-%d/" % (indent, BUILDDIR, root['tag_name'], root['id'], root['repo_id'])) + if logs: + print("%sLog Files:" % indent) + for log_path in logs: + print("%s %s" % (indent, log_path)) + if output: + print("%sOutput:" % indent) + for file_path in output: + print("%s %s" % (indent, file_path)) + + # white space + print('') + + if recurse: + level += 1 + children = session.getTaskChildren(task_id, request=True) + children.sort(cmp=lambda a, b: cmp(a['id'], b['id'])) + for child in children: + _printTaskInfo(session, child['id'], level, verbose=verbose) + +def anon_handle_taskinfo(options, session, args): + """[info] Show information about a task""" + usage = _("usage: %prog taskinfo [options] taskID [taskID...]") + usage += _("\n(Specify the --help global option for a list of other help options)") + parser = OptionParser(usage=usage) + parser.add_option("-r", "--recurse", action="store_true", help=_("Show children of this task as well")) + parser.add_option("-v", "--verbose", action="store_true", help=_("Be verbose")) + (options, args) = parser.parse_args(args) + if len(args) < 1: + parser.error(_("You must specify at least one task ID")) + assert False # pragma: no cover + + activate_session(session) + + for arg in args: + task_id = int(arg) + _printTaskInfo(session, task_id, 0, options.recurse, options.verbose) + +def anon_handle_taginfo(options, session, args): + "[info] Print basic information about a tag" + usage = _("usage: %prog taginfo [options] [ ...]") + usage += _("\n(Specify the --help global option for a list of other help options)") + parser = OptionParser(usage=usage) + parser.add_option("--event", type='int', metavar="EVENT#", help=_("query at event")) + parser.add_option("--ts", type='int', metavar="TIMESTAMP", help=_("query at timestamp")) + parser.add_option("--repo", type='int', metavar="REPO#", help=_("query at event for a repo")) + (options, args) = parser.parse_args(args) + if len(args) < 1: + parser.error(_("Please specify a tag")) + assert False # pragma: no cover + activate_session(session) + event = koji.util.eventFromOpts(session, options) + event_opts = {} + if event: + event['timestr'] = time.asctime(time.localtime(event['ts'])) + print("Querying at event %(id)i (%(timestr)s)" % event) + event_opts['event'] = event['id'] + perms = dict([(p['id'], p['name']) for p in session.getAllPerms()]) + + tags = [] + for tag in args: + info = session.getTag(tag, **event_opts) + if info is None: + print("No such tag: %s" % tag) + sys.exit(1) + tags.append(info) + + for n, info in enumerate(tags): + if n > 0: + print('') + print("Tag: %(name)s [%(id)d]" %info) + print("Arches: %(arches)s" %info) + group_list = [x['name'] for x in session.getTagGroups(info['id'], **event_opts)] + group_list.sort() + print("Groups: " + ', '.join(group_list)) + if info.get('locked'): + print('LOCKED') + if info.get('perm_id') is not None: + perm_id = info['perm_id'] + print("Required permission: %r" % perms.get(perm_id, perm_id)) + if session.mavenEnabled(): + print("Maven support?: %s" % (info['maven_support'] and 'yes' or 'no')) + print("Include all Maven archives?: %s" % (info['maven_include_all'] and 'yes' or 'no')) + if 'extra' in info: + print("Tag options:") + keys = list(info['extra'].keys()) + keys.sort() + for key in keys: + print(" %s : %s" % (key, pprint.pformat(info['extra'][key]))) + dest_targets = session.getBuildTargets(destTagID=info['id'], **event_opts) + build_targets = session.getBuildTargets(buildTagID=info['id'], **event_opts) + repos = {} + if not event: + for target in dest_targets + build_targets: + if target['build_tag'] not in repos: + repo = session.getRepo(target['build_tag']) + if repo is None: + repos[target['build_tag']] = "no active repo" + else: + repos[target['build_tag']] = "repo#%(id)i: %(creation_time)s" % repo + if dest_targets: + print("Targets that build into this tag:") + for target in dest_targets: + if event: + print(" %s (%s)" % (target['name'], target['build_tag_name'])) + else: + print(" %s (%s, %s)" % (target['name'], target['build_tag_name'], repos[target['build_tag']])) + if build_targets: + print("This tag is a buildroot for one or more targets") + if not event: + print("Current repo: %s" % repos[info['id']]) + print("Targets that build from this tag:") + for target in build_targets: + print(" %s" % target['name']) + external_repos = session.getTagExternalRepos(tag_info=info['id'], **event_opts) + if external_repos: + print("External repos:") + for rinfo in external_repos: + print(" %(priority)3i %(external_repo_name)s (%(url)s)" % rinfo) + print("Inheritance:") + for parent in session.getInheritanceData(tag, **event_opts): + flags = '' + for code,expr in ( + ('M',parent['maxdepth'] is not None), + ('F',parent['pkg_filter']), + ('I',parent['intransitive']), + ('N',parent['noconfig']),): + if expr: + flags += code + else: + flags += '.' + parent['flags'] = flags + print(" %(priority)-4d %(flags)s %(name)s [%(parent_id)s]" % parent) + if parent['maxdepth'] is not None: + print(" maxdepth: %(maxdepth)s" % parent) + if parent['pkg_filter']: + print(" package filter: %(pkg_filter)s" % parent) + + +def handle_add_tag(options, session, args): + "[admin] Add a new tag to the database" + usage = _("usage: %prog add-tag [options] name") + usage += _("\n(Specify the --help global option for a list of other help options)") + parser = OptionParser(usage=usage) + parser.add_option("--parent", help=_("Specify parent")) + parser.add_option("--arches", help=_("Specify arches")) + parser.add_option("--maven-support", action="store_true", help=_("Enable creation of Maven repos for this tag")) + parser.add_option("--include-all", action="store_true", help=_("Include all packages in this tag when generating Maven repos")) + parser.add_option("-x", "--extra", action="append", default=[], metavar="key=value", + help=_("Set tag extra option")) + (options, args) = parser.parse_args(args) + if len(args) != 1: + parser.error(_("Please specify a name for the tag")) + assert False # pragma: no cover + activate_session(session) + if not session.hasPerm('admin'): + print("This action requires admin privileges") + return + opts = {} + if options.parent: + opts['parent'] = options.parent + if options.arches: + opts['arches'] = parse_arches(options.arches) + if options.maven_support: + opts['maven_support'] = True + if options.include_all: + opts['maven_include_all'] = True + if options.extra: + extra = {} + for xopt in options.extra: + key, value = xopt.split('=', 1) + value = arg_filter(value) + extra[key] = value + opts['extra'] = extra + session.createTag(args[0],**opts) + +def handle_edit_tag(options, session, args): + "[admin] Alter tag information" + usage = _("usage: %prog edit-tag [options] name") + usage += _("\n(Specify the --help global option for a list of other help options)") + parser = OptionParser(usage=usage) + parser.add_option("--arches", help=_("Specify arches")) + parser.add_option("--perm", help=_("Specify permission requirement")) + parser.add_option("--no-perm", action="store_true", help=_("Remove permission requirement")) + parser.add_option("--lock", action="store_true", help=_("Lock the tag")) + parser.add_option("--unlock", action="store_true", help=_("Unlock the tag")) + parser.add_option("--rename", help=_("Rename the tag")) + parser.add_option("--maven-support", action="store_true", help=_("Enable creation of Maven repos for this tag")) + parser.add_option("--no-maven-support", action="store_true", help=_("Disable creation of Maven repos for this tag")) + parser.add_option("--include-all", action="store_true", help=_("Include all packages in this tag when generating Maven repos")) + parser.add_option("--no-include-all", action="store_true", help=_("Do not include all packages in this tag when generating Maven repos")) + parser.add_option("-x", "--extra", action="append", default=[], metavar="key=value", + help=_("Set tag extra option")) + parser.add_option("-r", "--remove-extra", action="append", default=[], metavar="key", + help=_("Remove tag extra option")) + (options, args) = parser.parse_args(args) + if len(args) != 1: + parser.error(_("Please specify a name for the tag")) + assert False # pragma: no cover + activate_session(session) + tag = args[0] + opts = {} + if options.arches: + opts['arches'] = parse_arches(options.arches) + if options.no_perm: + opts['perm_id'] = None + elif options.perm: + opts['perm'] = options.perm + if options.unlock: + opts['locked'] = False + if options.lock: + opts['locked'] = True + if options.rename: + opts['name'] = options.rename + if options.maven_support: + opts['maven_support'] = True + if options.no_maven_support: + opts['maven_support'] = False + if options.include_all: + opts['maven_include_all'] = True + if options.no_include_all: + opts['maven_include_all'] = False + if options.extra: + extra = {} + for xopt in options.extra: + key, value = xopt.split('=', 1) + value = arg_filter(value) + extra[key] = value + opts['extra'] = extra + if options.remove_extra: + opts['remove_extra'] = options.remove_extra + #XXX change callname + session.editTag2(tag, **opts) + +def handle_lock_tag(options, session, args): + "[admin] Lock a tag" + usage = _("usage: %prog lock-tag [options] [ ...] ") + usage += _("\n(Specify the --help global option for a list of other help options)") + parser = OptionParser(usage=usage) + parser.add_option("--perm", help=_("Specify permission requirement")) + parser.add_option("--glob", action="store_true", help=_("Treat args as glob patterns")) + parser.add_option("--master", action="store_true", help=_("Lock the master lock")) + parser.add_option("-n", "--test", action="store_true", help=_("Test mode")) + (options, args) = parser.parse_args(args) + if len(args) < 1: + parser.error(_("Please specify a tag")) + assert False # pragma: no cover + activate_session(session) + pdata = session.getAllPerms() + perm_ids = dict([(p['name'], p['id']) for p in pdata]) + perm = options.perm + if perm is None: + perm = 'admin' + perm_id = perm_ids[perm] + if options.glob: + selected = [] + for tag in session.listTags(): + for pattern in args: + if fnmatch.fnmatch(tag['name'], pattern): + selected.append(tag) + break + if not selected: + print(_("No tags matched")) + else: + selected = [session.getTag(name) for name in args] + for tag in selected: + if options.master: + #set the master lock + if tag['locked']: + print(_("Tag %s: master lock already set") % tag['name']) + continue + elif options.test: + print(_("Would have set master lock for: %s") % tag['name']) + continue + session.editTag2(tag['id'], locked=True) + else: + if tag['perm_id'] == perm_id: + print(_("Tag %s: %s permission already required") % (tag['name'], perm)) + continue + elif options.test: + print(_("Would have set permission requirement %s for tag %s") % (perm, tag['name'])) + continue + session.editTag2(tag['id'], perm=perm_id) + +def handle_unlock_tag(options, session, args): + "[admin] Unlock a tag" + usage = _("usage: %prog unlock-tag [options] [ ...]") + usage += _("\n(Specify the --help global option for a list of other help options)") + parser = OptionParser(usage=usage) + parser.add_option("--glob", action="store_true", help=_("Treat args as glob patterns")) + parser.add_option("-n", "--test", action="store_true", help=_("Test mode")) + (options, args) = parser.parse_args(args) + if len(args) < 1: + parser.error(_("Please specify a tag")) + assert False # pragma: no cover + activate_session(session) + if options.glob: + selected = [] + for tag in session.listTags(): + for pattern in args: + if fnmatch.fnmatch(tag['name'], pattern): + selected.append(tag) + break + if not selected: + print(_("No tags matched")) + else: + selected = [] + for name in args: + tag = session.getTag(name) + if tag is None: + parser.error(_("No such tag: %s") % name) + assert False # pragma: no cover + selected.append(tag) + selected = [session.getTag(name) for name in args] + for tag in selected: + opts = {} + if tag['locked']: + opts['locked'] = False + if tag['perm_id']: + opts['perm'] = None + if not opts: + print("Tag %(name)s: not locked" % tag) + continue + if options.test: + print("Tag %s: skipping changes: %r" % (tag['name'], opts)) + else: + session.editTag2(tag['id'], locked=False, perm_id=None) + +def handle_add_tag_inheritance(options, session, args): + """[admin] Add to a tag's inheritance""" + usage = _("usage: %prog add-tag-inheritance [options] tag parent-tag") + usage += _("\n(Specify the --help global option for a list of other help options)") + parser = OptionParser(usage=usage) + parser.add_option("--priority", help=_("Specify priority")) + parser.add_option("--maxdepth", help=_("Specify max depth")) + parser.add_option("--intransitive", action="store_true", help=_("Set intransitive")) + parser.add_option("--noconfig", action="store_true", help=_("Set to packages only")) + parser.add_option("--pkg-filter", help=_("Specify the package filter")) + parser.add_option("--force", help=_("Force adding a parent to a tag that already has that parent tag")) + (options, args) = parser.parse_args(args) + + if len(args) != 2: + parser.error(_("This command takes exctly two argument: a tag name or ID and that tag's new parent name or ID")) + assert False # pragma: no cover + + activate_session(session) + + tag = session.getTag(args[0]) + if not tag: + parser.error(_("Invalid tag: %s" % args[0])) + + parent = session.getTag(args[1]) + if not parent: + parser.error(_("Invalid tag: %s" % args[1])) + + inheritanceData = session.getInheritanceData(tag['id']) + priority = options.priority and int(options.priority) or 0 + sameParents = [datum for datum in inheritanceData if datum['parent_id'] == parent['id']] + samePriority = [datum for datum in inheritanceData if datum['priority'] == priority] + + if sameParents and not options.force: + print(_("Error: You are attempting to add %s as %s's parent even though it already is %s's parent.") + % (parent['name'], tag['name'], tag['name'])) + print(_("Please use --force if this is what you really want to do.")) + return + if samePriority: + print(_("Error: There is already an active inheritance with that priority on %s, please specify a different priority with --priority." % tag['name'])) + return + + new_data = {} + new_data['parent_id'] = parent['id'] + new_data['priority'] = options.priority or 0 + if options.maxdepth and options.maxdepth.isdigit(): + new_data['maxdepth'] = int(options.maxdepth) + else: + new_data['maxdepth'] = None + new_data['intransitive'] = options.intransitive or False + new_data['noconfig'] = options.noconfig or False + new_data['pkg_filter'] = options.pkg_filter or '' + + inheritanceData.append(new_data) + session.setInheritanceData(tag['id'], inheritanceData) + + +def handle_edit_tag_inheritance(options, session, args): + """[admin] Edit tag inheritance""" + usage = _("usage: %prog edit-tag-inheritance [options] tag ") + usage += _("\n(Specify the --help global option for a list of other help options)") + parser = OptionParser(usage=usage) + parser.add_option("--priority", help=_("Specify a new priority")) + parser.add_option("--maxdepth", help=_("Specify max depth")) + parser.add_option("--intransitive", action="store_true", help=_("Set intransitive")) + parser.add_option("--noconfig", action="store_true", help=_("Set to packages only")) + parser.add_option("--pkg-filter", help=_("Specify the package filter")) + (options, args) = parser.parse_args(args) + + if len(args) < 1: + parser.error(_("This command takes at lease one argument: a tag name or ID")) + assert False # pragma: no cover + + if len(args) > 3: + parser.error(_("This command takes at most three argument: a tag name or ID, a parent tag name or ID, and a priority")) + assert False # pragma: no cover + + activate_session(session) + + tag = session.getTag(args[0]) + if not tag: + parser.error(_("Invalid tag: %s" % args[0])) + + parent = None + priority = None + if len(args) > 1: + parent = session.getTag(args[1]) + if not parent: + parser.error(_("Invalid tag: %s" % args[1])) + if len(args) > 2: + priority = args[2] + + data = session.getInheritanceData(tag['id']) + if parent and data: + data = [datum for datum in data if datum['parent_id'] == parent['id']] + if priority and data: + data = [datum for datum in data if datum['priority'] == priority] + + if len(data) == 0: + print(_("No inheritance link found to remove. Please check your arguments")) + return 1 + elif len(data) > 1: + print(_("Multiple matches for tag.")) + if not parent: + print(_("Please specify a parent on the command line.")) + return 1 + if not priority: + print(_("Please specify a priority on the command line.")) + return 1 + print(_("Error: Key constraints may be broken. Exiting.")) + return 1 + + # len(data) == 1 + data = data[0] + + inheritanceData = session.getInheritanceData(tag['id']) + samePriority = [datum for datum in inheritanceData if datum['priority'] == options.priority] + if samePriority: + print(_("Error: There is already an active inheritance with that priority on %s, please specify a different priority with --priority.") % tag['name']) + return 1 + + new_data = data.copy() + if options.priority is not None and options.priority.isdigit(): + new_data['priority'] = int(options.priority) + if options.maxdepth is not None: + if options.maxdepth.isdigit(): + new_data['maxdepth'] = int(options.maxdepth) + elif options.maxdepth.lower() == "none": + new_data['maxdepth'] = None + else: + print(_("Invalid maxdepth: %s") % options.maxdepth) + return 1 + if options.intransitive: + new_data['intransitive'] = options.intransitive + if options.noconfig: + new_data['noconfig'] = options.noconfig + if options.pkg_filter: + new_data['pkg_filter'] = options.pkg_filter + + # find the data we want to edit and replace it + index = inheritanceData.index(data) + inheritanceData[index] = new_data + session.setInheritanceData(tag['id'], inheritanceData) + +def handle_remove_tag_inheritance(options, session, args): + """[admin] Remove a tag inheritance link""" + usage = _("usage: %prog remove-tag-inheritance tag ") + 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) < 1: + parser.error(_("This command takes at lease one argument: a tag name or ID")) + assert False # pragma: no cover + + if len(args) > 3: + parser.error(_("This command takes at most three argument: a tag name or ID, a parent tag name or ID, and a priority")) + assert False # pragma: no cover + + activate_session(session) + + tag = session.getTag(args[0]) + if not tag: + parser.error(_("Invalid tag: %s" % args[0])) + + parent = None + priority = None + if len(args) > 1: + parent = session.getTag(args[1]) + if not parent: + parser.error(_("Invalid tag: %s" % args[1])) + if len(args) > 2: + priority = args[2] + + data = session.getInheritanceData(tag['id']) + if parent and data: + data = [datum for datum in data if datum['parent_id'] == parent['id']] + if priority and data: + data = [datum for datum in data if datum['priority'] == priority] + + if len(data) == 0: + print(_("No inheritance link found to remove. Please check your arguments")) + return + elif len(data) > 1: + print(_("Multiple matches for tag.")) + if not parent: + print(_("Please specify a parent on the command line.")) + return + if not priority: + print(_("Please specify a priority on the command line.")) + return + print(_("Error: Key constrainsts may be broken. Exiting.")) + return + + # len(data) == 1 + data = data[0] + + inheritanceData = session.getInheritanceData(tag['id']) + + new_data = data.copy() + new_data['delete link'] = True + + # find the data we want to edit and replace it + index = inheritanceData.index(data) + inheritanceData[index] = new_data + session.setInheritanceData(tag['id'], inheritanceData) + +def anon_handle_show_groups(options, session, args): + "[info] Show groups data for a tag" + usage = _("usage: %prog show-groups [options] ") + usage += _("\n(Specify the --help global option for a list of other help options)") + parser = OptionParser(usage=usage) + parser.add_option("--comps", action="store_true", help=_("Print in comps format")) + parser.add_option("-x", "--expand", action="store_true", default=False, + help=_("Expand groups in comps format")) + parser.add_option("--spec", action="store_true", help=_("Print build spec")) + (options, args) = parser.parse_args(args) + if len(args) != 1: + parser.error(_("Incorrect number of arguments")) + assert False # pragma: no cover + activate_session(session) + tag = args[0] + groups = session.getTagGroups(tag) + if options.comps: + print(koji.generate_comps(groups, expand_groups=options.expand)) + elif options.spec: + print(koji.make_groups_spec(groups,name='buildgroups',buildgroup='build')) + else: + pprint.pprint(groups) + +def anon_handle_list_external_repos(options, session, args): + "[info] List external repos" + usage = _("usage: %prog list-external-repos [options]") + usage += _("\n(Specify the --help global option for a list of other help options)") + parser = OptionParser(usage=usage) + parser.add_option("--url", help=_("Select by url")) + parser.add_option("--name", help=_("Select by name")) + parser.add_option("--id", type="int", help=_("Select by id")) + parser.add_option("--tag", help=_("Select by tag")) + parser.add_option("--used", action='store_true', help=_("List which tags use the repo(s)")) + parser.add_option("--inherit", action='store_true', help=_("Follow tag inheritance when selecting by tag")) + parser.add_option("--event", type='int', metavar="EVENT#", help=_("Query at event")) + parser.add_option("--ts", type='int', metavar="TIMESTAMP", help=_("Query at timestamp")) + parser.add_option("--repo", type='int', metavar="REPO#", + help=_("Query at event corresponding to (nonexternal) repo")) + parser.add_option("--quiet", action="store_true", default=options.quiet, + help=_("Do not display the column headers")) + (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) + opts = {} + event = koji.util.eventFromOpts(session, options) + if event: + opts['event'] = event['id'] + event['timestr'] = time.asctime(time.localtime(event['ts'])) + print("Querying at event %(id)i (%(timestr)s)" % event) + if options.tag: + format = "tag" + opts['tag_info'] = options.tag + opts['repo_info'] = options.id or options.name or None + if opts['repo_info']: + if options.inherit: + parser.error(_("Can't select by repo when using --inherit")) + assert False # pragma: no cover + if options.inherit: + del opts['repo_info'] + data = session.getExternalRepoList(**opts) + format = "multitag" + else: + data = session.getTagExternalRepos(**opts) + elif options.used: + format = "multitag" + opts['repo_info'] = options.id or options.name or None + data = session.getTagExternalRepos(**opts) + else: + format = "basic" + opts['info'] = options.id or options.name or None + opts['url'] = options.url or None + data = session.listExternalRepos (**opts) + + # There are three different output formats + # 1) Listing just repo data (name, url) + # 2) Listing repo data for a tag (priority, name, url) + # 3) Listing repo data for multiple tags (tag, priority, name, url) + if format == "basic": + format = "%(name)-25s %(url)s" + header1 = "%-25s %s" % ("External repo name", "URL") + header2 = "%s %s" % ("-"*25, "-"*40) + elif format == "tag": + format = "%(priority)-3i %(external_repo_name)-25s %(url)s" + header1 = "%-3s %-25s %s" % ("Pri", "External repo name", "URL") + header2 = "%s %s %s" % ("-"*3, "-"*25, "-"*40) + elif format == "multitag": + format = "%(tag_name)-20s %(priority)-3i %(external_repo_name)s" + header1 = "%-20s %-3s %s" % ("Tag", "Pri", "External repo name") + header2 = "%s %s %s" % ("-"*20, "-"*3, "-"*25) + if not options.quiet: + print(header1) + print(header2) + for rinfo in data: + print(format % rinfo) + +def _pick_external_repo_priority(session, tag): + """pick priority after current ones, leaving space for later insertions""" + repolist = session.getTagExternalRepos(tag_info=tag) + #ordered by priority + if not repolist: + priority = 5 + else: + priority = (repolist[-1]['priority'] + 7) // 5 * 5 + #at least 3 higher than current max and a multiple of 5 + return priority + +def _parse_tagpri(tagpri): + parts = tagpri.rsplit('::', 1) + tag = parts[0] + if len(parts) == 1: + return tag, None + elif parts[1] in ('auto', '-1'): + return tag, None + else: + try: + pri = int(parts[1]) + except ValueError: + raise koji.GenericError("Invalid priority: %s" % parts[1]) + return tag, pri + +def handle_add_external_repo(options, session, args): + "[admin] Create an external repo and/or add one to a tag" + usage = _("usage: %prog add-external-repo [options] name [url]") + usage += _("\n(Specify the --help global option for a list of other help options)") + parser = OptionParser(usage=usage) + parser.add_option("-t", "--tag", action="append", metavar="TAG", + help=_("Also add repo to tag. Use tag::N to set priority")) + parser.add_option("-p", "--priority", type='int', + help=_("Set priority (when adding to tag)")) + (options, args) = parser.parse_args(args) + activate_session(session) + if len(args) == 1: + name = args[0] + rinfo = session.getExternalRepo(name, strict=True) + if not options.tag: + parser.error(_("A url is required to create an external repo entry")) + elif len(args) == 2: + name, url = args + rinfo = session.createExternalRepo(name, url) + print("Created external repo %(id)i" % rinfo) + else: + parser.error(_("Incorrect number of arguments")) + assert False # pragma: no cover + if options.tag: + for tagpri in options.tag: + tag, priority = _parse_tagpri(tagpri) + if priority is None: + if options.priority is not None: + priority = options.priority + else: + priority = _pick_external_repo_priority(session, tag) + session.addExternalRepoToTag(tag, rinfo['name'], priority) + print("Added external repo %s to tag %s (priority %i)" \ + % (rinfo['name'], tag, priority)) + +def handle_edit_external_repo(options, session, args): + "[admin] Edit data for an external repo" + usage = _("usage: %prog edit-external-repo name") + usage += _("\n(Specify the --help global option for a list of other help options)") + parser = OptionParser(usage=usage) + parser.add_option("--url", help=_("Change the url")) + parser.add_option("--name", help=_("Change the name")) + (options, args) = parser.parse_args(args) + if len(args) != 1: + parser.error(_("Incorrect number of arguments")) + parser.error(_("This command takes no arguments")) + assert False # pragma: no cover + opts = {} + if options.url: + opts['url'] = options.url + if options.name: + opts['name'] = options.name + if not opts: + parser.error(_("No changes specified")) + activate_session(session) + session.editExternalRepo(args[0], **opts) + +def handle_remove_external_repo(options, session, args): + "[admin] Remove an external repo from a tag or tags, or remove entirely" + usage = _("usage: %prog remove-external-repo repo [tag ...]") + usage += _("\n(Specify the --help global option for a list of other help options)") + parser = OptionParser(usage=usage) + parser.add_option("--alltags", action="store_true", help=_("Remove from all tags")) + parser.add_option("--force", action='store_true', help=_("Force action")) + (options, args) = parser.parse_args(args) + if len(args) < 1: + parser.error(_("Incorrect number of arguments")) + assert False # pragma: no cover + activate_session(session) + repo = args[0] + tags = args[1:] + delete = not bool(tags) + data = session.getTagExternalRepos(repo_info=repo) + current_tags = [d['tag_name'] for d in data] + if options.alltags: + delete = False + if tags: + parser.error(_("Do not specify tags when using --alltags")) + assert False # pragma: no cover + if not current_tags: + print(_("External repo %s not associated with any tags") % repo) + return 0 + tags = current_tags + if delete: + #removing entirely + if current_tags and not options.force: + print(_("Error: external repo %s used by tag(s): %s") % (repo, ', '.join(current_tags))) + print(_("Use --force to remove anyway")) + return 1 + session.deleteExternalRepo(args[0]) + else: + for tag in tags: + if not tag in current_tags: + print(_("External repo %s not associated with tag %s") % (repo, tag)) + continue + session.removeExternalRepoFromTag(tag, repo) + +# This handler is for spinning livecd images +# +def handle_spin_livecd(options, session, args): + """[build] Create a live CD image given a kickstart file""" + + # Usage & option parsing. + usage = _("usage: %prog spin-livecd [options] " + + " ") + usage += _("\n(Specify the --help global option for a list of other " + + "help options)") + parser = OptionParser(usage=usage) + parser.add_option("--wait", action="store_true", + help=_("Wait on the livecd creation, even if running in the background")) + parser.add_option("--nowait", action="store_false", dest="wait", + help=_("Don't wait on livecd creation")) + 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 livecd creation task at a lower priority")) + parser.add_option("--ksurl", metavar="SCMURL", + help=_("The URL to the SCM containing the kickstart file")) + parser.add_option("--ksversion", metavar="VERSION", + help=_("The syntax version used in the kickstart file")) + parser.add_option("--scratch", action="store_true", + help=_("Create a scratch LiveCD image")) + parser.add_option("--repo", action="append", + help=_("Specify a repo that will override the repo used to install " + + "RPMs in the LiveCD. May be used multiple times. The " + + "build tag repo associated with the target is the default.")) + parser.add_option("--release", help=_("Forcibly set the release field")) + parser.add_option("--specfile", metavar="URL", + help=_("SCM URL to spec file fragment to use to generate wrapper RPMs")) + parser.add_option("--skip-tag", action="store_true", + help=_("Do not attempt to tag package")) + (task_options, args) = parser.parse_args(args) + + # Make sure the target and kickstart is specified. + print('spin-livecd is deprecated and will be replaced with spin-livemedia') + if len(args) != 5: + parser.error(_("Five arguments are required: a name, a version, an" + + " architecture, a build target, and a relative path to" + + " a kickstart file.")) + assert False # pragma: no cover + _build_image(options, task_options, session, args, 'livecd') + + +# This handler is for spinning livemedia images +def handle_spin_livemedia(options, session, args): + """[admin] Create a livemedia image given a kickstart file""" + + # Usage & option parsing. + usage = _("usage: %prog spin-livemedia [options] " + + " ") + usage += _("\n(Specify the --help global option for a list of other " + + "help options)") + parser = OptionParser(usage=usage) + parser.add_option("--wait", action="store_true", + help=_("Wait on the livemedia creation, even if running in the background")) + parser.add_option("--nowait", action="store_false", dest="wait", + help=_("Don't wait on livemedia creation")) + 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 livemedia creation task at a lower priority")) + parser.add_option("--ksurl", metavar="SCMURL", + help=_("The URL to the SCM containing the kickstart file")) + parser.add_option("--install-tree-url", metavar="URL", + help=_("Provide the URL for the install tree")) + parser.add_option("--ksversion", metavar="VERSION", + help=_("The syntax version used in the kickstart file")) + parser.add_option("--scratch", action="store_true", + help=_("Create a scratch LiveMedia image")) + parser.add_option("--repo", action="append", + help=_("Specify a repo that will override the repo used to install " + + "RPMs in the LiveMedia. May be used multiple times. The " + + "build tag repo associated with the target is the default.")) + parser.add_option("--release", help=_("Forcibly set the release field")) + parser.add_option("--title", help=_("Set the image title (defaults to )")) + parser.add_option("--specfile", metavar="URL", + help=_("SCM URL to 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("--can-fail", action="store", dest="optional_arches", + metavar="ARCH1,ARCH2,...", default="", + help=_("List of archs which are not blocking for build (separated by commas.")) + (task_options, args) = parser.parse_args(args) + + # Make sure the target and kickstart is specified. + if len(args) != 5: + parser.error(_("Five arguments are required: a name, a version, a" + + " build target, an architecture, and a relative path to" + + " a kickstart file.")) + assert False # pragma: no cover + _build_image(options, task_options, session, args, 'livemedia') + +# This handler is for spinning appliance images +# +def handle_spin_appliance(options, session, args): + """[build] Create an appliance given a kickstart file""" + + # Usage & option parsing + usage = _("usage: %prog spin-appliance [options] " + + " ") + usage += _("\n(Specify the --help global option for a list of other " + + "help options)") + parser = OptionParser(usage=usage) + parser.add_option("--wait", action="store_true", + help=_("Wait on the appliance creation, even if running in the background")) + parser.add_option("--nowait", action="store_false", dest="wait", + help=_("Don't wait on appliance creation")) + 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 appliance creation task at a lower priority")) + parser.add_option("--ksurl", metavar="SCMURL", + help=_("The URL to the SCM containing the kickstart file")) + parser.add_option("--ksversion", metavar="VERSION", + help=_("The syntax version used in the kickstart file")) + parser.add_option("--scratch", action="store_true", + help=_("Create a scratch appliance")) + parser.add_option("--repo", action="append", + help=_("Specify a repo that will override the repo used to install " + + "RPMs in the appliance. May be used multiple times. The " + + "build tag repo associated with the target is the default.")) + parser.add_option("--release", help=_("Forcibly set the release field")) + parser.add_option("--specfile", metavar="URL", + help=_("SCM URL to 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("--vmem", metavar="VMEM", default=None, + help=_("Set the amount of virtual memory in the appliance in MB, " + + "default is 512")) + parser.add_option("--vcpu", metavar="VCPU", default=None, + help=_("Set the number of virtual cpus in the appliance, " + + "default is 1")) + parser.add_option("--format", metavar="DISK_FORMAT", default='raw', + help=_("Disk format, default is raw. Other options are qcow, " + + "qcow2, and vmx.")) + + (task_options, args) = parser.parse_args(args) + + # Make sure the target and kickstart is specified. + print('spin-appliance is deprecated and will be replaced with image-build') + if len(args) != 5: + parser.error(_("Five arguments are required: a name, a version, " + + "an architecture, a build target, and a relative path" + + " to a kickstart file.")) + assert False # pragma: no cover + _build_image(options, task_options, session, args, 'appliance') + +def handle_image_build_indirection(options, session, args): + """[build] Create a disk image using other disk images via the Indirection plugin""" + usage = _("usage: %prog image-build-indirection [base_image] " + + "[utility_image] [indirection_build_template]") + usage += _("\n %prog image-build --config FILE") + usage += _("\n\n(Specify the --help global option for a list of other " + + "help options)") + parser = OptionParser(usage=usage) + parser.add_option("--config", + help=_("Use a configuration file to define image-build options " + + "instead of command line options (they will be ignored).")) + parser.add_option("--background", action="store_true", + help=_("Run the image creation task at a lower priority")) + parser.add_option("--name", + help=_("Name of the output image")) + parser.add_option("--version", + help=_("Version of the output image")) + parser.add_option("--release", + help=_("Release of the output image")) + parser.add_option("--arch", + help=_("Architecture of the output image and input images")) + parser.add_option("--target", + help=_("Build target to use for the indirection build")) + parser.add_option("--skip-tag", action="store_true", + help=_("Do not tag the resulting build")) + parser.add_option("--base-image-task", + help=_("ID of the createImage task of the base image to be used")) + parser.add_option("--base-image-build", + help=_("NVR or build ID of the base image to be used")) + parser.add_option("--utility-image-task", + help=_("ID of the createImage task of the utility image to be used")) + parser.add_option("--utility-image-build", + help=_("NVR or build ID of the utility image to be used")) + parser.add_option("--indirection-template", + help=_("Name of the local file, or SCM file containing the template used to drive the indirection plugin")) + parser.add_option("--indirection-template-url", + help=_("SCM URL containing the template used to drive the indirection plugin")) + parser.add_option("--results-loc", + help=_("Relative path inside the working space image where the results should be extracted from")) + parser.add_option("--scratch", action="store_true", + help=_("Create a scratch image")) + parser.add_option("--wait", action="store_true", + help=_("Wait on the image creation, even if running in the background")) + parser.add_option("--noprogress", action="store_true", + help=_("Do not display progress of the upload")) + + + (task_options, args) = parser.parse_args(args) + _build_image_indirection(options, task_options, session, args) + + +def _build_image_indirection(options, task_opts, session, args): + """ + A private helper function for builds using the indirection plugin of ImageFactory + """ + + # Do some sanity checks before even attempting to create the session + if not (bool(task_opts.utility_image_task) != + bool(task_opts.utility_image_build)): + raise koji.GenericError(_("You must specify either a utility-image task or build ID/NVR")) + + if not (bool(task_opts.base_image_task) != + bool(task_opts.base_image_build)): + raise koji.GenericError(_("You must specify either a base-image task or build ID/NVR")) + + required_opts = [ 'name', 'version', 'arch', 'target', 'indirection_template', 'results_loc' ] + optional_opts = [ 'indirection_template_url', 'scratch', 'utility_image_task', 'utility_image_build', + 'base_image_task', 'base_image_build', 'release', 'skip_tag' ] + + missing = [ ] + for opt in required_opts: + if not getattr(task_opts, opt, None): + missing.append(opt) + + if len(missing) > 0: + print("Missing the following required options: %s" % ' '.join(['--%s' % o.replace('_','-') for o in missing])) + raise koji.GenericError(_("Missing required options specified above")) + + activate_session(session) + + # Set the task's priority. Users can only lower it with --background. + priority = None + if task_opts.background: + # relative to koji.PRIO_DEFAULT; higher means a "lower" priority. + priority = 5 + if _running_in_bg() or task_opts.noprogress: + callback = None + else: + callback = _progress_callback + + # We do some early sanity checking of the given target. + # Kojid gets these values again later on, but we check now as a convenience + # for the user. + + tmp_target = session.getBuildTarget(task_opts.target) + if not tmp_target: + raise koji.GenericError(_("Unknown build target: %s" % tmp_target)) + dest_tag = session.getTag(tmp_target['dest_tag']) + if not dest_tag: + raise koji.GenericError(_("Unknown destination tag: %s" % + tmp_target['dest_tag_name'])) + + # Set the architecture + task_opts.arch = koji.canonArch(task_opts.arch) + + + # Upload the indirection template file to the staging area. + # If it's a URL, it's kojid's job to go get it when it does the checkout. + if not task_opts.indirection_template_url: + if not task_opts.scratch: + # only scratch builds can omit indirection_template_url + raise koji.GenericError(_("Non-scratch builds must provide a URL for the indirection template")) + templatefile = task_opts.indirection_template + serverdir = _unique_path('cli-image-indirection') + session.uploadWrapper(templatefile, serverdir, callback=callback) + task_opts.indirection_template = os.path.join('work', serverdir, + os.path.basename(templatefile)) + print('') + + hub_opts = { } + # Just pass everything in as opts. No posiitonal arguments at all. Why not? + for opt in required_opts + optional_opts: + val = getattr(task_opts, opt, None) + # We pass these through even if they are None + # The builder code can then check if they are set without using getattr + hub_opts[opt] = val + + # finally, create the task. + task_id = session.buildImageIndirection(opts=hub_opts, + priority=priority) + + if not options.quiet: + print("Created task: %d" % task_id) + print("Task info: %s/taskinfo?taskID=%s" % (options.weburl, task_id)) + #if task_opts.wait or (task_opts.wait is None and not _running_in_bg()): + # session.logout() + # return watch_tasks(session, [task_id], quiet=options.quiet) + #else: + # return + + +def handle_image_build(options, session, args): + """[build] Create a disk image given an install tree""" + formats = ('vmdk', 'qcow', 'qcow2', 'vdi', 'vpc', 'rhevm-ova', + 'vsphere-ova', 'vagrant-virtualbox', 'vagrant-libvirt', + 'vagrant-vmware-fusion', 'vagrant-hyperv', 'docker', 'raw-xz', + 'liveimg-squashfs', 'tar-gz') + usage = _("usage: %prog image-build [options] " + + " [...]") + usage += _("\n %prog image-build --config FILE") + usage += _("\n\n(Specify the --help global option for a list of other " + + "help options)") + parser = OptionParser(usage=usage) + parser.add_option("--background", action="store_true", + help=_("Run the image creation task at a lower priority")) + parser.add_option("--config", + help=_("Use a configuration file to define image-build options " + + "instead of command line options (they will be ignored).")) + parser.add_option("--disk-size", default=10, + help=_("Set the disk device size in gigabytes")) + parser.add_option("--distro", + help=_("specify the RPM based distribution the image will be based " + + "on with the format RHEL-X.Y, CentOS-X.Y, SL-X.Y, or Fedora-NN. " + + "The packages for the Distro you choose must have been built " + + "in this system.")) + parser.add_option("--format", default=[], action="append", + help=_("Convert results to one or more formats " + + "(%s), this option may be used " % ', '.join(formats) + + "multiple times. By default, specifying this option will " + + "omit the raw disk image (which is 10G in size) from the " + + "build results. If you really want it included with converted " + + "images, pass in 'raw' as an option.")) + parser.add_option("--kickstart", help=_("Path to a local kickstart file")) + parser.add_option("--ksurl", metavar="SCMURL", + help=_("The URL to the SCM containing the kickstart file")) + parser.add_option("--ksversion", metavar="VERSION", + help=_("The syntax version used in the kickstart file")) + parser.add_option("--noprogress", action="store_true", + help=_("Do not display progress of the upload")) + parser.add_option("--nowait", action="store_false", dest="wait", + help=_("Don't wait on image creation")) + parser.add_option("--ova-option", action="append", + help=_("Override a value in the OVA description XML. Provide a value " + + "in a name=value format, such as 'ovf_memory_mb=6144'")) + parser.add_option("--factory-parameter", nargs=2, action="append", + help=_("Pass a parameter to Image Factory. The results are highly specific " + + "to the image format being created. This is a two argument parameter " + + "that can be specified an arbitrary number of times. For example: " + "--factory-parameter docker_cmd '[ \"/bin/echo Hello World\" ]'")) + parser.add_option("--release", help=_("Forcibly set the release field")) + parser.add_option("--repo", action="append", + help=_("Specify a repo that will override the repo used to install " + + "RPMs in the image. May be used multiple times. The " + + "build tag repo associated with the target is the default.")) + parser.add_option("--scratch", action="store_true", + help=_("Create a scratch image")) + parser.add_option("--skip-tag", action="store_true", + help=_("Do not attempt to tag package")) + parser.add_option("--can-fail", action="store", dest="optional_arches", + metavar="ARCH1,ARCH2,...", default="", + help=_("List of archs which are not blocking for build (separated by commas.")) + parser.add_option("--specfile", metavar="URL", + help=_("SCM URL to spec file fragment to use to generate wrapper RPMs")) + parser.add_option("--wait", action="store_true", + help=_("Wait on the image creation, even if running in the background")) + + (task_options, args) = parser.parse_args(args) + + if task_options.config: + if not os.path.exists(task_options.config): + parser.error(_("%s not found!" % task_options.config)) + section = 'image-build' + config = six.moves.configparser.ConfigParser() + conf_fd = open(task_options.config) + config.readfp(conf_fd) + conf_fd.close() + if not config.has_section(section): + parser.error(_("single section called [%s] is required" % section)) + # pluck out the positional arguments first + args = [] + for arg in ('name', 'version', 'target', 'install_tree'): + args.append(config.get(section, arg)) + config.remove_option(section, arg) + args.extend(config.get(section, 'arches').split(',')) + config.remove_option(section, 'arches') + # turn comma-separated options into lists + for arg in ('repo', 'format'): + if config.has_option(section, arg): + setattr(task_options, arg, config.get(section, arg).split(',')) + config.remove_option(section, arg) + if config.has_option(section, 'can_fail'): + setattr(task_options, 'optional_arches', config.get(section, 'can_fail').split(',')) + config.remove_option(section, 'can_fail') + # handle everything else + for k, v in config.items(section): + setattr(task_options, k, v) + + # ova-options belong in their own section + section = 'ova-options' + if config.has_section(section): + task_options.ova_option = [] + for k, v in config.items(section): + task_options.ova_option.append('%s=%s' % (k, v)) + + # as do factory-parameters + section = 'factory-parameters' + if config.has_section(section): + task_options.factory_parameter = [ ] + for k, v in config.items(section): + # We do this, rather than a dict, to match what argparse spits out + task_options.factory_parameter.append( (k, v) ) + + else: + if len(args) < 5: + parser.error(_("At least five arguments are required: a name, " + + "a version, a build target, a URL to an " + + "install tree, and 1 or more architectures.")) + if not task_options.ksurl and not task_options.kickstart: + parser.error(_('You must specify --kickstart')) + if not task_options.distro: + parser.error( + _("You must specify --distro. Examples: Fedora-16, RHEL-6.4, " + + "SL-6.4 or CentOS-6.4")) + _build_image_oz(options, task_options, session, args) + +def _build_image(options, task_opts, session, args, img_type): + """ + A private helper function that houses common CLI code for building + images with chroot-based tools. + """ + + if img_type not in ('livecd', 'appliance', 'livemedia'): + raise koji.GenericError('Unrecognized image type: %s' % img_type) + activate_session(session) + + # Set the task's priority. Users can only lower it with --background. + priority = None + if task_opts.background: + # relative to koji.PRIO_DEFAULT; higher means a "lower" priority. + priority = 5 + if _running_in_bg() or task_opts.noprogress: + callback = None + else: + callback = _progress_callback + + # We do some early sanity checking of the given target. + # Kojid gets these values again later on, but we check now as a convenience + # for the user. + target = args[2] + tmp_target = session.getBuildTarget(target) + if not tmp_target: + raise koji.GenericError(_("Unknown build target: %s" % target)) + dest_tag = session.getTag(tmp_target['dest_tag']) + if not dest_tag: + raise koji.GenericError(_("Unknown destination tag: %s" % + tmp_target['dest_tag_name'])) + + # Set the architecture + if img_type == 'livemedia': + # livemedia accepts multiple arches + arch = [koji.canonArch(a) for a in args[3].split(",")] + else: + arch = koji.canonArch(args[3]) + + # Upload the KS file to the staging area. + # If it's a URL, it's kojid's job to go get it when it does the checkout. + ksfile = args[4] + + if not task_opts.ksurl: + serverdir = _unique_path('cli-' + img_type) + session.uploadWrapper(ksfile, serverdir, callback=callback) + ksfile = os.path.join(serverdir, os.path.basename(ksfile)) + print('') + + hub_opts = {} + if hasattr(task_opts, 'optional_arches'): + hub_opts['optional_arches'] = task_opts.optional_arches.split(',') + passthru_opts = [ + 'format', 'install_tree_url', 'isoname', 'ksurl', + 'ksversion', 'release', 'repo', 'scratch', 'skip_tag', + 'specfile', 'title', 'vcpu', 'vmem', 'optional_arches', + ] + for opt in passthru_opts: + val = getattr(task_opts, opt, None) + if val is not None: + hub_opts[opt] = val + + # finally, create the task. + task_id = session.buildImage(args[0], args[1], arch, target, ksfile, + img_type, opts=hub_opts, priority=priority) + + if not options.quiet: + print("Created task: %d" % task_id) + print("Task info: %s/taskinfo?taskID=%s" % (options.weburl, task_id)) + if task_opts.wait or (task_opts.wait is None and not _running_in_bg()): + session.logout() + return watch_tasks(session, [task_id], quiet=options.quiet) + else: + return + +def _build_image_oz(options, task_opts, session, args): + """ + A private helper function that houses common CLI code for building + images with Oz and ImageFactory + """ + activate_session(session) + + # Set the task's priority. Users can only lower it with --background. + priority = None + if task_opts.background: + # relative to koji.PRIO_DEFAULT; higher means a "lower" priority. + priority = 5 + if _running_in_bg() or task_opts.noprogress: + callback = None + else: + callback = _progress_callback + + # We do some early sanity checking of the given target. + # Kojid gets these values again later on, but we check now as a convenience + # for the user. + target = args[2] + tmp_target = session.getBuildTarget(target) + if not tmp_target: + raise koji.GenericError(_("Unknown build target: %s" % target)) + dest_tag = session.getTag(tmp_target['dest_tag']) + if not dest_tag: + raise koji.GenericError(_("Unknown destination tag: %s" % + tmp_target['dest_tag_name'])) + + # Set the architectures + arches = [] + for arch in args[4:]: + arches.append(koji.canonArch(arch)) + + # Upload the KS file to the staging area. + # If it's a URL, it's kojid's job to go get it when it does the checkout. + if not task_opts.ksurl: + if not task_opts.scratch: + # only scratch builds can omit ksurl + raise koji.GenericError(_("Non-scratch builds must provide ksurl")) + ksfile = task_opts.kickstart + serverdir = _unique_path('cli-image') + session.uploadWrapper(ksfile, serverdir, callback=callback) + task_opts.kickstart = os.path.join('work', serverdir, + os.path.basename(ksfile)) + print('') + + hub_opts = {} + for opt in ('ksurl', 'ksversion', 'kickstart', 'scratch', 'repo', + 'release', 'skip_tag', 'specfile', 'distro', 'format', + 'disk_size', 'ova_option', 'factory_parameter', + 'optional_arches'): + val = getattr(task_opts, opt, None) + if val is not None: + hub_opts[opt] = val + + # finally, create the task. + task_id = session.buildImageOz(args[0], args[1], arches, target, args[3], + opts=hub_opts, priority=priority) + + if not options.quiet: + print("Created task: %d" % task_id) + print("Task info: %s/taskinfo?taskID=%s" % (options.weburl, task_id)) + if task_opts.wait or (task_opts.wait is None and not _running_in_bg()): + session.logout() + return watch_tasks(session, [task_id], quiet=options.quiet) + else: + return + +def handle_win_build(options, session, args): + """[build] Build a Windows package from source""" + # Usage & option parsing + usage = _("usage: %prog win-build [options] target URL VM") + usage += _("\n(Specify the --help global option for a list of other " + + "help options)") + parser = OptionParser(usage=usage) + parser.add_option("--winspec", metavar="URL", + help=_("SCM URL to retrieve the build descriptor from. " + \ + "If not specified, the winspec must be in the root directory " + \ + "of the source repository.")) + parser.add_option("--patches", metavar="URL", + help=_("SCM URL of a directory containing patches to apply " + \ + "to the sources before building")) + parser.add_option("--cpus", type="int", + help=_("Number of cpus to allocate to the build VM " + \ + "(requires admin access)")) + parser.add_option("--mem", type="int", + help=_("Amount of memory (in megabytes) to allocate to the build VM " + \ + "(requires admin access)")) + parser.add_option("--static-mac", action="store_true", + help=_("Retain the original MAC address when cloning the VM")) + parser.add_option("--specfile", metavar="URL", + help=_("SCM URL of a spec file fragment to use to generate wrapper RPMs")) + parser.add_option("--scratch", action="store_true", + help=_("Perform a scratch build")) + parser.add_option("--repo-id", type="int", help=_("Use a specific repo")) + parser.add_option("--skip-tag", action="store_true", + help=_("Do not attempt to tag package")) + parser.add_option("--background", action="store_true", + help=_("Run the build at a lower priority")) + 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) + (build_opts, args) = parser.parse_args(args) + if len(args) != 3: + parser.error(_("Exactly three arguments (a build target, a SCM URL, and a VM name) are required")) + assert False # pragma: no cover + 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'])) + scmurl = args[1] + vm_name = args[2] + opts = {} + for key in ('winspec', 'patches', 'cpus', 'mem', 'static_mac', + 'specfile', 'scratch', 'repo_id', 'skip_tag'): + 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 + task_id = session.winBuild(vm_name, scmurl, 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_free_task(options, session, args): + "[admin] Free a task" + usage = _("usage: %prog free-task [options] [ ...]") + usage += _("\n(Specify the --help global option for a list of other help options)") + parser = OptionParser(usage=usage) + (options, args) = parser.parse_args(args) + activate_session(session) + tlist = [] + for task_id in args: + try: + tlist.append(int(task_id)) + except ValueError: + parser.error(_("task-id must be an integer")) + assert False # pragma: no cover + for task_id in tlist: + session.freeTask(task_id) + +def handle_cancel(options, session, args): + "[build] Cancel tasks and/or builds" + usage = _("usage: %prog cancel [options] [ ...]") + usage += _("\n(Specify the --help global option for a list of other help options)") + parser = OptionParser(usage=usage) + parser.add_option("--justone", action="store_true", help=_("Do not cancel subtasks")) + parser.add_option("--full", action="store_true", help=_("Full cancellation (admin only)")) + parser.add_option("--force", action="store_true", help=_("Allow subtasks with --full")) + (options, args) = parser.parse_args(args) + if len(args) == 0: + parser.error(_("You must specify at least one task id or build")) + assert False # pragma: no cover + activate_session(session) + tlist = [] + blist = [] + for arg in args: + try: + tlist.append(int(arg)) + except ValueError: + try: + koji.parse_NVR(arg) + blist.append(arg) + except koji.GenericError: + parser.error(_("please specify only task ids (integer) or builds (n-v-r)")) + assert False # pragma: no cover + if tlist: + opts = {} + remote_fn = session.cancelTask + if options.justone: + opts['recurse'] = False + elif options.full: + remote_fn = session.cancelTaskFull + if options.force: + opts['strict'] = False + for task_id in tlist: + remote_fn(task_id, **opts) + for build in blist: + session.cancelBuild(build) + +def handle_set_task_priority(options, session, args): + "[admin] Set task priority" + usage = _("usage: %prog set-task-priority [options] --priority= [task-id]...") + usage += _("\n(Specify the --help global option for a list of other help options)") + parser = OptionParser(usage=usage) + parser.add_option("--priority", type="int", help=_("New priority")) + parser.add_option("--recurse", action="store_true", default=False, help=_("Change priority of child tasks as well")) + (options, args) = parser.parse_args(args) + if len(args) == 0: + parser.error(_("You must specify at least one task id")) + assert False # pragma: no cover + + if options.priority is None: + parser.error(_("You must specify --priority")) + assert False # pragma: no cover + try: + tasks = [int(a) for a in args] + except ValueError: + parser.error(_("Task numbers must be integers")) + + activate_session(session) + + for task_id in tasks: + session.setTaskPriority(task_id, options.priority, options.recurse) + +def _list_tasks(options, session): + "Retrieve a list of tasks" + + callopts = { + 'state' : [koji.TASK_STATES[s] for s in ('FREE', 'OPEN', 'ASSIGNED')], + 'decode' : True, + } + + if options.mine: + user = session.getLoggedInUser() + if not user: + print("Unable to determine user") + sys.exit(1) + callopts['owner'] = user['id'] + if options.user: + user = session.getUser(options.user) + if not user: + print("No such user: %s" % options.user) + sys.exit(1) + callopts['owner'] = user['id'] + if options.arch: + callopts['arch'] = parse_arches(options.arch, to_list=True) + if options.method: + callopts['method'] = options.method + if options.channel: + chan = session.getChannel(options.channel) + if not chan: + print("No such channel: %s" % options.channel) + sys.exit(1) + callopts['channel_id'] = chan['id'] + if options.host: + host = session.getHost(options.host) + if not host: + print("No such host: %s" % options.host) + sys.exit(1) + callopts['host_id'] = host['id'] + + qopts = {'order' : 'priority,create_time'} + tasklist = session.listTasks(callopts, qopts) + tasks = dict([(x['id'], x) for x in tasklist]) + + #thread the tasks + for t in tasklist: + if t['parent'] is not None: + parent = tasks.get(t['parent']) + if parent: + parent.setdefault('children',[]) + parent['children'].append(t) + t['sub'] = True + + return tasklist + + +def handle_list_tasks(options, session, args): + "[info] Print the list of tasks" + usage = _("usage: %prog list-tasks [options]") + usage += _("\n(Specify the --help global option for a list of other help options)") + parser = OptionParser(usage=usage) + parser.add_option("--mine", action="store_true", help=_("Just print your tasks")) + parser.add_option("--user", help=_("Only tasks for this user")) + parser.add_option("--arch", help=_("Only tasks for this architecture")) + parser.add_option("--method", help=_("Only tasks of this method")) + parser.add_option("--channel", help=_("Only tasks in this channel")) + parser.add_option("--host", help=_("Only tasks for this host")) + parser.add_option("--quiet", action="store_true", help=_("Do not display the column headers"), default=options.quiet) + (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) + tasklist = _list_tasks(options, session) + if not tasklist: + print("(no tasks)") + return + if not options.quiet: + print_task_headers() + for t in tasklist: + if t.get('sub'): + # this subtask will appear under another task + continue + print_task_recurse(t) + +def handle_set_pkg_arches(options, session, args): + "[admin] Set the list of extra arches for a package" + usage = _("usage: %prog set-pkg-arches [options] arches 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=_("Force operation")) + (options, args) = parser.parse_args(args) + if len(args) < 3: + parser.error(_("Please specify an archlist, a tag, and at least one package")) + assert False # pragma: no cover + activate_session(session) + arches = parse_arches(args[0]) + tag = args[1] + for package in args[2:]: + #really should implement multicall... + session.packageListSetArches(tag,package,arches,force=options.force) + +def handle_set_pkg_owner(options, session, args): + "[admin] Set the owner for a package" + usage = _("usage: %prog set-pkg-owner [options] owner 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=_("Force operation")) + (options, args) = parser.parse_args(args) + if len(args) < 3: + parser.error(_("Please specify an owner, a tag, and at least one package")) + assert False # pragma: no cover + activate_session(session) + owner = args[0] + tag = args[1] + for package in args[2:]: + #really should implement multicall... + session.packageListSetOwner(tag,package,owner,force=options.force) + +def handle_set_pkg_owner_global(options, session, args): + "[admin] Set the owner for a package globally" + usage = _("usage: %prog set-pkg-owner-global [options] owner package [package2 ...]") + usage += _("\n(Specify the --help global option for a list of other help options)") + parser = OptionParser(usage=usage) + parser.add_option("--verbose", action='store_true', help=_("List changes")) + parser.add_option("--test", action='store_true', help=_("Test mode")) + parser.add_option("--old-user", "--from", action="store", help=_("Only change ownership for packages belonging to this user")) + (options, args) = parser.parse_args(args) + if options.old_user: + if len(args) < 1: + parser.error(_("Please specify an owner")) + assert False # pragma: no cover + elif len(args) < 2: + parser.error(_("Please specify an owner and at least one package")) + assert False # pragma: no cover + activate_session(session) + owner = args[0] + packages = args[1:] + user = session.getUser(owner) + if not user: + print("No such user: %s" % owner) + return 1 + opts = {'with_dups' : True} + old_user = None + if options.old_user: + old_user = session.getUser(options.old_user) + if not old_user: + print("No such user: %s" % options.old_user) + return 1 + opts['userID'] = old_user['id'] + to_change = [] + for package in packages: + entries = session.listPackages(pkgID=package, **opts) + if not entries: + print("No data for package %s" % package) + continue + to_change.extend(entries) + if not packages and options.old_user: + entries = session.listPackages(**opts) + if not entries: + print("No data for user %s" % old_user['name']) + return 1 + to_change.extend(entries) + for entry in to_change: + if user['id'] == entry['owner_id']: + if options.verbose: + print("Preserving owner=%s for package %s in tag %s" \ + % (user['name'], package, entry['tag_name'])) + else: + if options.test: + print("Would have changed owner for %s in tag %s: %s -> %s" \ + % (entry['package_name'], entry['tag_name'], entry['owner_name'], user['name'])) + continue + if options.verbose: + print("Changing owner for %s in tag %s: %s -> %s" \ + % (entry['package_name'], entry['tag_name'], entry['owner_name'], user['name'])) + session.packageListSetOwner(entry['tag_id'], entry['package_name'], user['id']) + +def anon_handle_watch_task(options, session, args): + "[monitor] Track progress of particular tasks" + usage = _("usage: %prog watch-task [options] [...]") + usage += _("\n(Specify the --help global option for a list of other help options)") + parser = OptionParser(usage=usage) + parser.add_option("--quiet", action="store_true", + help=_("Do not print the task information"), default=options.quiet) + parser.add_option("--mine", action="store_true", help=_("Just watch your tasks")) + parser.add_option("--user", help=_("Only tasks for this user")) + parser.add_option("--arch", help=_("Only tasks for this architecture")) + parser.add_option("--method", help=_("Only tasks of this method")) + parser.add_option("--channel", help=_("Only tasks in this channel")) + parser.add_option("--host", help=_("Only tasks for this host")) + (options, args) = parser.parse_args(args) + selection = (options.mine or + options.user or + options.arch or + options.method or + options.channel or + options.host) + if args and selection: + parser.error(_("Selection options cannot be combined with a task list")) + + activate_session(session) + if selection: + tasks = [task['id'] for task in _list_tasks(options, session)] + if not tasks: + print("(no tasks)") + return + else: + tasks = [] + for task in args: + try: + tasks.append(int(task)) + except ValueError: + parser.error(_("task id must be an integer")) + if not tasks: + parser.error(_("at least one task id must be specified")) + + return watch_tasks(session, tasks, quiet=options.quiet) + +def anon_handle_watch_logs(options, session, args): + "[monitor] Watch logs in realtime" + usage = _("usage: %prog watch-logs [options] [...]") + usage += _("\n(Specify the --help global option for a list of other help options)") + parser = OptionParser(usage=usage) + parser.add_option("--log", help=_("Watch only a specific log")) + (options, args) = parser.parse_args(args) + activate_session(session) + + tasks = [] + for task in args: + try: + tasks.append(int(task)) + except ValueError: + parser.error(_("task id must be an integer")) + if not tasks: + parser.error(_("at least one task id must be specified")) + + watch_logs(session, tasks, options) + +def handle_make_task(opts, session, args): + "[admin] Create an arbitrary task" + usage = _("usage: %prog make-task [options] [...]") + usage += _("\n(Specify the --help global option for a list of other help options)") + parser = OptionParser(usage=usage) + parser.add_option("--channel", help=_("set channel")) + parser.add_option("--priority", help=_("set priority")) + parser.add_option("--watch", action="store_true", help=_("watch the task")) + parser.add_option("--arch", help=_("set arch")) + (options, args) = parser.parse_args(args) + activate_session(session) + + taskopts = {} + for key in ('channel','priority','arch'): + value = getattr(options,key,None) + if value is not None: + taskopts[key] = value + task_id = session.makeTask(method=args[0], + arglist=list(map(arg_filter,args[1:])), + **taskopts) + print("Created task id %d" % task_id) + if _running_in_bg() or not options.watch: + return + else: + session.logout() + return watch_tasks(session, [task_id], quiet=opts.quiet) + +def handle_tag_build(opts, session, args): + "[bind] Apply a tag to one or more builds" + usage = _("usage: %prog tag-build [options] [...]") + 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 operation")) + parser.add_option("--nowait", action="store_true", help=_("Do not wait on task")) + (options, args) = parser.parse_args(args) + if len(args) < 2: + parser.error(_("This command takes at least two arguments: a tag name/ID and one or more package n-v-r's")) + assert False # pragma: no cover + activate_session(session) + tasks = [] + for pkg in args[1:]: + task_id = session.tagBuild(args[0], pkg, force=options.force) + #XXX - wait on task + tasks.append(task_id) + print("Created task %d" % task_id) + if _running_in_bg() or options.nowait: + return + else: + session.logout() + return watch_tasks(session,tasks,quiet=opts.quiet) + +def handle_move_build(opts, session, args): + "[bind] 'Move' one or more builds between tags" + usage = _("usage: %prog move-build [options] [...]") + 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 operation")) + parser.add_option("--nowait", action="store_true", help=_("do not wait on tasks")) + parser.add_option("--all", action="store_true", help=_("move all instances of a package, 's are package names")) + (options, args) = parser.parse_args(args) + if len(args) < 3: + if options.all: + parser.error(_("This command, with --all, takes at least three arguments: two tags and one or more package names")) + else: + parser.error(_("This command takes at least three arguments: two tags and one or more package n-v-r's")) + assert False # pragma: no cover + activate_session(session) + tasks = [] + builds = [] + + if options.all: + for arg in args[2:]: + pkg = session.getPackage(arg) + if not pkg: + print(_("Invalid package name %s, skipping." % arg)) + continue + tasklist = session.moveAllBuilds(args[0], args[1], arg, options.force) + tasks.extend(tasklist) + else: + for arg in args[2:]: + build = session.getBuild(arg) + if not build: + print(_("Invalid build %s, skipping." % arg)) + continue + if not build in builds: + builds.append(build) + + for build in builds: + task_id = session.moveBuild(args[0], args[1], build['id'], options.force) + tasks.append(task_id) + print("Created task %d, moving %s" % (task_id, koji.buildLabel(build))) + if _running_in_bg() or options.nowait: + return + else: + session.logout() + return watch_tasks(session, tasks, quiet=opts.quiet) + +def handle_untag_build(options, session, args): + "[bind] Remove a tag from one or more builds" + usage = _("usage: %prog untag-build [options] [...]") + usage += _("\n(Specify the --help global option for a list of other help options)") + parser = OptionParser(usage=usage) + parser.add_option("--all", action="store_true", help=_("untag all versions of the package in this tag")) + parser.add_option("--non-latest", action="store_true", help=_("untag all versions of the package in this tag except the latest")) + parser.add_option("-n", "--test", action="store_true", help=_("test mode")) + parser.add_option("-v", "--verbose", action="store_true", help=_("print details")) + parser.add_option("--force", action="store_true", help=_("force operation")) + (options, args) = parser.parse_args(args) + if options.non_latest and options.force: + if len(args) < 1: + parser.error(_("Please specify a tag")) + assert False # pragma: no cover + elif len(args) < 2: + parser.error(_("This command takes at least two arguments: a tag name/ID and one or more package n-v-r's")) + assert False # pragma: no cover + activate_session(session) + tag = session.getTag(args[0]) + if not tag: + parser.error(_("Invalid tag: %s" % args[0])) + if options.all: + builds = [] + for pkg in args[1:]: + builds.extend(session.listTagged(args[0], package=pkg)) + elif options.non_latest: + if options.force and len(args) == 1: + tagged = session.listTagged(args[0]) + else: + tagged = [] + for pkg in args[1:]: + tagged.extend(session.listTagged(args[0], package=pkg)) + # listTagged orders entries latest first + seen_pkg = {} + builds = [] + for binfo in tagged: + if binfo['name'] not in seen_pkg: + #latest for this package + if options.verbose: + print(_("Leaving latest build for package %(name)s: %(nvr)s") % binfo) + else: + builds.append(binfo) + seen_pkg[binfo['name']] = 1 + else: + tagged = session.listTagged(args[0]) + idx = dict([(b['nvr'], b) for b in tagged]) + builds = [] + for nvr in args[1:]: + binfo = idx.get(nvr) + if binfo: + builds.append(binfo) + else: + # not in tag, see if it even exists + binfo = session.getBuild(nvr) + if not binfo: + print(_("No such build: %s") % nvr) + else: + print(_("Build %s not in tag %s") % (nvr, tag['name'])) + if not options.force: + return 1 + builds.reverse() + for binfo in builds: + if options.test: + print(_("would have untagged %(nvr)s") % binfo) + else: + if options.verbose: + print(_("untagging %(nvr)s") % binfo) + session.untagBuild(tag['name'], binfo['nvr'], force=options.force) + +def handle_unblock_pkg(options, session, args): + "[admin] Unblock a package in the listing for tag" + usage = _("usage: %prog unblock-pkg [options] tag package [package2 ...]") + 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] + for package in args[1:]: + #really should implement multicall... + session.packageListUnblock(tag,package) + +def anon_handle_download_build(options, session, args): + "[download] Download a built package" + usage = _("usage: %prog download-build [options] ") + usage += _("\n(Specify the --help global option for a list of other help options)") + parser = OptionParser(usage=usage) + parser.add_option("--arch", "-a", dest="arches", metavar="ARCH", action="append", default=[], + help=_("Only download packages for this arch (may be used multiple times)")) + parser.add_option("--type", help=_("Download archives of the given type, rather than rpms (maven, win, or image)")) + parser.add_option("--latestfrom", dest="latestfrom", help=_("Download the latest build from this tag")) + parser.add_option("--debuginfo", action="store_true", help=_("Also download -debuginfo rpms")) + parser.add_option("--task-id", action="store_true", help=_("Interperet id as a task id")) + parser.add_option("--rpm", action="store_true", help=_("Download the given rpm")) + parser.add_option("--key", help=_("Download rpms signed with the given key")) + parser.add_option("--topurl", metavar="URL", default=options.topurl, + help=_("URL under which Koji files are accessible")) + parser.add_option("-q", "--quiet", action="store_true", help=_("Do not display progress meter"), + default=options.quiet) + (suboptions, args) = parser.parse_args(args) + if len(args) < 1: + parser.error(_("Please specify a package N-V-R or build ID")) + assert False # pragma: no cover + elif len(args) > 1: + parser.error(_("Only a single package N-V-R or build ID may be specified")) + assert False # pragma: no cover + + activate_session(session) + build = args[0] + + if build.isdigit(): + if suboptions.latestfrom: + print("--latestfrom not compatible with build IDs, specify a package name.") + return 1 + build = int(build) + if suboptions.task_id: + builds = session.listBuilds(taskID=build) + if not builds: + print("No associated builds for task %s" % build) + return 1 + build = builds[0]['build_id'] + + if suboptions.latestfrom: + # We want the latest build, not a specific build + try: + builds = session.listTagged(suboptions.latestfrom, latest=True, package=build, type=suboptions.type) + except koji.GenericError as data: + print("Error finding latest build: %s" % data) + return 1 + if not builds: + print("%s has no builds of %s" % (suboptions.latestfrom, build)) + return 1 + info = builds[0] + elif suboptions.rpm: + rpminfo = session.getRPM(build) + if rpminfo is None: + print("No such rpm: %s" % build) + return 1 + info = session.getBuild(rpminfo['build_id']) + else: + # if we're given an rpm name without --rpm, download the containing build + try: + nvra = koji.parse_NVRA(build) + rpminfo = session.getRPM(build) + build = rpminfo['build_id'] + except Exception: + pass + info = session.getBuild(build) + + if info is None: + print("No such build: %s" % build) + return 1 + + if not suboptions.topurl: + print("You must specify --topurl to download files") + return 1 + pathinfo = koji.PathInfo(topdir=suboptions.topurl) + + urls = [] + if suboptions.type: + archives = session.listArchives(buildID=info['id'], type=suboptions.type) + if not archives: + print("No %s archives available for %s" % (suboptions.type, koji.buildLabel(info))) + return 1 + if suboptions.type == 'maven': + for archive in archives: + url = pathinfo.mavenbuild(info) + '/' + pathinfo.mavenfile(archive) + urls.append((url, pathinfo.mavenfile(archive))) + elif suboptions.type == 'win': + for archive in archives: + url = pathinfo.winbuild(info) + '/' + pathinfo.winfile(archive) + urls.append((url, pathinfo.winfile(archive))) + elif suboptions.type == 'image': + if not suboptions.topurl: + print("You must specify --topurl to download images") + return 1 + pi = koji.PathInfo(topdir=suboptions.topurl) + for archive in archives: + url = '%s/%s' % (pi.imagebuild(info), archive['filename']) + urls.append((url, archive['filename'])) + else: + # can't happen + assert False # pragma: no cover + else: + arches = suboptions.arches + if len(arches) == 0: + arches = None + if suboptions.rpm: + rpms = [rpminfo] + else: + rpms = session.listRPMs(buildID=info['id'], arches=arches) + if not rpms: + if arches: + print("No %s packages available for %s" % (" or ".join(arches), koji.buildLabel(info))) + else: + print("No packages available for %s" % koji.buildLabel(info)) + return 1 + for rpm in rpms: + if not suboptions.debuginfo and koji.is_debuginfo(rpm['name']): + continue + if suboptions.key: + fname = pathinfo.signed(rpm, suboptions.key) + else: + fname = pathinfo.rpm(rpm) + url = pathinfo.build(info) + '/' + fname + urls.append((url, os.path.basename(fname))) + + def _progress(download_t, download_d, upload_t, upload_d): + if download_t == 0: + percent_done = 0.0 + else: + percent_done = float(download_d)/float(download_t) + percent_done_str = "%02d%%" % (percent_done * 100) + data_done = _format_size(download_d) + + sys.stdout.write("[% -36s] % 4s % 10s\r" % ('='*(int(percent_done * 36)), percent_done_str, data_done)) + sys.stdout.flush() + + for url, relpath in urls: + if '/' in relpath: + koji.ensuredir(os.path.dirname(relpath)) + print(relpath) + c = pycurl.Curl() + c.setopt(c.URL, url) + c.setopt(c.WRITEDATA, open(relpath, 'wb')) + if not suboptions.quiet: + c.setopt(c.NOPROGRESS, False) + c.setopt(c.XFERINFOFUNCTION, _progress) + c.perform() + print('') + + +def anon_handle_download_logs(options, session, args): + "[download] Download a logs for package" + + FAIL_LOG = "task_failed.log" + usage = _("usage: %prog download-logs [options] [ ...]") + usage += _("\n %prog download-logs [options] --nvr [ ...]") + usage += _("\n(Specify the --help global option for a list of other help options)") + usage += _("\nCreates special log with name %s if task failed." % FAIL_LOG) + parser = OptionParser(usage=usage) + parser.add_option("-r", "--recurse", action="store_true", + help=_("Process children of this task as well")) + parser.add_option("--nvr", action="store_true", + help=_("Get logs from n-v-r")) + parser.add_option("-m", "--match", action="append", metavar="PATTERN", + help=_("Get only log matching PATTERN. May be used multiple times.")) + parser.add_option("-c", "--continue", action="store_true", dest="cont", + help=_("Continue previous download")) + parser.add_option("-d", "--dir", metavar="DIRECTORY", default='kojilogs', + help=_("Write logs to DIRECTORY")) + (suboptions, args) = parser.parse_args(args) + + if len(args) < 1: + parser.error(_("Please specify at least one task id or n-v-r")) + + def write_fail_log(task_log_dir, task_id): + """Gets output only from failed tasks""" + try: + result = session.getTaskResult(task_id) + # with current code, failed task results should always be faults, + # but that could change in the future + content = pprint.pformat(result) + except (xmlrpclib.Fault, koji.GenericError): + etype, e = sys.exc_info()[:2] + content = ''.join(traceback.format_exception_only(etype, e)) + full_filename = os.path.normpath(os.path.join(task_log_dir, FAIL_LOG)) + koji.ensuredir(os.path.dirname(full_filename)) + sys.stdout.write("Writing: %s\n" % full_filename) + open(full_filename, 'w').write(content) + + def download_log(task_log_dir, task_id, filename, blocksize=102400, volume=None): + # Create directories only if there is any log file to write to + # For each non-default volume create special sub-directory + if volume not in (None, 'DEFAULT'): + full_filename = os.path.normpath(os.path.join(task_log_dir, volume, filename)) + else: + full_filename = os.path.normpath(os.path.join(task_log_dir, filename)) + koji.ensuredir(os.path.dirname(full_filename)) + contents = 'IGNORE ME!' + if suboptions.cont and os.path.exists(full_filename): + sys.stdout.write("Continuing: %s\n" % full_filename) + fd = open(full_filename, 'ab') + offset = fd.tell() + else: + sys.stdout.write("Downloading: %s\n" % full_filename) + fd = open(full_filename, 'wb') + offset = 0 + try: + while contents: + contents = session.downloadTaskOutput(task_id, filename, offset=offset, size=blocksize, volume=volume) + offset += len(contents) + if contents: + fd.write(contents) + finally: + fd.close() + + def save_logs(task_id, match, parent_dir='.', recurse=True): + assert task_id == int(task_id), "Task id must be number: %r" % task_id + task_info = session.getTaskInfo(task_id) + if task_info is None: + error(_("No such task id: %i" % task_id)) + files = list_task_output_all_volumes(session, task_id) + logs = [] # list of tuples (filename, volume) + for filename in files: + if not filename.endswith(".log"): + continue + if match and not koji.util.multi_fnmatch(filename, match): + continue + logs += [(filename, volume) for volume in files[filename]] + + task_log_dir = os.path.join(parent_dir, + "%s-%s" % (task_info["arch"], task_id)) + + count = 0 + state = koji.TASK_STATES[task_info['state']] + if state == 'FAILED': + if not match or koji.util.multi_fnmatch(FAIL_LOG, match): + write_fail_log(task_log_dir, task_id) + count += 1 + elif state not in ['CLOSED', 'CANCELED']: + sys.stderr.write(_("Warning: task %s is %s\n") % (task_id, state)) + + for log_filename, log_volume in logs: + download_log(task_log_dir, task_id, log_filename, volume=log_volume) + count += 1 + + if count == 0 and not recurse: + sys.stderr.write(_("No logs found for task %i. Perhaps try --recurse?\n") % task_id) + + if recurse: + child_tasks = session.getTaskChildren(task_id) + for child_task in child_tasks: + save_logs(child_task['id'], match, task_log_dir, recurse) + + for arg in args: + if suboptions.nvr: + suboptions.recurse = True + binfo = session.getBuild(arg) + if binfo is None: + error(_("There is no build with n-v-r: %s" % arg)) + assert binfo['task_id'], binfo + task_id = binfo['task_id'] + sys.stdout.write("Using task ID: %s\n" % task_id) + else: + try: + task_id = int(arg) + except ValueError: + error(_("Task id must be number: %r") % task_id) + continue + save_logs(task_id, suboptions.match, suboptions.dir, suboptions.recurse) + + +def anon_handle_download_task(options, session, args): + "[download] Download the output of a build task " + usage = _("usage: %prog download-task ") + usage += _("\n(Specify the --help global option for a list of other help options)") + parser = OptionParser(usage=usage) + parser.add_option("--arch", dest="arches", metavar="ARCH", action="append", default=[], + help=_("Only download packages for this arch (may be used multiple times)")) + parser.add_option("--logs", dest="logs", action="store_true", default=False, help=_("Also download build logs")) + + (suboptions, args) = parser.parse_args(args) + if len(args) == 0: + parser.error(_("Please specify a task ID")) + elif len(args) > 1: + parser.error(_("Only one task ID may be specified")) + + base_task_id = int(args.pop()) + if len(suboptions.arches) > 0: + suboptions.arches = ",".join(suboptions.arches).split(",") + + # get downloadable tasks + + base_task = session.getTaskInfo(base_task_id) + + check_downloadable = lambda task: task["method"] == "buildArch" + downloadable_tasks = [] + + if check_downloadable(base_task): + downloadable_tasks.append(base_task) + else: + subtasks = session.getTaskChildren(base_task_id) + downloadable_tasks.extend(list(filter(check_downloadable, subtasks))) + + # get files for download + + downloads = [] + + for task in downloadable_tasks: + files = list_task_output_all_volumes(session, task["id"]) + for filename in files: + if filename.endswith(".log") and suboptions.logs: + for volume in files[filename]: + # rename logs, they would conflict + new_filename = "%s.%s.log" % (filename.rstrip(".log"), task["arch"]) + downloads.append((task, filename, volume, new_filename)) + continue + + if filename.endswith(".rpm"): + for volume in files[filename]: + filearch = filename.split(".")[-2] + if len(suboptions.arches) == 0 or filearch in suboptions.arches: + downloads.append((task, filename, volume, filename)) + continue + + if len(downloads) == 0: + error(_("No files for download found.")) + + required_tasks = {} + for (task, nop, nop, nop) in downloads: + if task["id"] not in required_tasks: + required_tasks[task["id"]] = task + + for task_id in required_tasks: + if required_tasks[task_id]["state"] != koji.TASK_STATES.get("CLOSED"): + if task_id == base_task_id: + error(_("Task %d has not finished yet.") % task_id) + else: + error(_("Child task %d has not finished yet.") % task_id) + + # perform the download + + number = 0 + for (task, filename, volume, new_filename) in downloads: + number += 1 + if volume not in (None, 'DEFAULT'): + koji.ensuredir(volume) + new_filename = os.path.join(volume, new_filename) + print(_("Downloading [%d/%d]: %s") % (number, len(downloads), new_filename)) + output_file = open(new_filename, "wb") + output_file.write(session.downloadTaskOutput(task["id"], filename, volume=volume)) + output_file.close() + +def anon_handle_wait_repo(options, session, args): + "[monitor] Wait for a repo to be regenerated" + usage = _("usage: %prog wait-repo [options] ") + usage += _("\n(Specify the --help global option for a list of other help options)") + parser = OptionParser(usage=usage) + parser.add_option("--build", metavar="NVR", dest="builds", action="append", default=[], + help=_("Check that the given build is in the newly-generated repo (may be used multiple times)")) + parser.add_option("--target", action="store_true", help=_("Interpret the argument as a build target name")) + parser.add_option("--timeout", type="int", help=_("Amount of time to wait (in minutes) before giving up (default: 120)"), default=120) + parser.add_option("--quiet", action="store_true", help=_("Suppress output, success or failure will be indicated by the return value only"), default=options.quiet) + (suboptions, args) = parser.parse_args(args) + + start = time.time() + + builds = [koji.parse_NVR(build) for build in suboptions.builds] + if len(args) < 1: + parser.error(_("Please specify a tag name")) + elif len(args) > 1: + parser.error(_("Only one tag may be specified")) + + tag = args[0] + + if suboptions.target: + target_info = session.getBuildTarget(tag) + if not target_info: + parser.error("Invalid build target: %s" % tag) + tag = target_info['build_tag_name'] + tag_id = target_info['build_tag'] + else: + tag_info = session.getTag(tag) + if not tag_info: + parser.error("Invalid tag: %s" % tag) + targets = session.getBuildTargets(buildTagID=tag_info['id']) + if not targets: + print("%(name)s is not a build tag for any target" % tag_info) + targets = session.getBuildTargets(destTagID=tag_info['id']) + if targets: + maybe = {}.fromkeys([t['build_tag_name'] for t in targets]) + maybe = list(maybe.keys()) + maybe.sort() + print("Suggested tags: %s" % ', '.join(maybe)) + return 1 + tag_id = tag_info['id'] + + + for nvr in builds: + data = session.getLatestBuilds(tag_id, package=nvr["name"]) + if len(data) == 0: + print("Warning: package %s is not in tag %s" % (nvr["name"], tag)) + else: + present_nvr = [x["nvr"] for x in data][0] + if present_nvr != "%s-%s-%s" % (nvr["name"], nvr["version"], nvr["release"]): + print("Warning: nvr %s-%s-%s is not current in tag %s\n latest build in %s is %s" % (nvr["name"], nvr["version"], nvr["release"], tag, tag, present_nvr)) + + last_repo = None + repo = session.getRepo(tag_id) + + while True: + if builds and repo and repo != last_repo: + if koji.util.checkForBuilds(session, tag_id, builds, repo['create_event'], latest=True): + if not suboptions.quiet: + print("Successfully waited %s for %s to appear in the %s repo" % (koji.util.duration(start), koji.util.printList(suboptions.builds), tag)) + return + + if (time.time() - start) >= (suboptions.timeout * 60.0): + if not suboptions.quiet: + if builds: + print("Unsuccessfully waited %s for %s to appear in the %s repo" % (koji.util.duration(start), koji.util.printList(suboptions.builds), tag)) + else: + print("Unsuccessfully waited %s for a new %s repo" % (koji.util.duration(start), tag)) + return 1 + + time.sleep(options.poll_interval) + last_repo = repo + repo = session.getRepo(tag_id) + + if not builds: + if repo != last_repo: + if not suboptions.quiet: + print("Successfully waited %s for a new %s repo" % (koji.util.duration(start), tag)) + return + +_search_types = ('package', 'build', 'tag', 'target', 'user', 'host', 'rpm', 'maven', 'win') + +def handle_regen_repo(options, session, args): + "[admin] Force a repo to be regenerated" + usage = _("usage: %prog regen-repo [options] ") + usage += _("\n(Specify the --help global option for a list of other help options)") + parser = OptionParser(usage=usage) + parser.add_option("--target", action="store_true", help=_("Interpret the argument as a build target name")) + parser.add_option("--nowait", action="store_true", help=_("Don't wait on for regen to finish")) + parser.add_option("--debuginfo", action="store_true", help=_("Include debuginfo rpms in repo")) + parser.add_option("--source", "--src", action="store_true", help=_("Include source rpms in the repo")) + (suboptions, args) = parser.parse_args(args) + if len(args) == 0: + parser.error(_("A tag name must be specified")) + assert False # pragma: no cover + elif len(args) > 1: + if suboptions.target: + parser.error(_("Only a single target may be specified")) + else: + parser.error(_("Only a single tag name may be specified")) + assert False # pragma: no cover + activate_session(session) + tag = args[0] + repo_opts = {} + if suboptions.target: + info = session.getBuildTarget(tag) + if not info: + parser.error(_("No matching build target: " + tag)) + assert False # pragma: no cover + tag = info['build_tag_name'] + info = session.getTag(tag) + else: + info = session.getTag(tag) + if not info: + parser.error(_("No matching tag: " + tag)) + assert False # pragma: no cover + tag = info['name'] + targets = session.getBuildTargets(buildTagID=info['id']) + if not targets: + print("Warning: %s is not a build tag" % tag) + if not info['arches']: + print("Warning: tag %s has an empty arch list" % info['name']) + if suboptions.debuginfo: + repo_opts['debuginfo'] = True + if suboptions.source: + repo_opts['src'] = True + task_id = session.newRepo(tag, **repo_opts) + print("Regenerating repo for tag: %s" % tag) + print("Created task: %d" % task_id) + print("Task info: %s/taskinfo?taskID=%s" % (options.weburl, task_id)) + if _running_in_bg() or suboptions.nowait: + return + else: + session.logout() + return watch_tasks(session, [task_id], quiet=options.quiet) + +def handle_dist_repo(options, session, args): + """Create a yum repo with distribution options""" + usage = _("usage: %prog dist-repo [options] tag keyID [keyID...]") + usage += _("\n(Specify the --help option for a list of other options)") + parser = OptionParser(usage=usage) + parser.add_option('--allow-missing-signatures', action='store_true', + default=False, + help=_('For RPMs not signed with a desired key, fall back to the ' + 'primary copy')) + parser.add_option("--arch", action='append', default=[], + help=_("Indicate an architecture to consider. The default is all " + + "architectures associated with the given tag. This option may " + + "be specified multiple times.")) + parser.add_option('--comps', help='Include a comps file in the repodata') + parser.add_option('--delta-rpms', metavar='REPO',default=[], + action='append', + help=_('Create delta rpms. REPO can be the id of another dist repo ' + 'or the name of a tag that has a dist repo. May be specified ' + 'multiple times.')) + parser.add_option('--event', type='int', + help=_('create a dist repository based on a Brew event')) + parser.add_option('--non-latest', dest='latest', default=True, + action='store_false', help='Include older builds, not just the latest') + parser.add_option('--multilib', default=None, metavar="CONFIG", + help=_('Include multilib packages in the repository using the given ' + 'config file')) + parser.add_option("--noinherit", action='store_true', default=False, + help=_('Do not consider tag inheritance')) + parser.add_option("--nowait", action='store_true', default=False, + help=_('Do not wait for the task to complete')) + parser.add_option('--skip-missing-signatures', action='store_true', default=False, + help=_('Skip RPMs not signed with the desired key(s)')) + task_opts, args = parser.parse_args(args) + if len(args) < 1: + parser.error(_('You must provide a tag to generate the repo from')) + if len(args) < 2 and not task_opts.allow_missing_signatures: + parser.error(_('Please specify one or more GPG key IDs (or ' + '--allow-missing-signatures)')) + if task_opts.allow_missing_signatures and task_opts.skip_missing_signatures: + parser.error(_('allow_missing_signatures and skip_missing_signatures ' + 'are mutually exclusive')) + activate_session(session) + stuffdir = _unique_path('cli-dist-repo') + if task_opts.comps: + if not os.path.exists(task_opts.comps): + parser.error(_('could not find %s') % task_opts.comps) + session.uploadWrapper(task_opts.comps, stuffdir, + callback=_progress_callback) + print('') + task_opts.comps = os.path.join(stuffdir, + os.path.basename(task_opts.comps)) + old_repos = [] + if len(task_opts.delta_rpms) > 0: + for repo in task_opts.delta_rpms: + if repo.isdigit(): + rinfo = session.repoInfo(int(repo), strict=True) + else: + # get dist repo for tag + rinfo = session.getRepo(repo, dist=True) + if not rinfo: + # maybe there is an expired one + rinfo = session.getRepo(repo, + state=koji.REPO_STATES['EXPIRED'], dist=True) + if not rinfo: + parser.errpr(_("Can't find repo for tag: %s") % repo) + old_repos.append(rinfo['id']) + tag = args[0] + keys = args[1:] + taginfo = session.getTag(tag) + if not taginfo: + parser.error(_('unknown tag %s') % tag) + if len(task_opts.arch) == 0: + arches = taginfo['arches'] or '' + task_opts.arch = arches.split() + if task_opts.arch == None: + parser.error(_('No arches given and no arches associated with tag')) + else: + for a in task_opts.arch: + if not taginfo['arches'] or a not in taginfo['arches']: + print(_('Warning: %s is not in the list of tag arches') % a) + if task_opts.multilib: + if not os.path.exists(task_opts.multilib): + parser.error(_('could not find %s') % task_opts.multilib) + if 'x86_64' in task_opts.arch and not 'i686' in task_opts.arch: + parser.error(_('The multilib arch (i686) must be included')) + if 's390x' in task_opts.arch and not 's390' in task_opts.arch: + parser.error(_('The multilib arch (s390) must be included')) + if 'ppc64' in task_opts.arch and not 'ppc' in task_opts.arch: + parser.error(_('The multilib arch (ppc) must be included')) + session.uploadWrapper(task_opts.multilib, stuffdir, + callback=_progress_callback) + task_opts.multilib = os.path.join(stuffdir, + os.path.basename(task_opts.multilib)) + print('') + try: + task_opts.arch.remove('noarch') # handled specifically + task_opts.arch.remove('src') # ditto + except ValueError: + pass + opts = { + 'arch': task_opts.arch, + 'comps': task_opts.comps, + 'delta': old_repos, + 'event': task_opts.event, + 'inherit': not task_opts.noinherit, + 'latest': task_opts.latest, + 'multilib': task_opts.multilib, + 'skip_missing_signatures': task_opts.skip_missing_signatures, + 'allow_missing_signatures': task_opts.allow_missing_signatures + } + task_id = session.distRepo(tag, keys, **opts) + print("Creating dist repo for tag " + tag) + if _running_in_bg() or task_opts.nowait: + return + else: + session.logout() + return watch_tasks(session, [task_id], quiet=options.quiet) + + +def anon_handle_search(options, session, args): + "[search] Search the system" + usage = _("usage: %prog search [options] search_type pattern") + usage += _('\nAvailable search types: %s') % ', '.join(_search_types) + usage += _("\n(Specify the --help global option for a list of other help options)") + parser = OptionParser(usage=usage) + parser.add_option("-r", "--regex", action="store_true", help=_("treat pattern as regex")) + parser.add_option("--exact", action="store_true", help=_("exact matches only")) + (options, args) = parser.parse_args(args) + if not args: + parser.print_help() + return + type = args[0] + if type not in _search_types: + parser.error(_("Unknown search type: %s") % type) + pattern = args[1] + matchType = 'glob' + if options.regex: + matchType = 'regexp' + elif options.exact: + matchType = 'exact' + data = session.search(pattern, type, matchType) + for row in data: + print(row['name']) + +def handle_moshimoshi(options, session, args): + "[misc] Introduce yourself" + usage = _("usage: %prog moshimoshi [options]") + parser = OptionParser(usage=usage) + (opts, args) = parser.parse_args(args) + if len(args) != 0: + parser.error(_("This command takes no arguments")) + assert False # pragma: no cover + activate_session(session) + u = session.getLoggedInUser() + if not u: + print("Not authenticated") + u = {'name' : 'anonymous user'} + print("%s, %s!" % (_printable_unicode(random.choice(greetings)), u["name"])) + print("") + print("You are using the hub at %s" % session.baseurl) + authtype = u.get('authtype', getattr(session, 'authtype', None)) + if authtype == koji.AUTHTYPE_NORMAL: + print("Authenticated via password") + elif authtype == koji.AUTHTYPE_GSSAPI: + print("Authenticated via GSSAPI") + elif authtype == koji.AUTHTYPE_KERB: + print("Authenticated via Kerberos principal %s" % u["krb_principal"]) + elif authtype == koji.AUTHTYPE_SSL: + print("Authenticated via client certificate %s" % options.cert) diff --git a/cli/koji_cli/lib.py b/cli/koji_cli/lib.py new file mode 100644 index 0000000..d449811 --- /dev/null +++ b/cli/koji_cli/lib.py @@ -0,0 +1,440 @@ +def _(args): + """Stub function for translation""" + return args +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 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 --help" for help about the options of a particular command +Try "%(progname)s help " to get commands under a particular category +Available categories are: %(categories)s +''' % ({'progname': progname, 'categories': categories_ordered}) + return _(epilog_str) +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 _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)])) + +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) + +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 + else: + percent_done = float(uploaded)/float(total) + percent_done_str = "%02d%%" % (percent_done * 100) + data_done = _format_size(uploaded) + elapsed = _format_secs(total_time) + + 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" + + # 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 linked_upload(localfile, path, name=None): + """Link a file into the (locally writable) workdir, bypassing upload""" + old_umask = os.umask(0o02) + try: + if name is None: + name = os.path.basename(localfile) + dest_dir = os.path.join(koji.pathinfo.work(), path) + dst = os.path.join(dest_dir, name) + koji.ensuredir(dest_dir) + # fix uid/gid to keep httpd happy + st = os.stat(koji.pathinfo.work()) + os.chown(dest_dir, st.st_uid, st.st_gid) + print("Linking rpm to: %s" % dst) + os.link(localfile, dst) + finally: + os.umask(old_umask) +def error(msg=None, code=1): + if msg: + sys.stderr.write(msg + "\n") + sys.stderr.flush() + sys.exit(code) + +def warn(msg): + sys.stderr.write(msg + "\n") + sys.stderr.flush() + +def has_krb_creds(): + if krbV is None: + return False + try: + ctx = krbV.default_context() + ccache = ctx.default_ccache() + princ = ccache.principal() + return True + except krbV.Krb5Error: + return False + +def activate_session(session): + """Test and login the session is applicable""" + global options + if options.authtype == "noauth" or options.noauth: + #skip authentication + pass + elif options.authtype == "ssl" or os.path.isfile(options.cert) and options.authtype is None: + # authenticate using SSL client cert + session.ssl_login(options.cert, None, options.serverca, proxyuser=options.runas) + elif options.authtype == "password" or options.user and options.authtype is None: + # authenticate using user/password + session.login() + elif options.authtype == "kerberos" or has_krb_creds() and options.authtype is None: + try: + if options.keytab and options.principal: + session.krb_login(principal=options.principal, keytab=options.keytab, proxyuser=options.runas) + else: + session.krb_login(proxyuser=options.runas) + except socket.error as e: + warn(_("Could not connect to Kerberos authentication service: %s") % e.args[1]) + except Exception as e: + if krbV is not None and isinstance(e, krbV.Krb5Error): + error(_("Kerberos authentication failed: %s (%s)") % (e.args[1], e.args[0])) + else: + raise + if not options.noauth and options.authtype != "noauth" and not session.logged_in: + error(_("Unable to log in, no authentication methods available")) + ensure_connection(session) + if options.debug: + print("successfully connected to hub") diff --git a/plugins/cli/runroot.py b/plugins/cli/runroot.py new file mode 100644 index 0000000..d9865da --- /dev/null +++ b/plugins/cli/runroot.py @@ -0,0 +1,76 @@ +def handle_runroot(options, session, args): + "[admin] Run a command in a buildroot" + usage = _("usage: %prog runroot [options] ") + usage += _("\n(Specify the --help global option for a list of other help options)") + parser = OptionParser(usage=usage) + parser.disable_interspersed_args() + parser.add_option("-p", "--package", action="append", default=[], help=_("make sure this package is in the chroot")) + parser.add_option("-m", "--mount", action="append", default=[], help=_("mount this directory read-write in the chroot")) + parser.add_option("--skip-setarch", action="store_true", default=False, + help=_("bypass normal setarch in the chroot")) + parser.add_option("-w", "--weight", type='int', help=_("set task weight")) + parser.add_option("--channel-override", help=_("use a non-standard channel")) + parser.add_option("--task-id", action="store_true", default=False, + help=_("Print the ID of the runroot task")) + parser.add_option("--use-shell", action="store_true", default=False, + help=_("Run command through a shell, otherwise uses exec")) + parser.add_option("--new-chroot", action="store_true", default=False, + help=_("Run command with the --new-chroot (systemd-nspawn) option to mock")) + parser.add_option("--repo-id", type="int", help=_("ID of the repo to use")) + + (opts, args) = parser.parse_args(args) + + if len(args) < 3: + parser.error(_("Incorrect number of arguments")) + activate_session(session) + tag = args[0] + arch = args[1] + if opts.use_shell: + # everything must be correctly quoted + command = ' '.join(args[2:]) + else: + command = args[2:] + try: + kwargs = { 'channel': opts.channel_override, + 'packages': opts.package, + 'mounts': opts.mount, + 'repo_id': opts.repo_id, + 'skip_setarch': opts.skip_setarch, + 'weight': opts.weight } + # Only pass this kwarg if it is true - this prevents confusing older + # builders with a different function signature + if opts.new_chroot: + kwargs['new_chroot'] = True + + task_id = session.runroot(tag, arch, command, **kwargs) + except koji.GenericError as e: + if 'Invalid method' in str(e): + print("* The runroot plugin appears to not be installed on the" + " koji hub. Please contact the administrator.") + raise + if opts.task_id: + print(task_id) + + try: + while True: + # wait for the task to finish + if session.taskFinished(task_id): + break + time.sleep(options.poll_interval) + except KeyboardInterrupt: + # this is probably the right thing to do here + print("User interrupt: canceling runroot task") + session.cancelTask(task_id) + raise + output = list_task_output_all_volumes(session, task_id) + if 'runroot.log' in output: + for volume in output['runroot.log']: + log = session.downloadTaskOutput(task_id, 'runroot.log', volume=volume) + sys.stdout.write(log) + info = session.getTaskInfo(task_id) + if info is None: + sys.exit(1) + state = koji.TASK_STATES[info['state']] + if state in ('FAILED', 'CANCELED'): + sys.exit(1) + return diff --git a/plugins/cli/save_failed_tree.py b/plugins/cli/save_failed_tree.py new file mode 100644 index 0000000..4a06c8e --- /dev/null +++ b/plugins/cli/save_failed_tree.py @@ -0,0 +1,62 @@ +def handle_save_failed_tree(options, session, args): + "Create tarball with whole buildtree" + usage = _("usage: %prog save-failed-tree [options] ID") + usage += _("\n(Specify the --help global option for a list of other help options)") + parser = OptionParser(usage=usage) + parser.add_option("-f", "--full", action="store_true", default=False, + help=_("Download whole tree, if not specified, only builddir will be downloaded")) + parser.add_option("-t", "--task", action="store_const", dest="mode", + const="task", default="task", + help=_("Treat ID as a task ID (the default)")) + parser.add_option("-r", "--buildroot", action="store_const", dest="mode", + const="buildroot", + help=_("Treat ID as a buildroot ID")) + parser.add_option("--quiet", action="store_true", default=options.quiet, + help=_("Do not print the task information")) + parser.add_option("--nowait", action="store_true", + help=_("Don't wait on build")) + + (opts, args) = parser.parse_args(args) + + if len(args) != 1: + parser.error(_("List exactly one task or buildroot ID")) + + try: + id_val = int(args[0]) + except ValueError: + parser.error(_("ID must be an integer")) + + activate_session(session) + + if opts.mode == "buildroot": + br_id = id_val + else: + brs = [b['id'] for b in session.listBuildroots(taskID=id_val)] + if not brs: + print(_("No buildroots for task %s") % id_val) + return 1 + br_id = max(brs) + if len(brs) > 1: + print(_("Multiple buildroots for task. Choosing last one (%s)") % br_id) + + try: + task_id = session.saveFailedTree(br_id, opts.full) + except koji.GenericError as e: + m = str(e) + if 'Invalid method' in m: + print(_("* The save_failed_tree plugin appears to not be " + "installed on the koji hub. Please contact the " + "administrator.")) + return 1 + raise + + if not opts.quiet: + print(_("Created task %s for buildroot %s") % (task_id, br_id)) + print("Task info: %s/taskinfo?taskID=%s" + % (options.weburl, task_id)) + + if opts.nowait: + return + else: + session.logout() + watch_tasks(session, [task_id], quiet=opts.quiet) From 37bcef025cde9200c92610c145a120faf9997d6b Mon Sep 17 00:00:00 2001 From: Tomas Kopecek Date: Jun 13 2017 09:42:04 +0000 Subject: [PATCH 5/19] cleanup after automatic split --- diff --git a/cli/koji b/cli/koji index 4bf44e9..3b11821 100755 --- a/cli/koji +++ b/cli/koji @@ -27,96 +27,18 @@ 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 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 - -# 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 - -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'안녕하세요') - - - -ARGMAP = {'None': None, - 'True': True, - 'False': False} +from koji_cli.lib import _, OptionParser, get_epilog_str, greetings, \ + warn, categories def get_options(): @@ -240,18 +162,6 @@ def get_options(): return options, cmd, args[1:] - - - - - - - - - - - - def handle_help(options, session, args): "[info] List available commands" usage = _("usage: %prog help ...") diff --git a/cli/koji_cli/commands.py b/cli/koji_cli/commands.py index 0eef519..ec796af 100644 --- a/cli/koji_cli/commands.py +++ b/cli/koji_cli/commands.py @@ -1,3 +1,46 @@ +from __future__ import absolute_import +from __future__ import division +import ast +import base64 +import dateutil.parser +import fnmatch +import json +import logging +import optparse +import os +import pprint +import pycurl +import random +import re +import six +import stat +import sys +import time +import traceback + +import six.moves.xmlrpc_client +from six.moves import filter +from six.moves import map +from six.moves import zip + +try: + import libcomps +except ImportError: # pragma: no cover + libcomps = None + try: + import yum.comps as yumcomps + except ImportError: + yumcomps = None + +import koji +from koji.util import md5_constructor +from koji_cli.lib import _, OptionParser, activate_session, parse_arches, \ + _unique_path, _running_in_bg, _progress_callback, watch_tasks, \ + arg_filter, linked_upload, list_task_output_all_volumes, \ + print_task_headers, print_task_recurse, _format_size, watch_logs, \ + error, greetings + + def _printable_unicode(s): if six.PY2: return s.encode('utf-8') @@ -410,7 +453,8 @@ def handle_build(options, session, args): 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) + return watch_tasks(session, [task_id], quiet=build_opts.quiet, + poll_interval=options.poll_interval) else: return @@ -487,7 +531,8 @@ def handle_chain_build(options, session, args): return else: session.logout() - return watch_tasks(session, [task_id], quiet=build_opts.quiet) + return watch_tasks(session, [task_id], quiet=build_opts.quiet, + poll_interval=options.poll_interval) def handle_maven_build(options, session, args): "[build] Build a Maven package from source" @@ -585,7 +630,8 @@ def handle_maven_build(options, session, args): return else: session.logout() - return watch_tasks(session, [task_id], quiet=build_opts.quiet) + return watch_tasks(session, [task_id], quiet=build_opts.quiet, + poll_interval=options.poll_interval) def handle_wrapper_rpm(options, session, args): """[build] Build wrapper rpms for any archives associated with a build.""" @@ -651,7 +697,8 @@ def handle_wrapper_rpm(options, session, args): return else: session.logout() - return watch_tasks(session,[task_id],quiet=options.quiet) + return watch_tasks(session, [task_id], quiet=options.quiet, + poll_interval=options.poll_interval) def handle_maven_chain(options, session, args): "[build] Run a set of Maven builds in dependency order" @@ -703,9 +750,10 @@ def handle_maven_chain(options, session, args): return else: session.logout() - return watch_tasks(session, [task_id], quiet=options.quiet) + return watch_tasks(session, [task_id], quiet=options.quiet, + poll_interval=options.poll_interval) -def handle_resubmit(options, session, args): +def handle_resubmit(global_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)") @@ -722,7 +770,7 @@ def handle_resubmit(options, session, args): taskID = int(args[0]) if not options.quiet: print("Resubmitting the following task:") - _printTaskInfo(session, taskID, 0, False, True) + _printTaskInfo(session, taskID, 0, global_options.topdir, False, True) newID = session.resubmitTask(taskID) if not options.quiet: print("Resubmitted task %s as new task %s" % (taskID, newID)) @@ -730,7 +778,8 @@ def handle_resubmit(options, session, args): return else: session.logout() - return watch_tasks(session, [newID], quiet=options.quiet) + return watch_tasks(session, [newID], quiet=options.quiet, + poll_interval=options.poll_interval) def handle_call(options, session, args): "Execute an arbitrary XML-RPC call" @@ -963,7 +1012,8 @@ def handle_restart_hosts(options, session, args): task_id = session.restartHosts() if my_opts.wait or (my_opts.wait is None and not _running_in_bg()): session.logout() - return watch_tasks(session, [task_id], quiet=my_opts.quiet) + return watch_tasks(session, [task_id], quiet=my_opts.quiet, + poll_interval=options.poll_interval) else: return def handle_import(options, session, args): @@ -2551,14 +2601,14 @@ def handle_unblock_group_req(options, session, args): activate_session(session) session.groupReqListUnblock(tag, group, req) -def anon_handle_list_channels(options, session, args): +def anon_handle_list_channels(goptions, session, args): "[info] Print channels listing" usage = _("usage: %prog list-channels") usage += _("\n(Specify the --help global option for a list of other help options)") parser = OptionParser(usage=usage) - parser.add_option("--quiet", action="store_true", help=_("Do not print header information"), default=options.quiet) + parser.add_option("--quiet", action="store_true", help=_("Do not print header information"), default=goptions.quiet) (options, args) = parser.parse_args(args) - activate_session(session) + activate_session(session, goptions) channels = session.listChannels() session.multicall = True for channel in channels: @@ -3993,17 +4043,18 @@ def _handleOpts(lines, opts, prefix=''): _handleMap(lines, opts, prefix) -def _parseTaskParams(session, method, task_id): +def _parseTaskParams(session, method, task_id, topdir): try: - return _do_parseTaskParams(session, method, task_id) + return _do_parseTaskParams(session, method, task_id, topdir) except Exception: + logger = logging.getLogger("koji") if logger.isEnabledFor(logging.DEBUG): tb_str = ''.join(traceback.format_exception(*sys.exc_info())) logger.debug(tb_str) return ['Unable to parse task parameters'] -def _do_parseTaskParams(session, method, task_id): +def _do_parseTaskParams(session, method, task_id, topdir): """Parse the return of getTaskRequest()""" params = session.getTaskRequest(task_id) @@ -4014,7 +4065,7 @@ def _do_parseTaskParams(session, method, task_id): elif method == 'buildSRPMFromSCM': lines.append("SCM URL: %s" % params[0]) elif method == 'buildArch': - lines.append("SRPM: %s/work/%s" % (options.topdir, params[0])) + lines.append("SRPM: %s/work/%s" % (topdir, params[0])) lines.append("Build Tag: %s" % session.getTag(params[1])['name']) lines.append("Build Arch: %s" % params[2]) lines.append("SRPM Kept: %r" % params[3]) @@ -4151,7 +4202,7 @@ def _do_parseTaskParams(session, method, task_id): return lines -def _printTaskInfo(session, task_id, level=0, recurse=True, verbose=True): +def _printTaskInfo(session, task_id, topdir, level=0, recurse=True, verbose=True): """Recursive function to print information about a task and its children.""" @@ -4189,7 +4240,7 @@ def _printTaskInfo(session, task_id, level=0, recurse=True, verbose=True): print("%sType: %s" % (indent, info['method'])) if verbose: print("%sRequest Parameters:" % indent) - for line in _parseTaskParams(session, info['method'], task_id): + for line in _parseTaskParams(session, info['method'], task_id, topdir): print("%s %s" % (indent, line)) print("%sOwner: %s" % (indent, owner)) print("%sState: %s" % (indent, koji.TASK_STATES[info['state']].lower())) @@ -4223,9 +4274,9 @@ def _printTaskInfo(session, task_id, level=0, recurse=True, verbose=True): children = session.getTaskChildren(task_id, request=True) children.sort(cmp=lambda a, b: cmp(a['id'], b['id'])) for child in children: - _printTaskInfo(session, child['id'], level, verbose=verbose) + _printTaskInfo(session, child['id'], topdir, level, verbose=verbose) -def anon_handle_taskinfo(options, session, args): +def anon_handle_taskinfo(global_options, session, args): """[info] Show information about a task""" usage = _("usage: %prog taskinfo [options] taskID [taskID...]") usage += _("\n(Specify the --help global option for a list of other help options)") @@ -4241,7 +4292,7 @@ def anon_handle_taskinfo(options, session, args): for arg in args: task_id = int(arg) - _printTaskInfo(session, task_id, 0, options.recurse, options.verbose) + _printTaskInfo(session, task_id, options.topdir, 0, options.recurse, options.verbose) def anon_handle_taginfo(options, session, args): "[info] Print basic information about a tag" @@ -5444,7 +5495,8 @@ def _build_image(options, task_opts, session, args, img_type): print("Task info: %s/taskinfo?taskID=%s" % (options.weburl, task_id)) if task_opts.wait or (task_opts.wait is None and not _running_in_bg()): session.logout() - return watch_tasks(session, [task_id], quiet=options.quiet) + return watch_tasks(session, [task_id], quiet=options.quiet, + poll_interval=options.poll_interval) else: return @@ -5513,7 +5565,8 @@ def _build_image_oz(options, task_opts, session, args): print("Task info: %s/taskinfo?taskID=%s" % (options.weburl, task_id)) if task_opts.wait or (task_opts.wait is None and not _running_in_bg()): session.logout() - return watch_tasks(session, [task_id], quiet=options.quiet) + return watch_tasks(session, [task_id], quiet=options.quiet, + poll_interval=options.poll_interval) else: return @@ -5590,7 +5643,8 @@ def handle_win_build(options, session, args): 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) + return watch_tasks(session, [task_id], quiet=build_opts.quiet, + poll_interval=options.poll_interval) else: return @@ -5851,7 +5905,7 @@ def handle_set_pkg_owner_global(options, session, args): % (entry['package_name'], entry['tag_name'], entry['owner_name'], user['name'])) session.packageListSetOwner(entry['tag_id'], entry['package_name'], user['id']) -def anon_handle_watch_task(options, session, args): +def anon_handle_watch_task(global_options, session, args): "[monitor] Track progress of particular tasks" usage = _("usage: %prog watch-task [options] [...]") usage += _("\n(Specify the --help global option for a list of other help options)") @@ -5890,9 +5944,10 @@ def anon_handle_watch_task(options, session, args): if not tasks: parser.error(_("at least one task id must be specified")) - return watch_tasks(session, tasks, quiet=options.quiet) + return watch_tasks(session, tasks, quiet=options.quiet, + poll_interval=global_options.poll_interval) -def anon_handle_watch_logs(options, session, args): +def anon_handle_watch_logs(global_options, session, args): "[monitor] Watch logs in realtime" usage = _("usage: %prog watch-logs [options] [...]") usage += _("\n(Specify the --help global option for a list of other help options)") @@ -5910,7 +5965,7 @@ def anon_handle_watch_logs(options, session, args): if not tasks: parser.error(_("at least one task id must be specified")) - watch_logs(session, tasks, options) + watch_logs(session, tasks, options, global_options.poll_interval) def handle_make_task(opts, session, args): "[admin] Create an arbitrary task" @@ -5937,7 +5992,8 @@ def handle_make_task(opts, session, args): return else: session.logout() - return watch_tasks(session, [task_id], quiet=opts.quiet) + return watch_tasks(session, [task_id], quiet=opts.quiet, + poll_interval=opts.poll_interval) def handle_tag_build(opts, session, args): "[bind] Apply a tag to one or more builds" @@ -5961,7 +6017,8 @@ def handle_tag_build(opts, session, args): return else: session.logout() - return watch_tasks(session,tasks,quiet=opts.quiet) + return watch_tasks(session, tasks, quiet=opts.quiet, + poll_interval=opts.poll_interval) def handle_move_build(opts, session, args): "[bind] 'Move' one or more builds between tags" @@ -6007,7 +6064,8 @@ def handle_move_build(opts, session, args): return else: session.logout() - return watch_tasks(session, tasks, quiet=opts.quiet) + return watch_tasks(session, tasks, quiet=opts.quiet, + poll_interval=opts.poll_interval) def handle_untag_build(options, session, args): "[bind] Remove a tag from one or more builds" @@ -6275,7 +6333,7 @@ def anon_handle_download_logs(options, session, args): # with current code, failed task results should always be faults, # but that could change in the future content = pprint.pformat(result) - except (xmlrpclib.Fault, koji.GenericError): + except (six.moves.xmlrpc_client.Fault, koji.GenericError): etype, e = sys.exc_info()[:2] content = ''.join(traceback.format_exception_only(etype, e)) full_filename = os.path.normpath(os.path.join(task_log_dir, FAIL_LOG)) @@ -6582,7 +6640,8 @@ def handle_regen_repo(options, session, args): return else: session.logout() - return watch_tasks(session, [task_id], quiet=options.quiet) + return watch_tasks(session, [task_id], quiet=options.quiet, + poll_interval=options.poll_interval) def handle_dist_repo(options, session, args): """Create a yum repo with distribution options""" @@ -6700,7 +6759,8 @@ def handle_dist_repo(options, session, args): return else: session.logout() - return watch_tasks(session, [task_id], quiet=options.quiet) + return watch_tasks(session, [task_id], quiet=options.quiet, + poll_interval=options.poll_interval) def anon_handle_search(options, session, args): diff --git a/cli/koji_cli/lib.py b/cli/koji_cli/lib.py index d449811..f4b7754 100644 --- a/cli/koji_cli/lib.py +++ b/cli/koji_cli/lib.py @@ -1,3 +1,61 @@ +# coding=utf-8 +from __future__ import absolute_import +from __future__ import division +import optparse +import os +import random +import six +import socket +import string +import sys +import time +from six.moves import range + +try: + import krbV +except ImportError: # pragma: no cover + krbV = None + +import koji + +# 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 + +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'안녕하세요') + +ARGMAP = {'None': None, + 'True': True, + 'False': False} + def _(args): """Stub function for translation""" return args @@ -104,7 +162,7 @@ class TaskWatcher(object): return '' error = None try: - result = self.session.getTaskResult(self.id) + self.session.getTaskResult(self.id) except (six.moves.xmlrpc_client.Fault,koji.GenericError) as e: error = e if error is None: @@ -200,7 +258,7 @@ def display_task_results(tasks): # shouldn't happen print('%s has not completed' % task_label) -def watch_tasks(session,tasklist,quiet=False): +def watch_tasks(session, tasklist, quiet=False, poll_interval=60): global options if not tasklist: return @@ -240,7 +298,7 @@ def watch_tasks(session,tasklist,quiet=False): break sys.stdout.flush() - time.sleep(options.poll_interval) + time.sleep(poll_interval) except KeyboardInterrupt: if tasks and not quiet: progname = os.path.basename(sys.argv[0]) or 'koji' @@ -253,8 +311,7 @@ Running Tasks: raise return rv -def watch_logs(session, tasklist, opts): - global options +def watch_logs(session, tasklist, opts, poll_interval): print("Watching logs (this may be safely interrupted)...") def _isDone(session, taskId): info = session.getTaskInfo(taskId) @@ -306,7 +363,7 @@ def watch_logs(session, tasklist, opts): if not tasklist: break - time.sleep(options.poll_interval) + time.sleep(poll_interval) def list_task_output_all_volumes(session, task_id): @@ -369,7 +426,7 @@ def _progress_callback(uploaded, total, piece, time, total_time): def _running_in_bg(): try: return (not os.isatty(0)) or (os.getpgrp() != os.tcgetpgrp(0)) - except OSError as e: + except OSError: return True def linked_upload(localfile, path, name=None): """Link a file into the (locally writable) workdir, bypassing upload""" @@ -403,12 +460,12 @@ def has_krb_creds(): try: ctx = krbV.default_context() ccache = ctx.default_ccache() - princ = ccache.principal() + ccache.principal() return True except krbV.Krb5Error: return False -def activate_session(session): +def activate_session(session, options): """Test and login the session is applicable""" global options if options.authtype == "noauth" or options.noauth: From c15c55304849fc33abd01f48f63ba10e207005fc Mon Sep 17 00:00:00 2001 From: Tomas Kopecek Date: Jun 13 2017 09:42:04 +0000 Subject: [PATCH 6/19] fix entry path --- diff --git a/cli/koji b/cli/koji index 3b11821..25af487 100755 --- a/cli/koji +++ b/cli/koji @@ -26,19 +26,60 @@ from __future__ import absolute_import from __future__ import division -import sys -import six -import six.moves.configparser -import koji -import koji.util import logging +import optparse import os import re +import six +import sys +import types + +import six.moves.configparser import six.moves.xmlrpc_client -import optparse + +import koji +import koji.util +import koji.plugin from koji_cli.lib import _, OptionParser, get_epilog_str, greetings, \ warn, categories +from koji_cli.commands import * + + +def register_plugin(plugin): + """Scan a given plugin for handlers + + 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 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') + pluginpaths = options.pluginpath.split() + 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): + pluginpaths = [syspath] + pluginpaths + tracker = koji.plugin.PluginTracker(path=pluginpaths) + for name in options.plugins.split(): + logger.info('Loading plugin: %s', name) + tracker.load(name) + register_plugin(tracker.get(name)) def get_options(): @@ -87,6 +128,44 @@ def get_options(): parser.add_option("--help-commands", action="store_true", default=False, help=_("list commands")) (options, args) = parser.parse_args() + # load local config + try: + result = koji.read_config(options.profile, user_config=options.configFile) + except koji.ConfigurationError as e: + parser.error(e.args[0]) + assert False # pragma: no cover + + # update options according to local config + for name, value in six.iteritems(result): + if getattr(options, name, None) is None: + setattr(options, name, value) + + dir_opts = ('topdir', 'cert', 'serverca', 'pluginpath') + for name in dir_opts: + # expand paths here, so we don't have to worry about it later + value = os.path.expanduser(getattr(options, name)) + setattr(options, name, value) + + #honor topdir + if options.topdir: + koji.BASEDIR = options.topdir + koji.pathinfo.topdir = options.topdir + + #pkgurl is obsolete + if options.pkgurl: + if options.topurl: + warn("Warning: the pkgurl option is obsolete") + else: + suggest = re.sub(r'/packages/?$', '', options.pkgurl) + if suggest != options.pkgurl: + warn("Warning: the pkgurl option is obsolete, using topurl=%r" + % suggest) + options.topurl = suggest + else: + warn("Warning: The pkgurl option is obsolete, please use topurl instead") + + load_plugins(options) + if options.help_commands: list_commands() sys.exit(0) @@ -123,42 +202,6 @@ def get_options(): 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) - except koji.ConfigurationError as e: - parser.error(e.args[0]) - assert False # pragma: no cover - - # update options according to local config - for name, value in six.iteritems(result): - if getattr(options, name, None) is None: - setattr(options, name, value) - - dir_opts = ('topdir', 'cert', 'serverca') - for name in dir_opts: - # expand paths here, so we don't have to worry about it later - value = os.path.expanduser(getattr(options, name)) - setattr(options, name, value) - - #honor topdir - if options.topdir: - koji.BASEDIR = options.topdir - koji.pathinfo.topdir = options.topdir - - #pkgurl is obsolete - if options.pkgurl: - if options.topurl: - warn("Warning: the pkgurl option is obsolete") - else: - suggest = re.sub(r'/packages/?$', '', options.pkgurl) - if suggest != options.pkgurl: - warn("Warning: the pkgurl option is obsolete, using topurl=%r" - % suggest) - options.topurl = suggest - else: - warn("Warning: The pkgurl option is obsolete, please use topurl instead") - return options, cmd, args[1:] diff --git a/cli/koji.conf b/cli/koji.conf index ae77e4e..fe237fc 100644 --- a/cli/koji.conf +++ b/cli/koji.conf @@ -32,3 +32,6 @@ ;certificate of the CA that issued the HTTP server certificate ;serverca = ~/.koji/serverca.crt + +;enabled plugins for CLI, runroot and save_failed_tree are available +;plugins = diff --git a/cli/koji_cli/lib.py b/cli/koji_cli/lib.py index f4b7754..3ce1a85 100644 --- a/cli/koji_cli/lib.py +++ b/cli/koji_cli/lib.py @@ -259,7 +259,6 @@ def display_task_results(tasks): print('%s has not completed' % task_label) def watch_tasks(session, tasklist, quiet=False, poll_interval=60): - global options if not tasklist: return if not quiet: @@ -467,7 +466,6 @@ def has_krb_creds(): def activate_session(session, options): """Test and login the session is applicable""" - global options if options.authtype == "noauth" or options.noauth: #skip authentication pass diff --git a/koji/__init__.py b/koji/__init__.py index 9e1e5ce..afe35a1 100644 --- a/koji/__init__.py +++ b/koji/__init__.py @@ -1636,6 +1636,8 @@ def read_config(profile_name, user_config=None): 'authtype': None, 'debug': False, 'debug_xmlrpc': False, + 'pluginpath': '', + 'plugins': '', } result = config_defaults.copy() From ac3f76a4de91b0e39e5fc23a0aa8db3c3034c70f Mon Sep 17 00:00:00 2001 From: Tomas Kopecek Date: Jun 13 2017 09:42:04 +0000 Subject: [PATCH 7/19] refactor activate_session --- diff --git a/cli/koji_cli/commands.py b/cli/koji_cli/commands.py index ec796af..af8f400 100644 --- a/cli/koji_cli/commands.py +++ b/cli/koji_cli/commands.py @@ -46,7 +46,7 @@ def _printable_unicode(s): return s.encode('utf-8') else: return s -def handle_add_group(options, session, args): +def handle_add_group(goptions, session, args): "[admin] Add a group to a tag" usage = _("usage: %prog add-group ") usage += _("\n(Specify the --help global option for a list of other help options)") @@ -58,7 +58,7 @@ def handle_add_group(options, session, args): tag = args[0] group = args[1] - activate_session(session) + activate_session(session, goptions) if not session.hasPerm('admin'): print("This action requires admin privileges") return 1 @@ -76,7 +76,7 @@ def handle_add_group(options, session, args): session.groupListAdd(tag, group) -def handle_assign_task(options, session, args): +def handle_assign_task(goptions, 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)') @@ -103,7 +103,7 @@ def handle_assign_task(options, session, args): if options.force: force = True - activate_session(session) + activate_session(session, goptions) if not session.hasPerm('admin'): print("This action requires admin privileges") return 1 @@ -115,7 +115,7 @@ def handle_assign_task(options, session, args): print('failed to assign task %d to host %s' % (task_id, hostname)) -def handle_add_host(options, session, args): +def handle_add_host(goptions, 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)") @@ -126,7 +126,7 @@ def handle_add_host(options, session, args): parser.error(_("Please specify a hostname and at least one arch")) assert False # pragma: no cover host = args[0] - activate_session(session) + activate_session(session, goptions) id = session.getHost(host) if id: print("%s is already in the database" % host) @@ -152,7 +152,7 @@ def handle_edit_host(options, session, args): if not args: parser.error(_("Please specify a hostname")) - activate_session(session) + activate_session(session, options) vals = {} for key, val in subopts.__dict__.items(): @@ -183,7 +183,7 @@ def handle_edit_host(options, session, args): else: print(_("No changes made to %s") % host) -def handle_add_host_to_channel(options, session, args): +def handle_add_host_to_channel(goptions, 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)") @@ -194,7 +194,7 @@ def handle_add_host_to_channel(options, session, 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) + activate_session(session, goptions) if options.list: for channel in session.listChannels(): print(channel['name']) @@ -215,7 +215,7 @@ def handle_add_host_to_channel(options, session, args): kwargs['create'] = True session.addHostToChannel(host, channel, **kwargs) -def handle_remove_host_from_channel(options, session, args): +def handle_remove_host_from_channel(goptions, 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)") @@ -225,7 +225,7 @@ def handle_remove_host_from_channel(options, session, args): parser.error(_("Please specify a hostname and a channel")) assert False # pragma: no cover host = args[0] - activate_session(session) + activate_session(session, goptions) hostinfo = session.getHost(host) if not hostinfo: print("No such host: %s" % host) @@ -239,7 +239,7 @@ def handle_remove_host_from_channel(options, session, args): session.removeHostFromChannel(host, channel) -def handle_remove_channel(options, session, args): +def handle_remove_channel(goptions, 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)") @@ -249,14 +249,14 @@ def handle_remove_channel(options, session, args): if len(args) != 1: parser.error(_("Incorrect number of arguments")) assert False # pragma: no cover - activate_session(session) + activate_session(session, goptions) 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): +def handle_rename_channel(goptions, 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)") @@ -265,14 +265,14 @@ def handle_rename_channel(options, session, args): if len(args) != 2: parser.error(_("Incorrect number of arguments")) assert False # pragma: no cover - activate_session(session) + activate_session(session, goptions) cinfo = session.getChannel(args[0]) if not cinfo: print("No such channel: %s" % args[0]) return 1 session.renameChannel(args[0], args[1]) -def handle_add_pkg(options, session, args): +def handle_add_pkg(goptions, 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)") @@ -290,7 +290,7 @@ def handle_add_pkg(options, session, args): if not session.getUser(options.owner): print("User %s does not exist" % options.owner) return 1 - activate_session(session) + activate_session(session, goptions) tag = args[0] opts = {} opts['force'] = options.force @@ -319,7 +319,7 @@ def handle_add_pkg(options, session, args): session.multiCall(strict=True) -def handle_block_pkg(options, session, args): +def handle_block_pkg(goptions, session, args): "[admin] Block a package in the listing for tag" usage = _("usage: %prog block-pkg [options] tag package [package2 ...]") usage += _("\n(Specify the --help global option for a list of other help options)") @@ -328,7 +328,7 @@ def handle_block_pkg(options, session, args): if len(args) < 2: parser.error(_("Please specify a tag and at least one package")) assert False # pragma: no cover - activate_session(session) + activate_session(session, goptions) tag = args[0] # check if list of packages exists for that tag already dsttag=session.getTag(tag) @@ -349,7 +349,7 @@ def handle_block_pkg(options, session, args): session.packageListBlock(tag, package) session.multiCall(strict=True) -def handle_remove_pkg(options, session, args): +def handle_remove_pkg(goptions, 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)") @@ -359,7 +359,7 @@ def handle_remove_pkg(options, session, args): if len(args) < 2: parser.error(_("Please specify a tag and at least one package")) assert False # pragma: no cover - activate_session(session) + activate_session(session, goptions) tag = args[0] opts = {} opts['force'] = options.force @@ -408,7 +408,7 @@ def handle_build(options, session, args): 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) + activate_session(session, options) target = args[0] if target.lower() == "none" and build_opts.repo_id: target = None @@ -474,7 +474,7 @@ def handle_chain_build(options, session, 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) + activate_session(session, options) target = args[0] build_target = session.getBuildTarget(target) if not build_target: @@ -589,7 +589,7 @@ def handle_maven_build(options, session, args): else: if len(args) != 2: parser.error(_("Exactly two arguments (a build target and a SCM URL) are required")) - activate_session(session) + activate_session(session, options) target = args[0] build_target = session.getBuildTarget(target) if not build_target: @@ -656,7 +656,7 @@ def handle_wrapper_rpm(options, session, args): 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) + activate_session(session, options) target = args[0] if build_opts.inis: @@ -721,7 +721,7 @@ def handle_maven_chain(options, session, 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) + activate_session(session, options) target = args[0] build_target = session.getBuildTarget(target) if not build_target: @@ -753,7 +753,7 @@ def handle_maven_chain(options, session, args): return watch_tasks(session, [task_id], quiet=options.quiet, poll_interval=options.poll_interval) -def handle_resubmit(global_options, session, args): +def handle_resubmit(goptions, 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)") @@ -766,11 +766,11 @@ def handle_resubmit(global_options, session, args): if len(args) != 1: parser.error(_("Please specify a single task ID")) assert False # pragma: no cover - activate_session(session) + activate_session(session, goptions) taskID = int(args[0]) if not options.quiet: print("Resubmitting the following task:") - _printTaskInfo(session, taskID, 0, global_options.topdir, False, True) + _printTaskInfo(session, taskID, 0, goptions.topdir, False, True) newID = session.resubmitTask(taskID) if not options.quiet: print("Resubmitted task %s as new task %s" % (taskID, newID)) @@ -781,7 +781,7 @@ def handle_resubmit(global_options, session, args): return watch_tasks(session, [newID], quiet=options.quiet, poll_interval=options.poll_interval) -def handle_call(options, session, args): +def handle_call(goptions, 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)") @@ -799,7 +799,7 @@ def handle_call(options, session, args): 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) + activate_session(session, goptions) name = args[0] non_kw = [] kw = {} @@ -820,7 +820,7 @@ def handle_call(options, session, args): else: pprint.pprint(response) -def anon_handle_mock_config(options, session, args): +def anon_handle_mock_config(goptions, 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)") @@ -843,7 +843,7 @@ def anon_handle_mock_config(options, session, args): 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) + activate_session(session, goptions) if args: #for historical reasons, we also accept buildroot name as first arg if not options.name: @@ -940,7 +940,7 @@ def anon_handle_mock_config(options, session, args): else: print(output) -def handle_disable_host(options, session, args): +def handle_disable_host(goptions, session, args): "[admin] Mark one or more hosts as disabled" usage = _("usage: %prog disable-host [options] hostname ...") usage += _("\n(Specify the --help global option for a list of other help options)") @@ -948,7 +948,7 @@ def handle_disable_host(options, session, args): parser.add_option("--comment", help=_("Comment indicating why the host(s) are being disabled")) (options, args) = parser.parse_args(args) - activate_session(session) + activate_session(session, goptions) session.multicall = True for host in args: session.getHost(host) @@ -967,7 +967,7 @@ def handle_disable_host(options, session, args): session.editHost(host, comment=options.comment) session.multiCall(strict=True) -def handle_enable_host(options, session, args): +def handle_enable_host(goptions, session, args): "[admin] Mark one or more hosts as enabled" usage = _("usage: %prog enable-host [options] hostname ...") usage += _("\n(Specify the --help global option for a list of other help options)") @@ -975,7 +975,7 @@ def handle_enable_host(options, session, args): parser.add_option("--comment", help=_("Comment indicating why the host(s) are being enabled")) (options, args) = parser.parse_args(args) - activate_session(session) + activate_session(session, goptions) session.multicall = True for host in args: session.getHost(host) @@ -1008,7 +1008,7 @@ def handle_restart_hosts(options, session, args): help=_("Do not print the task information"), default=options.quiet) (my_opts, args) = parser.parse_args(args) - activate_session(session) + activate_session(session, options) task_id = session.restartHosts() if my_opts.wait or (my_opts.wait is None and not _running_in_bg()): session.logout() @@ -1016,7 +1016,7 @@ def handle_restart_hosts(options, session, args): poll_interval=options.poll_interval) else: return -def handle_import(options, session, args): +def handle_import(goptions, session, args): "[admin] Import externally built RPMs into the database" usage = _("usage: %prog import [options] package [package...]") usage += _("\n(Specify the --help global option for a list of other help options)") @@ -1037,7 +1037,7 @@ def handle_import(options, session, args): except (ValueError, TypeError): parser.error(_("Invalid value for epoch: %s") % options.src_epoch) assert False # pragma: no cover - activate_session(session) + activate_session(session, goptions) to_import = {} for path in args: data = koji.get_header_fields(path, ('name','version','release','epoch', @@ -1166,7 +1166,7 @@ def handle_import(options, session, args): do_import(path, data) -def handle_import_cg(options, session, args): +def handle_import_cg(goptions, session, args): "[admin] Import external builds with rich metadata" usage = _("usage: %prog import-cg [options] metadata_file files_dir") usage += _("\n(Specify the --help global option for a list of other help options)") @@ -1182,7 +1182,7 @@ def handle_import_cg(options, session, args): if json is None: parser.error(_("Unable to find json module")) assert False # pragma: no cover - activate_session(session) + activate_session(session, goptions) metadata = json.load(open(args[0], 'r')) if 'output' not in metadata: print(_("Metadata contains no output")) @@ -1222,7 +1222,7 @@ def handle_import_cg(options, session, args): session.CGImport(metadata, serverdir) -def handle_import_comps(options, session, args): +def handle_import_comps(goptions, session, args): "Import group/package information from a comps file" usage = _("usage: %prog import-comps [options] ") usage += _("\n(Specify the --help global option for a list of other help options)") @@ -1232,7 +1232,7 @@ def handle_import_comps(options, session, args): if len(args) != 2: parser.error(_("Incorrect number of arguments")) assert False # pragma: no cover - activate_session(session) + activate_session(session, goptions) # check if the tag exists dsttag = session.getTag(args[1]) if dsttag is None: @@ -1317,7 +1317,7 @@ def _import_comps_alt(session, filename, tag, options): #yum.comps does not support metapkgs -def handle_import_sig(options, session, args): +def handle_import_sig(goptions, session, args): "[admin] Import signatures into the database" usage = _("usage: %prog import-sig [options] package [package...]") usage += _("\n(Specify the --help global option for a list of other help options)") @@ -1335,7 +1335,7 @@ def handle_import_sig(options, session, args): for path in args: if not os.path.exists(path): parser.error(_("No such file: %s") % path) - activate_session(session) + activate_session(session, goptions) for path in args: data = koji.get_header_fields(path, ('name','version','release','arch','siggpg','sigpgp','sourcepackage')) if data['sourcepackage']: @@ -1380,7 +1380,7 @@ def handle_import_sig(options, session, args): session.writeSignedRPM(rinfo['id'], sigkey) -def handle_write_signed_rpm(options, session, args): +def handle_write_signed_rpm(goptions, session, args): "[admin] Write signed RPMs to disk" usage = _("usage: %prog write-signed-rpm [options] n-v-r [n-v-r...]") usage += _("\n(Specify the --help global option for a list of other help options)") @@ -1395,7 +1395,7 @@ def handle_write_signed_rpm(options, session, args): parser.error(_("At least one RPM must be specified")) assert False # pragma: no cover key = args.pop(0) - activate_session(session) + activate_session(session, goptions) if options.all: rpms = session.queryRPMSigs(sigkey=key) rpms = [session.getRPM(r['rpm_id']) for r in rpms] @@ -1760,7 +1760,7 @@ def handle_prune_signed_copies(options, session, args): print("Files: %i" % total_files) print("Bytes: %i" % total_space) -def handle_set_build_volume(options, session, args): +def handle_set_build_volume(goptions, session, args): "[admin] Move a build to a different volume" usage = _("usage: %prog set-build-volume volume n-v-r [n-v-r ...]") usage += _("\n(Specify the --help global option for a list of other help options)") @@ -1773,7 +1773,7 @@ def handle_set_build_volume(options, session, args): if not volinfo: print("No such volume: %s" % args[0]) return 1 - activate_session(session) + activate_session(session, goptions) builds = [] for nvr in args[1:]: binfo = session.getBuild(nvr) @@ -1791,7 +1791,7 @@ def handle_set_build_volume(options, session, args): if options.verbose: print("%s: %s -> %s" % (binfo['nvr'], binfo['volume_name'], volinfo['name'])) -def handle_add_volume(options, session, args): +def handle_add_volume(goptions, session, args): "[admin] Add a new storage volume" usage = _("usage: %prog add-volume volume-name") usage += _("\n(Specify the --help global option for a list of other help options)") @@ -1804,7 +1804,7 @@ def handle_add_volume(options, session, args): if volinfo: print("Volume %s already exists" % name) return 1 - activate_session(session) + activate_session(session, goptions) volinfo = session.addVolume(name) print("Added volume %(name)s with id %(id)i" % volinfo) @@ -1817,7 +1817,7 @@ def handle_list_volumes(options, session, args): for volinfo in session.listVolumes(): print(volinfo['name']) -def handle_list_permissions(options, session, args): +def handle_list_permissions(goptions, session, args): "[info] List user permissions" usage = _("usage: %prog list-permissions [options]") usage += _("\n(Specify the --help global option for a list of other help options)") @@ -1828,7 +1828,7 @@ def handle_list_permissions(options, session, args): if len(args) > 0: parser.error(_("This command takes no arguments")) assert False # pragma: no cover - activate_session(session) + activate_session(session, goptions) if options.user: user = session.getUser(options.user) if not user: @@ -1842,7 +1842,7 @@ def handle_list_permissions(options, session, args): for perm in perms: print(perm) -def handle_add_user(options, session, args): +def handle_add_user(goptions, session, args): "[admin] Add a user" usage = _("usage: %prog add-user username [options]") usage += _("\n(Specify the --help global option for a list of other help options)") @@ -1859,11 +1859,11 @@ def handle_add_user(options, session, args): status = koji.USER_STATUS['BLOCKED'] else: status = koji.USER_STATUS['NORMAL'] - activate_session(session) + activate_session(session, goptions) user_id = session.createUser(username, status=status, krb_principal=options.principal) print("Added user %s (%i)" % (username, user_id)) -def handle_enable_user(options, session, args): +def handle_enable_user(goptions, session, args): "[admin] Enable logins by a user" usage = _("usage: %prog enable-user username") usage += _("\n(Specify the --help global option for a list of other help options)") @@ -1874,10 +1874,10 @@ def handle_enable_user(options, session, args): elif len(args) > 1: parser.error(_("This command only accepts one argument (username)")) username = args[0] - activate_session(session) + activate_session(session, goptions) session.enableUser(username) -def handle_disable_user(options, session, args): +def handle_disable_user(goptions, session, args): "[admin] Disable logins by a user" usage = _("usage: %prog disable-user username") usage += _("\n(Specify the --help global option for a list of other help options)") @@ -1888,10 +1888,10 @@ def handle_disable_user(options, session, args): elif len(args) > 1: parser.error(_("This command only accepts one argument (username)")) username = args[0] - activate_session(session) + activate_session(session, goptions) session.disableUser(username) -def handle_list_signed(options, session, args): +def handle_list_signed(goptions, session, args): "[admin] List signed copies of rpms" usage = _("usage: %prog list-signed [options]") usage += _("\n(Specify the --help global option for a list of other help options)") @@ -1902,7 +1902,7 @@ def handle_list_signed(options, session, args): parser.add_option("--rpm", help=_("Only list signed copies for this RPM")) parser.add_option("--tag", help=_("Only list RPMs within this tag")) (options, args) = parser.parse_args(args) - activate_session(session) + activate_session(session, goptions) qopts = {} build_idx = {} rpm_idx = {} @@ -1963,7 +1963,7 @@ def handle_list_signed(options, session, args): continue print(signedpath) -def handle_import_in_place(options, session, args): +def handle_import_in_place(goptions, session, args): "[admin] Import RPMs that are already in place" usage = _("usage: %prog import-in-place [options] package [package...]") usage += _("\n(Specify the --help global option for a list of other help options)") @@ -1972,7 +1972,7 @@ def handle_import_in_place(options, session, args): if len(args) < 1: parser.error(_("At least one package must be specified")) assert False # pragma: no cover - activate_session(session) + activate_session(session, goptions) for nvr in args: data = koji.parse_NVR(nvr) sys.stdout.write(_("importing %s... ") % nvr) @@ -2005,7 +2005,7 @@ def handle_import_archive(options, session, args): parser.error(_("You must specify a build ID or N-V-R and an archive to import")) assert False # pragma: no cover - activate_session(session) + activate_session(session, options) if not suboptions.type: parser.error(_("You must specify an archive type")) @@ -2089,7 +2089,7 @@ def handle_import_archive(options, session, args): session.importArchive(serverpath, buildinfo, suboptions.type, suboptions.type_info) print("Imported: %s" % filename) -def handle_grant_permission(options, session, args): +def handle_grant_permission(goptions, session, args): "[admin] Grant a permission to a user" usage = _("usage: %prog grant-permission [ ...]") usage += _("\n(Specify the --help global option for a list of other help options)") @@ -2099,7 +2099,7 @@ def handle_grant_permission(options, session, args): if len(args) < 2: parser.error(_("Please specify a permission and at least one user")) assert False # pragma: no cover - activate_session(session) + activate_session(session, goptions) perm = args[0] names = args[1:] users = [] @@ -2115,7 +2115,7 @@ def handle_grant_permission(options, session, args): for user in users: session.grantPermission(user['name'], perm, **kwargs) -def handle_revoke_permission(options, session, args): +def handle_revoke_permission(goptions, session, args): "[admin] Revoke a permission from a user" usage = _("usage: %prog revoke-permission [ ...]") usage += _("\n(Specify the --help global option for a list of other help options)") @@ -2124,7 +2124,7 @@ def handle_revoke_permission(options, session, args): if len(args) < 2: parser.error(_("Please specify a permission and at least one user")) assert False # pragma: no cover - activate_session(session) + activate_session(session, goptions) perm = args[0] names = args[1:] users = [] @@ -2138,7 +2138,7 @@ def handle_revoke_permission(options, session, args): session.revokePermission(user['name'], perm) -def handle_grant_cg_access(options, session, args): +def handle_grant_cg_access(goptions, session, args): "[admin] Add a user to a content generator" usage = _("usage: %prog grant-cg-access ") usage += _("\n(Specify the --help global option for a list of other help options)") @@ -2148,7 +2148,7 @@ def handle_grant_cg_access(options, session, args): if len(args) != 2: parser.error(_("Please specify a user and content generator")) assert False # pragma: no cover - activate_session(session) + activate_session(session, goptions) user = args[0] cg = args[1] uinfo = session.getUser(user) @@ -2161,7 +2161,7 @@ def handle_grant_cg_access(options, session, args): session.grantCGAccess(uinfo['name'], cg, **kwargs) -def handle_revoke_cg_access(options, session, args): +def handle_revoke_cg_access(goptions, session, args): "[admin] Remove a user from a content generator" usage = _("usage: %prog revoke-cg-access ") usage += _("\n(Specify the --help global option for a list of other help options)") @@ -2170,7 +2170,7 @@ def handle_revoke_cg_access(options, session, args): if len(args) != 2: parser.error(_("Please specify a user and content generator")) assert False # pragma: no cover - activate_session(session) + activate_session(session, goptions) user = args[0] cg = args[1] uinfo = session.getUser(user) @@ -2180,7 +2180,7 @@ def handle_revoke_cg_access(options, session, args): session.revokeCGAccess(uinfo['name'], cg) -def anon_handle_latest_build(options, session, args): +def anon_handle_latest_build(goptions, session, args): "[info] Print the latest builds for a tag" usage = _("usage: %prog latest-build [options] tag package [package...]") usage += _("\n(Specify the --help global option for a list of other help options)") @@ -2194,7 +2194,7 @@ def anon_handle_latest_build(options, session, args): if len(args) == 0: parser.error(_("A tag name must be specified")) assert False # pragma: no cover - activate_session(session) + activate_session(session, goptions) if options.all: if len(args) > 1: parser.error(_("A package name may not be combined with --all")) @@ -2254,7 +2254,7 @@ def anon_handle_latest_build(options, session, args): print(line) -def anon_handle_list_api(options, session, args): +def anon_handle_list_api(goptions, session, args): "[info] Print the list of XML-RPC APIs" usage = _("usage: %prog list-api [options]") usage += _("\n(Specify the --help global option for a list of other help options)") @@ -2263,7 +2263,7 @@ def anon_handle_list_api(options, session, args): if len(args) != 0: parser.error(_("This command takes no arguments")) assert False # pragma: no cover - activate_session(session) + activate_session(session, goptions) tmplist = [(x['name'], x) for x in session._listapi()] tmplist.sort() funcs = [x[1] for x in tmplist] @@ -2285,7 +2285,7 @@ def anon_handle_list_api(options, session, args): if x['doc']: print(" description: %s" % x['doc']) -def anon_handle_list_tagged(options, session, args): +def anon_handle_list_tagged(goptions, session, args): "[info] List the builds or rpms in a tag" usage = _("usage: %prog list-tagged [options] tag [package]") usage += _("\n(Specify the --help global option for a list of other help options)") @@ -2309,7 +2309,7 @@ def anon_handle_list_tagged(options, session, args): elif len(args) > 2: parser.error(_("Only one package name may be specified")) assert False # pragma: no cover - activate_session(session) + activate_session(session, goptions) pathinfo = koji.PathInfo() package = None if len(args) > 1: @@ -2387,7 +2387,7 @@ def anon_handle_list_tagged(options, session, args): for line in output: print(line) -def anon_handle_list_buildroot(options, session, args): +def anon_handle_list_buildroot(goptions, session, args): "[info] List the rpms used in or built in a buildroot" usage = _("usage: %prog list-buildroot [options] buildroot-id") usage += _("\n(Specify the --help global option for a list of other help options)") @@ -2399,7 +2399,7 @@ def anon_handle_list_buildroot(options, session, args): if len(args) != 1: parser.error(_("Incorrect number of arguments")) assert False # pragma: no cover - activate_session(session) + activate_session(session, goptions) buildrootID = int(args[0]) opts = {} if options.built: @@ -2417,7 +2417,7 @@ def anon_handle_list_buildroot(options, session, args): else: print(nvra) -def anon_handle_list_untagged(options, session, args): +def anon_handle_list_untagged(goptions, session, args): "[info] List untagged builds" usage = _("usage: %prog list-untagged [options] [package]") usage += _("\n(Specify the --help global option for a list of other help options)") @@ -2428,7 +2428,7 @@ def anon_handle_list_untagged(options, session, args): if len(args) > 1: parser.error(_("Only one package name may be specified")) assert False # pragma: no cover - activate_session(session) + activate_session(session, goptions) package = None if len(args) > 0: package = args[0] @@ -2473,7 +2473,7 @@ def print_group_list_req_group(group): def print_group_list_req_package(pkg): print(" %(package)s: %(basearchonly)s, %(type)s [%(tag_name)s]" % pkg) -def anon_handle_list_groups(options, session, args): +def anon_handle_list_groups(goptions, session, args): "[info] Print the group listings" usage = _("usage: %prog list-groups [options] [group]") usage += _("\n(Specify the --help global option for a list of other help options)") @@ -2486,7 +2486,7 @@ def anon_handle_list_groups(options, session, args): parser.error(_("Incorrect number of arguments")) assert False # pragma: no cover opts = {} - activate_session(session) + activate_session(session, goptions) event = koji.util.eventFromOpts(session, options) if event: opts['event'] = event['id'] @@ -2511,7 +2511,7 @@ def anon_handle_list_groups(options, session, args): x['tag_name'] = tags.get(x['tag_id'], x['tag_id']) print_group_list_req_package(x) -def handle_add_group_pkg(options, session, args): +def handle_add_group_pkg(goptions, session, args): "[admin] Add a package to a group's package listing" usage = _("usage: %prog add-group-pkg [options] [...]") usage += _("\n(Specify the --help global option for a list of other help options)") @@ -2522,11 +2522,11 @@ def handle_add_group_pkg(options, session, args): assert False # pragma: no cover tag = args[0] group = args[1] - activate_session(session) + activate_session(session, goptions) for pkg in args[2:]: session.groupPackageListAdd(tag, group, pkg) -def handle_block_group_pkg(options, session, args): +def handle_block_group_pkg(goptions, session, args): "[admin] Block a package from a group's package listing" usage = _("usage: %prog block-group-pkg [options] [...]") usage += _("\n(Specify the --help global option for a list of other help options)") @@ -2537,11 +2537,11 @@ def handle_block_group_pkg(options, session, args): assert False # pragma: no cover tag = args[0] group = args[1] - activate_session(session) + activate_session(session, goptions) for pkg in args[2:]: session.groupPackageListBlock(tag, group, pkg) -def handle_unblock_group_pkg(options, session, args): +def handle_unblock_group_pkg(goptions, session, args): "[admin] Unblock a package from a group's package listing" usage = _("usage: %prog unblock-group-pkg [options] [...]") usage += _("\n(Specify the --help global option for a list of other help options)") @@ -2552,11 +2552,11 @@ def handle_unblock_group_pkg(options, session, args): assert False # pragma: no cover tag = args[0] group = args[1] - activate_session(session) + activate_session(session, goptions) for pkg in args[2:]: session.groupPackageListUnblock(tag, group, pkg) -def handle_add_group_req(options, session, args): +def handle_add_group_req(goptions, session, args): "[admin] Add a group to a group's required list" usage = _("usage: %prog add-group-req [options] ") usage += _("\n(Specify the --help global option for a list of other help options)") @@ -2568,10 +2568,10 @@ def handle_add_group_req(options, session, args): tag = args[0] group = args[1] req = args[2] - activate_session(session) + activate_session(session, goptions) session.groupReqListAdd(tag, group, req) -def handle_block_group_req(options, session, args): +def handle_block_group_req(goptions, session, args): "[admin] Block a group's requirement listing" usage = _("usage: %prog block-group-req [options] ") usage += _("\n(Specify the --help global option for a list of other help options)") @@ -2583,10 +2583,10 @@ def handle_block_group_req(options, session, args): tag = args[0] group = args[1] req = args[2] - activate_session(session) + activate_session(session, goptions) session.groupReqListBlock(tag, group, req) -def handle_unblock_group_req(options, session, args): +def handle_unblock_group_req(goptions, session, args): "[admin] Unblock a group's requirement listing" usage = _("usage: %prog unblock-group-req [options] ") usage += _("\n(Specify the --help global option for a list of other help options)") @@ -2598,7 +2598,7 @@ def handle_unblock_group_req(options, session, args): tag = args[0] group = args[1] req = args[2] - activate_session(session) + activate_session(session, goptions) session.groupReqListUnblock(tag, group, req) def anon_handle_list_channels(goptions, session, args): @@ -2620,7 +2620,7 @@ def anon_handle_list_channels(goptions, session, args): for channel in channels: print("%(name)-15s %(hosts) 5d" % channel) -def anon_handle_list_hosts(options, session, args): +def anon_handle_list_hosts(goptions, session, args): "[info] Print the host listing" usage = _("usage: %prog list-hosts [options]") usage += _("\n(Specify the --help global option for a list of other help options)") @@ -2634,7 +2634,7 @@ def anon_handle_list_hosts(options, session, args): parser.add_option("--quiet", action="store_true", help=_("Do not print header information"), default=options.quiet) (options, args) = parser.parse_args(args) opts = {} - activate_session(session) + activate_session(session, goptions) if options.arch: opts['arches'] = options.arch if options.channel: @@ -2675,7 +2675,7 @@ def anon_handle_list_hosts(options, session, args): for host in hosts: print("%(name)-28s %(enabled)-3s %(ready)-3s %(task_load)4.1f/%(capacity)-3.1f %(arches)-16s %(update)s" % host) -def anon_handle_list_pkgs(options, session, args): +def anon_handle_list_pkgs(goptions, session, args): "[info] Print the package listing for tag or for owner" usage = _("usage: %prog list-pkgs [options]") usage += _("\n(Specify the --help global option for a list of other help options)") @@ -2694,7 +2694,7 @@ def anon_handle_list_pkgs(options, session, args): if len(args) != 0: parser.error(_("This command takes no arguments")) assert False # pragma: no cover - activate_session(session) + activate_session(session, goptions) opts = {} if options.owner: user = session.getUser(options.owner) @@ -2752,7 +2752,7 @@ def anon_handle_list_pkgs(options, session, args): fmt = "%(package_name)s" print(fmt % pkg) -def anon_handle_rpminfo(options, session, args): +def anon_handle_rpminfo(goptions, session, args): "[info] Print basic information about an RPM" usage = _("usage: %prog rpminfo [options] [ ...]") usage += _("\n(Specify the --help global option for a list of other help options)") @@ -2762,7 +2762,7 @@ def anon_handle_rpminfo(options, session, args): if len(args) < 1: parser.error(_("Please specify an RPM")) assert False # pragma: no cover - activate_session(session) + activate_session(session, goptions) for rpm in args: info = session.getRPM(rpm) if info is None: @@ -2818,7 +2818,7 @@ def anon_handle_rpminfo(options, session, args): print(" %(id)8i %(tag_name)-28s %(arch)-8s %(host_name)-29s" % br_info) -def anon_handle_buildinfo(options, session, args): +def anon_handle_buildinfo(goptions, session, args): "[info] Print basic information about a build" usage = _("usage: %prog buildinfo [options] [ ...]") usage += _("\n(Specify the --help global option for a list of other help options)") @@ -2828,7 +2828,7 @@ def anon_handle_buildinfo(options, session, args): if len(args) < 1: parser.error(_("Please specify a build")) assert False # pragma: no cover - activate_session(session) + activate_session(session, goptions) for build in args: if build.isdigit(): build = int(build) @@ -2909,7 +2909,7 @@ def anon_handle_buildinfo(options, session, args): print("Changelog:") print(koji.util.formatChangelog(changelog)) -def anon_handle_hostinfo(options, session, args): +def anon_handle_hostinfo(goptions, session, args): "[info] Print basic information about a host" usage = _("usage: %prog hostinfo [options] [ ...]") usage += _("\n(Specify the --help global option for a list of other help options)") @@ -2918,7 +2918,7 @@ def anon_handle_hostinfo(options, session, args): if len(args) < 1: parser.error(_("Please specify a host")) assert False # pragma: no cover - activate_session(session) + activate_session(session, goptions) for host in args: if host.isdigit(): host = int(host) @@ -2967,7 +2967,7 @@ def anon_handle_hostinfo(options, session, args): else: print("None") -def handle_clone_tag(options, session, args): +def handle_clone_tag(goptions, session, args): "[admin] Duplicate the contents of one tag onto another tag" usage = _("usage: %prog clone-tag [options] ") usage += _("\n(Specify the --help global option for a list of other help options)") @@ -3003,7 +3003,7 @@ def handle_clone_tag(options, session, args): if len(args) != 2: parser.error(_("This command takes two arguments: ")) assert False # pragma: no cover - activate_session(session) + activate_session(session, goptions) if not session.hasPerm('admin') and not options.test: print(_("This action requires admin privileges")) @@ -3324,7 +3324,7 @@ def handle_clone_tag(options, session, args): sys.stdout.write(gfmt % changes) -def handle_add_target(options, session, args): +def handle_add_target(goptions, session, args): "[admin] Create a new build target" usage = _("usage: %prog add-target name build-tag ") usage += _("\n(Specify the --help global option for a list of other help options)") @@ -3343,7 +3343,7 @@ def handle_add_target(options, session, args): else: #most targets have the same name as their destination dest_tag = name - activate_session(session) + activate_session(session, goptions) if not session.hasPerm('admin'): print("This action requires admin privileges") return 1 @@ -3362,7 +3362,7 @@ def handle_add_target(options, session, args): session.createBuildTarget(name, build_tag, dest_tag) -def handle_edit_target(options, session, args): +def handle_edit_target(goptions, session, args): "[admin] Set the name, build_tag, and/or dest_tag of an existing build target to new values" usage = _("usage: %prog edit-target [options] name") usage += _("\n(Specify the --help global option for a list of other help options)") @@ -3376,7 +3376,7 @@ def handle_edit_target(options, session, args): if len(args) != 1: parser.error(_("Please specify a build target")) assert False # pragma: no cover - activate_session(session) + activate_session(session, goptions) if not session.hasPerm('admin'): print("This action requires admin privileges") @@ -3408,7 +3408,7 @@ def handle_edit_target(options, session, args): session.editBuildTarget(targetInfo['orig_name'], targetInfo['name'], targetInfo['build_tag_name'], targetInfo['dest_tag_name']) -def handle_remove_target(options, session, args): +def handle_remove_target(goptions, session, args): "[admin] Remove a build target" usage = _("usage: %prog remove-target [options] name") usage += _("\n(Specify the --help global option for a list of other help options)") @@ -3418,7 +3418,7 @@ def handle_remove_target(options, session, args): if len(args) != 1: parser.error(_("Please specify a build target to remove")) assert False # pragma: no cover - activate_session(session) + activate_session(session, goptions) if not session.hasPerm('admin'): print("This action requires admin privileges") @@ -3432,7 +3432,7 @@ def handle_remove_target(options, session, args): session.deleteBuildTarget(target_info['id']) -def handle_remove_tag(options, session, args): +def handle_remove_tag(goptions, session, args): "[admin] Remove a tag" usage = _("usage: %prog remove-tag [options] name") usage += _("\n(Specify the --help global option for a list of other help options)") @@ -3442,7 +3442,7 @@ def handle_remove_tag(options, session, args): if len(args) != 1: parser.error(_("Please specify a tag to remove")) assert False # pragma: no cover - activate_session(session) + activate_session(session, goptions) if not session.hasPerm('admin'): print("This action requires admin privileges") @@ -3456,7 +3456,7 @@ def handle_remove_tag(options, session, args): session.deleteTag(tag_info['id']) -def anon_handle_list_targets(options, session, args): +def anon_handle_list_targets(goptions, session, args): "[info] List the build targets" usage = _("usage: %prog list-targets [options]") usage += _("\n(Specify the --help global option for a list of other help options)") @@ -3467,7 +3467,7 @@ def anon_handle_list_targets(options, session, args): if len(args) != 0: parser.error(_("This command takes no arguments")) assert False # pragma: no cover - activate_session(session) + activate_session(session, goptions) fmt = "%(name)-30s %(build_tag_name)-30s %(dest_tag_name)-30s" if not options.quiet: @@ -3520,7 +3520,7 @@ def _printInheritance(tags, sibdepths=None, reverse=False): _printInheritance(tags, sibdepths, reverse) -def anon_handle_list_tag_inheritance(options, session, args): +def anon_handle_list_tag_inheritance(goptions, session, args): "[info] Print the inheritance information for a tag" usage = _("usage: %prog list-tag-inheritance [options] ") usage += _("\n(Specify the --help global option for a list of other help options)") @@ -3535,7 +3535,7 @@ def anon_handle_list_tag_inheritance(options, session, args): if len(args) != 1: parser.error(_("This command takes exctly one argument: a tag name or ID")) assert False # pragma: no cover - activate_session(session) + activate_session(session, goptions) event = koji.util.eventFromOpts(session, options) if event: event['timestr'] = time.asctime(time.localtime(event['ts'])) @@ -3575,7 +3575,7 @@ def anon_handle_list_tag_inheritance(options, session, args): data = session.getFullInheritance(tag['id'], **opts) _printInheritance(data, None, opts['reverse']) -def anon_handle_list_tags(options, session, args): +def anon_handle_list_tags(goptions, session, args): "[info] Print the list of tags" usage = _("usage: %prog list-tags [options] [pattern]") usage += _("\n(Specify the --help global option for a list of other help options)") @@ -3586,7 +3586,7 @@ def anon_handle_list_tags(options, session, args): parser.add_option("--build", help=_("Show tags associated with a build")) parser.add_option("--package", help=_("Show tags associated with a package")) (options, args) = parser.parse_args(args) - activate_session(session) + activate_session(session, goptions) pkginfo = {} buildinfo = {} @@ -3631,7 +3631,7 @@ def anon_handle_list_tags(options, session, args): sys.stdout.write(' [%(perm)s perm required]' % tag) print('') -def anon_handle_list_tag_history(options, session, args): +def anon_handle_list_tag_history(goptions, session, args): "[info] Print a history of tag operations" usage = _("usage: %prog list-tag-history [options]") usage += _("\n(Specify the --help global option for a list of other help options)") @@ -3659,7 +3659,7 @@ def anon_handle_list_tag_history(options, session, args): if not limited and not options.all: parser.error(_("Please specify an option to limit the query")) - activate_session(session) + activate_session(session, goptions) hist = session.tagHistory(**kwargs) timeline = [] @@ -3902,7 +3902,7 @@ _table_keys = { 'group_package_listing' : ['group_id', 'tag_id', 'package'], } -def anon_handle_list_history(options, session, args): +def anon_handle_list_history(goptions, session, args): "[info] Display historical data" usage = _("usage: %prog list-history [options]") usage += _("\n(Specify the --help global option for a list of other help options)") @@ -3931,7 +3931,6 @@ def anon_handle_list_history(options, session, args): parser.add_option("-v", "--verbose", action="store_true", help=_("Show more detail")) parser.add_option("-e", "--events", action="store_true", help=_("Show event ids")) parser.add_option("--all", action="store_true", help=_("Allows listing the entire global history")) - global_options = options (options, args) = parser.parse_args(args) if len(args) != 0: parser.error(_("This command takes no arguments")) @@ -3975,7 +3974,7 @@ def anon_handle_list_history(options, session, args): if not limited and not options.all: parser.error(_("Please specify an option to limit the query")) - activate_session(session) + activate_session(session, goptions) if options.watch: if not kwargs.get('afterEvent') and not kwargs.get('after'): @@ -4027,7 +4026,7 @@ def anon_handle_list_history(options, session, args): if not options.watch: break else: - time.sleep(global_options.poll_interval) + time.sleep(goptions.poll_interval) # repeat query for later events if last_event: kwargs['afterEvent'] = last_event @@ -4276,7 +4275,7 @@ def _printTaskInfo(session, task_id, topdir, level=0, recurse=True, verbose=True for child in children: _printTaskInfo(session, child['id'], topdir, level, verbose=verbose) -def anon_handle_taskinfo(global_options, session, args): +def anon_handle_taskinfo(goptions, session, args): """[info] Show information about a task""" usage = _("usage: %prog taskinfo [options] taskID [taskID...]") usage += _("\n(Specify the --help global option for a list of other help options)") @@ -4288,13 +4287,13 @@ def anon_handle_taskinfo(global_options, session, args): parser.error(_("You must specify at least one task ID")) assert False # pragma: no cover - activate_session(session) + activate_session(session, goptions) for arg in args: task_id = int(arg) - _printTaskInfo(session, task_id, options.topdir, 0, options.recurse, options.verbose) + _printTaskInfo(session, task_id, goptions.topdir, 0, options.recurse, options.verbose) -def anon_handle_taginfo(options, session, args): +def anon_handle_taginfo(goptions, session, args): "[info] Print basic information about a tag" usage = _("usage: %prog taginfo [options] [ ...]") usage += _("\n(Specify the --help global option for a list of other help options)") @@ -4306,7 +4305,7 @@ def anon_handle_taginfo(options, session, args): if len(args) < 1: parser.error(_("Please specify a tag")) assert False # pragma: no cover - activate_session(session) + activate_session(session, goptions) event = koji.util.eventFromOpts(session, options) event_opts = {} if event: @@ -4395,7 +4394,7 @@ def anon_handle_taginfo(options, session, args): print(" package filter: %(pkg_filter)s" % parent) -def handle_add_tag(options, session, args): +def handle_add_tag(goptions, session, args): "[admin] Add a new tag to the database" usage = _("usage: %prog add-tag [options] name") usage += _("\n(Specify the --help global option for a list of other help options)") @@ -4410,7 +4409,7 @@ def handle_add_tag(options, session, args): if len(args) != 1: parser.error(_("Please specify a name for the tag")) assert False # pragma: no cover - activate_session(session) + activate_session(session, goptions) if not session.hasPerm('admin'): print("This action requires admin privileges") return @@ -4432,7 +4431,7 @@ def handle_add_tag(options, session, args): opts['extra'] = extra session.createTag(args[0],**opts) -def handle_edit_tag(options, session, args): +def handle_edit_tag(goptions, session, args): "[admin] Alter tag information" usage = _("usage: %prog edit-tag [options] name") usage += _("\n(Specify the --help global option for a list of other help options)") @@ -4455,7 +4454,7 @@ def handle_edit_tag(options, session, args): if len(args) != 1: parser.error(_("Please specify a name for the tag")) assert False # pragma: no cover - activate_session(session) + activate_session(session, goptions) tag = args[0] opts = {} if options.arches: @@ -4490,7 +4489,7 @@ def handle_edit_tag(options, session, args): #XXX change callname session.editTag2(tag, **opts) -def handle_lock_tag(options, session, args): +def handle_lock_tag(goptions, session, args): "[admin] Lock a tag" usage = _("usage: %prog lock-tag [options] [ ...] ") usage += _("\n(Specify the --help global option for a list of other help options)") @@ -4503,7 +4502,7 @@ def handle_lock_tag(options, session, args): if len(args) < 1: parser.error(_("Please specify a tag")) assert False # pragma: no cover - activate_session(session) + activate_session(session, goptions) pdata = session.getAllPerms() perm_ids = dict([(p['name'], p['id']) for p in pdata]) perm = options.perm @@ -4540,7 +4539,7 @@ def handle_lock_tag(options, session, args): continue session.editTag2(tag['id'], perm=perm_id) -def handle_unlock_tag(options, session, args): +def handle_unlock_tag(goptions, session, args): "[admin] Unlock a tag" usage = _("usage: %prog unlock-tag [options] [ ...]") usage += _("\n(Specify the --help global option for a list of other help options)") @@ -4551,7 +4550,7 @@ def handle_unlock_tag(options, session, args): if len(args) < 1: parser.error(_("Please specify a tag")) assert False # pragma: no cover - activate_session(session) + activate_session(session, goptions) if options.glob: selected = [] for tag in session.listTags(): @@ -4584,7 +4583,7 @@ def handle_unlock_tag(options, session, args): else: session.editTag2(tag['id'], locked=False, perm_id=None) -def handle_add_tag_inheritance(options, session, args): +def handle_add_tag_inheritance(goptions, session, args): """[admin] Add to a tag's inheritance""" usage = _("usage: %prog add-tag-inheritance [options] tag parent-tag") usage += _("\n(Specify the --help global option for a list of other help options)") @@ -4601,7 +4600,7 @@ def handle_add_tag_inheritance(options, session, args): parser.error(_("This command takes exctly two argument: a tag name or ID and that tag's new parent name or ID")) assert False # pragma: no cover - activate_session(session) + activate_session(session, goptions) tag = session.getTag(args[0]) if not tag: @@ -4640,7 +4639,7 @@ def handle_add_tag_inheritance(options, session, args): session.setInheritanceData(tag['id'], inheritanceData) -def handle_edit_tag_inheritance(options, session, args): +def handle_edit_tag_inheritance(goptions, session, args): """[admin] Edit tag inheritance""" usage = _("usage: %prog edit-tag-inheritance [options] tag ") usage += _("\n(Specify the --help global option for a list of other help options)") @@ -4660,7 +4659,7 @@ def handle_edit_tag_inheritance(options, session, args): parser.error(_("This command takes at most three argument: a tag name or ID, a parent tag name or ID, and a priority")) assert False # pragma: no cover - activate_session(session) + activate_session(session, goptions) tag = session.getTag(args[0]) if not tag: @@ -4727,7 +4726,7 @@ def handle_edit_tag_inheritance(options, session, args): inheritanceData[index] = new_data session.setInheritanceData(tag['id'], inheritanceData) -def handle_remove_tag_inheritance(options, session, args): +def handle_remove_tag_inheritance(goptions, session, args): """[admin] Remove a tag inheritance link""" usage = _("usage: %prog remove-tag-inheritance tag ") usage += _("\n(Specify the --help global option for a list of other help options)") @@ -4742,7 +4741,7 @@ def handle_remove_tag_inheritance(options, session, args): parser.error(_("This command takes at most three argument: a tag name or ID, a parent tag name or ID, and a priority")) assert False # pragma: no cover - activate_session(session) + activate_session(session, goptions) tag = session.getTag(args[0]) if not tag: @@ -4790,7 +4789,7 @@ def handle_remove_tag_inheritance(options, session, args): inheritanceData[index] = new_data session.setInheritanceData(tag['id'], inheritanceData) -def anon_handle_show_groups(options, session, args): +def anon_handle_show_groups(goptions, session, args): "[info] Show groups data for a tag" usage = _("usage: %prog show-groups [options] ") usage += _("\n(Specify the --help global option for a list of other help options)") @@ -4803,7 +4802,7 @@ def anon_handle_show_groups(options, session, args): if len(args) != 1: parser.error(_("Incorrect number of arguments")) assert False # pragma: no cover - activate_session(session) + activate_session(session, goptions) tag = args[0] groups = session.getTagGroups(tag) if options.comps: @@ -4813,7 +4812,7 @@ def anon_handle_show_groups(options, session, args): else: pprint.pprint(groups) -def anon_handle_list_external_repos(options, session, args): +def anon_handle_list_external_repos(goptions, session, args): "[info] List external repos" usage = _("usage: %prog list-external-repos [options]") usage += _("\n(Specify the --help global option for a list of other help options)") @@ -4834,7 +4833,7 @@ def anon_handle_list_external_repos(options, session, args): if len(args) > 0: parser.error(_("This command takes no arguments")) assert False # pragma: no cover - activate_session(session) + activate_session(session, goptions) opts = {} event = koji.util.eventFromOpts(session, options) if event: @@ -4912,7 +4911,7 @@ def _parse_tagpri(tagpri): raise koji.GenericError("Invalid priority: %s" % parts[1]) return tag, pri -def handle_add_external_repo(options, session, args): +def handle_add_external_repo(goptions, session, args): "[admin] Create an external repo and/or add one to a tag" usage = _("usage: %prog add-external-repo [options] name [url]") usage += _("\n(Specify the --help global option for a list of other help options)") @@ -4922,7 +4921,7 @@ def handle_add_external_repo(options, session, args): parser.add_option("-p", "--priority", type='int', help=_("Set priority (when adding to tag)")) (options, args) = parser.parse_args(args) - activate_session(session) + activate_session(session, goptions) if len(args) == 1: name = args[0] rinfo = session.getExternalRepo(name, strict=True) @@ -4947,7 +4946,7 @@ def handle_add_external_repo(options, session, args): print("Added external repo %s to tag %s (priority %i)" \ % (rinfo['name'], tag, priority)) -def handle_edit_external_repo(options, session, args): +def handle_edit_external_repo(goptions, session, args): "[admin] Edit data for an external repo" usage = _("usage: %prog edit-external-repo name") usage += _("\n(Specify the --help global option for a list of other help options)") @@ -4966,10 +4965,10 @@ def handle_edit_external_repo(options, session, args): opts['name'] = options.name if not opts: parser.error(_("No changes specified")) - activate_session(session) + activate_session(session, goptions) session.editExternalRepo(args[0], **opts) -def handle_remove_external_repo(options, session, args): +def handle_remove_external_repo(goptions, session, args): "[admin] Remove an external repo from a tag or tags, or remove entirely" usage = _("usage: %prog remove-external-repo repo [tag ...]") usage += _("\n(Specify the --help global option for a list of other help options)") @@ -4980,7 +4979,7 @@ def handle_remove_external_repo(options, session, args): if len(args) < 1: parser.error(_("Incorrect number of arguments")) assert False # pragma: no cover - activate_session(session) + activate_session(session, goptions) repo = args[0] tags = args[1:] delete = not bool(tags) @@ -5237,7 +5236,7 @@ def _build_image_indirection(options, task_opts, session, args): print("Missing the following required options: %s" % ' '.join(['--%s' % o.replace('_','-') for o in missing])) raise koji.GenericError(_("Missing required options specified above")) - activate_session(session) + activate_session(session, options) # Set the task's priority. Users can only lower it with --background. priority = None @@ -5432,7 +5431,7 @@ def _build_image(options, task_opts, session, args, img_type): if img_type not in ('livecd', 'appliance', 'livemedia'): raise koji.GenericError('Unrecognized image type: %s' % img_type) - activate_session(session) + activate_session(session, options) # Set the task's priority. Users can only lower it with --background. priority = None @@ -5505,7 +5504,7 @@ def _build_image_oz(options, task_opts, session, args): A private helper function that houses common CLI code for building images with Oz and ImageFactory """ - activate_session(session) + activate_session(session, options) # Set the task's priority. Users can only lower it with --background. priority = None @@ -5611,7 +5610,7 @@ def handle_win_build(options, session, args): if len(args) != 3: parser.error(_("Exactly three arguments (a build target, a SCM URL, and a VM name) are required")) assert False # pragma: no cover - activate_session(session) + activate_session(session, options) target = args[0] if target.lower() == "none" and build_opts.repo_id: target = None @@ -5648,13 +5647,13 @@ def handle_win_build(options, session, args): else: return -def handle_free_task(options, session, args): +def handle_free_task(goptions, session, args): "[admin] Free a task" usage = _("usage: %prog free-task [options] [ ...]") usage += _("\n(Specify the --help global option for a list of other help options)") parser = OptionParser(usage=usage) (options, args) = parser.parse_args(args) - activate_session(session) + activate_session(session, goptions) tlist = [] for task_id in args: try: @@ -5665,7 +5664,7 @@ def handle_free_task(options, session, args): for task_id in tlist: session.freeTask(task_id) -def handle_cancel(options, session, args): +def handle_cancel(goptions, session, args): "[build] Cancel tasks and/or builds" usage = _("usage: %prog cancel [options] [ ...]") usage += _("\n(Specify the --help global option for a list of other help options)") @@ -5677,7 +5676,7 @@ def handle_cancel(options, session, args): if len(args) == 0: parser.error(_("You must specify at least one task id or build")) assert False # pragma: no cover - activate_session(session) + activate_session(session, goptions) tlist = [] blist = [] for arg in args: @@ -5704,7 +5703,7 @@ def handle_cancel(options, session, args): for build in blist: session.cancelBuild(build) -def handle_set_task_priority(options, session, args): +def handle_set_task_priority(goptions, session, args): "[admin] Set task priority" usage = _("usage: %prog set-task-priority [options] --priority= [task-id]...") usage += _("\n(Specify the --help global option for a list of other help options)") @@ -5724,7 +5723,7 @@ def handle_set_task_priority(options, session, args): except ValueError: parser.error(_("Task numbers must be integers")) - activate_session(session) + activate_session(session, goptions) for task_id in tasks: session.setTaskPriority(task_id, options.priority, options.recurse) @@ -5782,7 +5781,7 @@ def _list_tasks(options, session): return tasklist -def handle_list_tasks(options, session, args): +def handle_list_tasks(goptions, session, args): "[info] Print the list of tasks" usage = _("usage: %prog list-tasks [options]") usage += _("\n(Specify the --help global option for a list of other help options)") @@ -5799,7 +5798,7 @@ def handle_list_tasks(options, session, args): parser.error(_("This command takes no arguments")) assert False # pragma: no cover - activate_session(session) + activate_session(session, goptions) tasklist = _list_tasks(options, session) if not tasklist: print("(no tasks)") @@ -5812,7 +5811,7 @@ def handle_list_tasks(options, session, args): continue print_task_recurse(t) -def handle_set_pkg_arches(options, session, args): +def handle_set_pkg_arches(goptions, session, args): "[admin] Set the list of extra arches for a package" usage = _("usage: %prog set-pkg-arches [options] arches tag package [package2 ...]") usage += _("\n(Specify the --help global option for a list of other help options)") @@ -5822,14 +5821,14 @@ def handle_set_pkg_arches(options, session, args): if len(args) < 3: parser.error(_("Please specify an archlist, a tag, and at least one package")) assert False # pragma: no cover - activate_session(session) + activate_session(session, goptions) arches = parse_arches(args[0]) tag = args[1] for package in args[2:]: #really should implement multicall... session.packageListSetArches(tag,package,arches,force=options.force) -def handle_set_pkg_owner(options, session, args): +def handle_set_pkg_owner(goptions, session, args): "[admin] Set the owner for a package" usage = _("usage: %prog set-pkg-owner [options] owner tag package [package2 ...]") usage += _("\n(Specify the --help global option for a list of other help options)") @@ -5839,14 +5838,14 @@ def handle_set_pkg_owner(options, session, args): if len(args) < 3: parser.error(_("Please specify an owner, a tag, and at least one package")) assert False # pragma: no cover - activate_session(session) + activate_session(session, goptions) owner = args[0] tag = args[1] for package in args[2:]: #really should implement multicall... session.packageListSetOwner(tag,package,owner,force=options.force) -def handle_set_pkg_owner_global(options, session, args): +def handle_set_pkg_owner_global(goptions, session, args): "[admin] Set the owner for a package globally" usage = _("usage: %prog set-pkg-owner-global [options] owner package [package2 ...]") usage += _("\n(Specify the --help global option for a list of other help options)") @@ -5862,7 +5861,7 @@ def handle_set_pkg_owner_global(options, session, args): elif len(args) < 2: parser.error(_("Please specify an owner and at least one package")) assert False # pragma: no cover - activate_session(session) + activate_session(session, goptions) owner = args[0] packages = args[1:] user = session.getUser(owner) @@ -5905,7 +5904,7 @@ def handle_set_pkg_owner_global(options, session, args): % (entry['package_name'], entry['tag_name'], entry['owner_name'], user['name'])) session.packageListSetOwner(entry['tag_id'], entry['package_name'], user['id']) -def anon_handle_watch_task(global_options, session, args): +def anon_handle_watch_task(goptions, session, args): "[monitor] Track progress of particular tasks" usage = _("usage: %prog watch-task [options] [...]") usage += _("\n(Specify the --help global option for a list of other help options)") @@ -5928,7 +5927,7 @@ def anon_handle_watch_task(global_options, session, args): if args and selection: parser.error(_("Selection options cannot be combined with a task list")) - activate_session(session) + activate_session(session, goptions) if selection: tasks = [task['id'] for task in _list_tasks(options, session)] if not tasks: @@ -5945,16 +5944,16 @@ def anon_handle_watch_task(global_options, session, args): parser.error(_("at least one task id must be specified")) return watch_tasks(session, tasks, quiet=options.quiet, - poll_interval=global_options.poll_interval) + poll_interval=goptions.poll_interval) -def anon_handle_watch_logs(global_options, session, args): +def anon_handle_watch_logs(goptions, session, args): "[monitor] Watch logs in realtime" usage = _("usage: %prog watch-logs [options] [...]") usage += _("\n(Specify the --help global option for a list of other help options)") parser = OptionParser(usage=usage) parser.add_option("--log", help=_("Watch only a specific log")) (options, args) = parser.parse_args(args) - activate_session(session) + activate_session(session, goptions) tasks = [] for task in args: @@ -5965,7 +5964,7 @@ def anon_handle_watch_logs(global_options, session, args): if not tasks: parser.error(_("at least one task id must be specified")) - watch_logs(session, tasks, options, global_options.poll_interval) + watch_logs(session, tasks, options, goptions.poll_interval) def handle_make_task(opts, session, args): "[admin] Create an arbitrary task" @@ -5977,7 +5976,7 @@ def handle_make_task(opts, session, args): parser.add_option("--watch", action="store_true", help=_("watch the task")) parser.add_option("--arch", help=_("set arch")) (options, args) = parser.parse_args(args) - activate_session(session) + activate_session(session, opts) taskopts = {} for key in ('channel','priority','arch'): @@ -6006,7 +6005,7 @@ def handle_tag_build(opts, session, args): if len(args) < 2: parser.error(_("This command takes at least two arguments: a tag name/ID and one or more package n-v-r's")) assert False # pragma: no cover - activate_session(session) + activate_session(session, opts) tasks = [] for pkg in args[1:]: task_id = session.tagBuild(args[0], pkg, force=options.force) @@ -6035,7 +6034,7 @@ def handle_move_build(opts, session, args): else: parser.error(_("This command takes at least three arguments: two tags and one or more package n-v-r's")) assert False # pragma: no cover - activate_session(session) + activate_session(session, opts) tasks = [] builds = [] @@ -6067,7 +6066,7 @@ def handle_move_build(opts, session, args): return watch_tasks(session, tasks, quiet=opts.quiet, poll_interval=opts.poll_interval) -def handle_untag_build(options, session, args): +def handle_untag_build(goptions, session, args): "[bind] Remove a tag from one or more builds" usage = _("usage: %prog untag-build [options] [...]") usage += _("\n(Specify the --help global option for a list of other help options)") @@ -6085,7 +6084,7 @@ def handle_untag_build(options, session, args): elif len(args) < 2: parser.error(_("This command takes at least two arguments: a tag name/ID and one or more package n-v-r's")) assert False # pragma: no cover - activate_session(session) + activate_session(session, goptions) tag = session.getTag(args[0]) if not tag: parser.error(_("Invalid tag: %s" % args[0])) @@ -6137,7 +6136,7 @@ def handle_untag_build(options, session, args): print(_("untagging %(nvr)s") % binfo) session.untagBuild(tag['name'], binfo['nvr'], force=options.force) -def handle_unblock_pkg(options, session, args): +def handle_unblock_pkg(goptions, session, args): "[admin] Unblock a package in the listing for tag" usage = _("usage: %prog unblock-pkg [options] tag package [package2 ...]") usage += _("\n(Specify the --help global option for a list of other help options)") @@ -6146,7 +6145,7 @@ def handle_unblock_pkg(options, session, args): if len(args) < 2: parser.error(_("Please specify a tag and at least one package")) assert False # pragma: no cover - activate_session(session) + activate_session(session, goptions) tag = args[0] for package in args[1:]: #really should implement multicall... @@ -6177,7 +6176,7 @@ def anon_handle_download_build(options, session, args): parser.error(_("Only a single package N-V-R or build ID may be specified")) assert False # pragma: no cover - activate_session(session) + activate_session(session, options) build = args[0] if build.isdigit(): @@ -6607,7 +6606,7 @@ def handle_regen_repo(options, session, args): else: parser.error(_("Only a single tag name may be specified")) assert False # pragma: no cover - activate_session(session) + activate_session(session, options) tag = args[0] repo_opts = {} if suboptions.target: @@ -6684,7 +6683,7 @@ def handle_dist_repo(options, session, args): if task_opts.allow_missing_signatures and task_opts.skip_missing_signatures: parser.error(_('allow_missing_signatures and skip_missing_signatures ' 'are mutually exclusive')) - activate_session(session) + activate_session(session, options) stuffdir = _unique_path('cli-dist-repo') if task_opts.comps: if not os.path.exists(task_opts.comps): @@ -6796,7 +6795,7 @@ def handle_moshimoshi(options, session, args): if len(args) != 0: parser.error(_("This command takes no arguments")) assert False # pragma: no cover - activate_session(session) + activate_session(session, options) u = session.getLoggedInUser() if not u: print("Not authenticated") From c02eb739aa1c3c4b6b4f7e106f402fe4fdb864d7 Mon Sep 17 00:00:00 2001 From: Tomas Kopecek Date: Jun 13 2017 09:42:04 +0000 Subject: [PATCH 8/19] update cli tests --- diff --git a/Makefile b/Makefile index 2a6ff55..1a82c6c 100644 --- a/Makefile +++ b/Makefile @@ -66,7 +66,7 @@ git-clean: 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 @@ test: 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 diff --git a/koji/plugin.py b/koji/plugin.py index 3bf243e..439e454 100644 --- a/koji/plugin.py +++ b/koji/plugin.py @@ -105,6 +105,15 @@ def export(f): setattr(f, 'exported', True) return f +def export_cli(f): + """a decorator that marks a function as exported for CLI + + intended to be used by plugins + the HandlerRegistry will export the function under its own name + """ + setattr(f, 'exported_cli', True) + return f + def export_as(alias): """returns a decorator that marks a function as exported and gives it an alias diff --git a/plugins/cli/runroot.py b/plugins/cli/runroot.py index d9865da..04c02fb 100644 --- a/plugins/cli/runroot.py +++ b/plugins/cli/runroot.py @@ -1,3 +1,11 @@ +import sys +import time + +import koji +from koji.plugin import export_cli +from koji_cli.lib import _, activate_session, OptionParser, list_task_output_all_volumes + +@export_cli def handle_runroot(options, session, args): "[admin] Run a command in a buildroot" usage = _("usage: %prog runroot [options] ") @@ -22,7 +30,7 @@ def handle_runroot(options, session, args): if len(args) < 3: parser.error(_("Incorrect number of arguments")) - activate_session(session) + activate_session(session, options) tag = args[0] arch = args[1] if opts.use_shell: diff --git a/plugins/cli/save_failed_tree.py b/plugins/cli/save_failed_tree.py index 4a06c8e..708e8b7 100644 --- a/plugins/cli/save_failed_tree.py +++ b/plugins/cli/save_failed_tree.py @@ -1,3 +1,8 @@ +import koji +from koji.plugin import export_cli +from koji_cli.lib import _, activate_session, OptionParser, watch_tasks + +@export_cli def handle_save_failed_tree(options, session, args): "Create tarball with whole buildtree" usage = _("usage: %prog save-failed-tree [options] ID") @@ -26,7 +31,7 @@ def handle_save_failed_tree(options, session, args): except ValueError: parser.error(_("ID must be an integer")) - activate_session(session) + activate_session(session, options) if opts.mode == "buildroot": br_id = id_val @@ -59,4 +64,4 @@ def handle_save_failed_tree(options, session, args): return else: session.logout() - watch_tasks(session, [task_id], quiet=opts.quiet) + watch_tasks(session, [task_id], quiet=opts.quiet, poll_interval=options.poll_interval) diff --git a/tests/test_cli/data/list-commands-admin.txt b/tests/test_cli/data/list-commands-admin.txt index 68114a3..6c2938f 100644 --- a/tests/test_cli/data/list-commands-admin.txt +++ b/tests/test_cli/data/list-commands-admin.txt @@ -51,7 +51,6 @@ admin commands: restart-hosts Restart enabled hosts revoke-cg-access Remove a user from a content generator revoke-permission Revoke a permission from a user - runroot Run a command in a buildroot set-build-volume Move a build to a different volume set-pkg-arches Set the list of extra arches for a package set-pkg-owner Set the owner for a package diff --git a/tests/test_cli/data/list-commands.txt b/tests/test_cli/data/list-commands.txt index 6046042..461c2de 100644 --- a/tests/test_cli/data/list-commands.txt +++ b/tests/test_cli/data/list-commands.txt @@ -51,7 +51,6 @@ admin commands: restart-hosts Restart enabled hosts revoke-cg-access Remove a user from a content generator revoke-permission Revoke a permission from a user - runroot Run a command in a buildroot set-build-volume Move a build to a different volume set-pkg-arches Set the list of extra arches for a package set-pkg-owner Set the owner for a package @@ -121,7 +120,6 @@ miscellaneous commands: dist-repo Create a yum repo with distribution options import-comps Import group/package information from a comps file moshimoshi Introduce yourself - save-failed-tree Create tarball with whole buildtree monitor commands: wait-repo Wait for a repo to be regenerated diff --git a/tests/test_cli/test_add_group.py b/tests/test_cli/test_add_group.py index d2899c7..a04c8c3 100644 --- a/tests/test_cli/test_add_group.py +++ b/tests/test_cli/test_add_group.py @@ -1,18 +1,14 @@ from __future__ import absolute_import -import unittest - -import os - -import sys import mock +import os import six +import sys +import unittest -from . import loadcli - -cli = loadcli.cli +from koji_cli.commands import handle_add_group class TestAddGroup(unittest.TestCase): @@ -20,7 +16,7 @@ class TestAddGroup(unittest.TestCase): maxDiff = None @mock.patch('sys.stdout', new_callable=six.StringIO) - @mock.patch('koji_cli.activate_session') + @mock.patch('koji_cli.commands.activate_session') def test_handle_add_group(self, activate_session_mock, stdout): tag = 'tag' group = 'group' @@ -35,13 +31,13 @@ class TestAddGroup(unittest.TestCase): {'name': 'otherGroup', 'group_id': 'otherGroupId'}] # Run it and check immediate output - rv = cli.handle_add_group(options, session, arguments) + rv = handle_add_group(options, session, arguments) actual = stdout.getvalue() expected = '' self.assertMultiLineEqual(actual, expected) # Finally, assert that things were called as we expected. - activate_session_mock.assert_called_once_with(session) + activate_session_mock.assert_called_once_with(session, options) session.hasPerm.assert_called_once_with('admin') session.getTag.assert_called_once_with(tag) session.getTagGroups.assert_called_once_with(tag, inherit=False) @@ -49,7 +45,7 @@ class TestAddGroup(unittest.TestCase): self.assertNotEqual(rv, 1) @mock.patch('sys.stdout', new_callable=six.StringIO) - @mock.patch('koji_cli.activate_session') + @mock.patch('koji_cli.commands.activate_session') def test_handle_add_group_dupl(self, activate_session_mock, stdout): tag = 'tag' group = 'group' @@ -64,13 +60,13 @@ class TestAddGroup(unittest.TestCase): {'name': 'group', 'group_id': 'groupId'}] # Run it and check immediate output - rv = cli.handle_add_group(options, session, arguments) + rv = handle_add_group(options, session, arguments) actual = stdout.getvalue() expected = 'Group group already exists for tag tag\n' self.assertMultiLineEqual(actual, expected) # Finally, assert that things were called as we expected. - activate_session_mock.assert_called_once_with(session) + activate_session_mock.assert_called_once_with(session, options) session.hasPerm.assert_called_once_with('admin') session.getTag.assert_called_once_with(tag) session.getTagGroups.assert_called_once_with(tag, inherit=False) @@ -79,7 +75,7 @@ class TestAddGroup(unittest.TestCase): @mock.patch('sys.stdout', new_callable=six.StringIO) @mock.patch('sys.stderr', new_callable=six.StringIO) - @mock.patch('koji_cli.activate_session') + @mock.patch('koji_cli.commands.activate_session') def test_handle_add_group_help( self, activate_session_mock, @@ -93,7 +89,7 @@ class TestAddGroup(unittest.TestCase): # Run it and check immediate output with self.assertRaises(SystemExit) as cm: - rv = cli.handle_add_group(options, session, arguments) + rv = handle_add_group(options, session, arguments) actual_stdout = stdout.getvalue() actual_stderr = stderr.getvalue() expected_stdout = '' @@ -115,7 +111,7 @@ class TestAddGroup(unittest.TestCase): self.assertEqual(cm.exception.code, 2) @mock.patch('sys.stdout', new_callable=six.StringIO) - @mock.patch('koji_cli.activate_session') + @mock.patch('koji_cli.commands.activate_session') def test_handle_add_group_no_perm(self, activate_session_mock, stdout): tag = 'tag' group = 'group' @@ -127,13 +123,13 @@ class TestAddGroup(unittest.TestCase): session.hasPerm.return_value = False # Run it and check immediate output - rv = cli.handle_add_group(options, session, arguments) + rv = handle_add_group(options, session, arguments) actual = stdout.getvalue() expected = 'This action requires admin privileges\n' self.assertMultiLineEqual(actual, expected) # Finally, assert that things were called as we expected. - activate_session_mock.assert_called_once_with(session) + activate_session_mock.assert_called_once_with(session, options) session.hasPerm.assert_called_once_with('admin') session.getTag.assert_not_called() session.getTagGroups.assert_not_called() @@ -141,7 +137,7 @@ class TestAddGroup(unittest.TestCase): self.assertEqual(rv, 1) @mock.patch('sys.stdout', new_callable=six.StringIO) - @mock.patch('koji_cli.activate_session') + @mock.patch('koji_cli.commands.activate_session') def test_handle_add_group_no_tag(self, activate_session_mock, stdout): tag = 'tag' group = 'group' @@ -154,13 +150,13 @@ class TestAddGroup(unittest.TestCase): session.getTag.return_value = None # Run it and check immediate output - rv = cli.handle_add_group(options, session, arguments) + rv = handle_add_group(options, session, arguments) actual = stdout.getvalue() expected = 'Unknown tag: tag\n' self.assertMultiLineEqual(actual, expected) # Finally, assert that things were called as we expected. - activate_session_mock.assert_called_once_with(session) + activate_session_mock.assert_called_once_with(session, options) session.hasPerm.assert_called_once_with('admin') session.getTag.assert_called_once_with(tag) session.getTagGroups.assert_not_called() diff --git a/tests/test_cli/test_add_host.py b/tests/test_cli/test_add_host.py index 026c4fa..331fb3f 100644 --- a/tests/test_cli/test_add_host.py +++ b/tests/test_cli/test_add_host.py @@ -1,15 +1,11 @@ from __future__ import absolute_import -import unittest - -import os -import sys import mock +import os import six +import sys +import unittest -from . import loadcli - -cli = loadcli.cli - +from koji_cli.commands import handle_add_host class TestAddHost(unittest.TestCase): @@ -17,7 +13,7 @@ class TestAddHost(unittest.TestCase): maxDiff = None @mock.patch('sys.stdout', new_callable=six.StringIO) - @mock.patch('koji_cli.activate_session') + @mock.patch('koji_cli.commands.activate_session') def test_handle_add_host(self, activate_session_mock, stdout): host = 'host' host_id = 1 @@ -36,18 +32,18 @@ class TestAddHost(unittest.TestCase): # Run it and check immediate output # args: host, arch1, arch2, --krb-principal=krb # expected: success - rv = cli.handle_add_host(options, session, arguments) + rv = handle_add_host(options, session, arguments) actual = stdout.getvalue() expected = 'host added: id 1\n' self.assertMultiLineEqual(actual, expected) # Finally, assert that things were called as we expected. - activate_session_mock.assert_called_once_with(session) + activate_session_mock.assert_called_once_with(session, options) session.getHost.assert_called_once_with(host) session.addHost.assert_called_once_with(host, arches, **kwargs) self.assertNotEqual(rv, 1) @mock.patch('sys.stdout', new_callable=six.StringIO) - @mock.patch('koji_cli.activate_session') + @mock.patch('koji_cli.commands.activate_session') def test_handle_add_host_no_krb_principal( self, activate_session_mock, stdout): host = 'host' @@ -63,18 +59,18 @@ class TestAddHost(unittest.TestCase): # Run it and check immediate output # args: host, arch1, arch2 # expected: success - rv = cli.handle_add_host(options, session, arguments) + rv = handle_add_host(options, session, arguments) actual = stdout.getvalue() expected = 'host added: id 1\n' self.assertMultiLineEqual(actual, expected) # Finally, assert that things were called as we expected. - activate_session_mock.assert_called_once_with(session) + activate_session_mock.assert_called_once_with(session, options) session.getHost.assert_called_once_with(host) session.addHost.assert_called_once_with(host, arches) self.assertNotEqual(rv, 1) @mock.patch('sys.stdout', new_callable=six.StringIO) - @mock.patch('koji_cli.activate_session') + @mock.patch('koji_cli.commands.activate_session') def test_handle_add_host_dupl(self, activate_session_mock, stdout): host = 'host' host_id = 1 @@ -90,19 +86,19 @@ class TestAddHost(unittest.TestCase): # Run it and check immediate output # args: host, arch1, arch2, --krb-principal=krb # expected: failed, host already exists - rv = cli.handle_add_host(options, session, arguments) + rv = handle_add_host(options, session, arguments) actual = stdout.getvalue() expected = 'host is already in the database\n' self.assertMultiLineEqual(actual, expected) # Finally, assert that things were called as we expected. - activate_session_mock.assert_called_once_with(session) + activate_session_mock.assert_called_once_with(session, options) session.getHost.assert_called_once_with(host) session.addHost.assert_not_called() self.assertEqual(rv, 1) @mock.patch('sys.stdout', new_callable=six.StringIO) @mock.patch('sys.stderr', new_callable=six.StringIO) - @mock.patch('koji_cli.activate_session') + @mock.patch('koji_cli.commands.activate_session') def test_handle_add_host_help(self, activate_session_mock, stderr, stdout): arguments = [] options = mock.MagicMock() @@ -113,7 +109,7 @@ class TestAddHost(unittest.TestCase): # Run it and check immediate output with self.assertRaises(SystemExit) as cm: - cli.handle_add_host(options, session, arguments) + handle_add_host(options, session, arguments) actual_stdout = stdout.getvalue() actual_stderr = stderr.getvalue() expected_stdout = '' @@ -132,7 +128,7 @@ class TestAddHost(unittest.TestCase): self.assertEqual(cm.exception.code, 2) @mock.patch('sys.stdout', new_callable=six.StringIO) - @mock.patch('koji_cli.activate_session') + @mock.patch('koji_cli.commands.activate_session') def test_handle_add_host_failed(self, activate_session_mock, stdout): host = 'host' arches = ['arch1', 'arch2'] @@ -150,12 +146,12 @@ class TestAddHost(unittest.TestCase): # Run it and check immediate output # args: host, arch1, arch2, --krb-principal=krb # expected: failed - cli.handle_add_host(options, session, arguments) + handle_add_host(options, session, arguments) actual = stdout.getvalue() expected = '' self.assertMultiLineEqual(actual, expected) # Finally, assert that things were called as we expected. - activate_session_mock.assert_called_once_with(session) + activate_session_mock.assert_called_once_with(session, options) session.getHost.assert_called_once_with(host) session.addHost.assert_called_once_with(host, arches, **kwargs) diff --git a/tests/test_cli/test_add_host_to_channel.py b/tests/test_cli/test_add_host_to_channel.py index 5daabe1..67ba327 100644 --- a/tests/test_cli/test_add_host_to_channel.py +++ b/tests/test_cli/test_add_host_to_channel.py @@ -1,15 +1,12 @@ from __future__ import absolute_import -import unittest -import os -import sys import mock +import os import six +import sys +import unittest -from . import loadcli - -cli = loadcli.cli - +from koji_cli.commands import handle_add_host_to_channel class TestAddHostToChannel(unittest.TestCase): @@ -17,7 +14,7 @@ class TestAddHostToChannel(unittest.TestCase): maxDiff = None @mock.patch('sys.stdout', new_callable=six.StringIO) - @mock.patch('koji_cli.activate_session') + @mock.patch('koji_cli.commands.activate_session') def test_handle_add_host_to_channel(self, activate_session_mock, stdout): host = 'host' host_info = mock.ANY @@ -34,19 +31,19 @@ class TestAddHostToChannel(unittest.TestCase): # Run it and check immediate output # args: host, channel # expected: success - rv = cli.handle_add_host_to_channel(options, session, args) + rv = handle_add_host_to_channel(options, session, args) actual = stdout.getvalue() expected = '' self.assertMultiLineEqual(actual, expected) # Finally, assert that things were called as we expected. - activate_session_mock.assert_called_once_with(session) + activate_session_mock.assert_called_once_with(session, options) session.getChannel.assert_called_once_with(channel) session.getHost.assert_called_once_with(host) session.addHostToChannel.assert_called_once_with(host, channel) self.assertNotEqual(rv, 1) @mock.patch('sys.stdout', new_callable=six.StringIO) - @mock.patch('koji_cli.activate_session') + @mock.patch('koji_cli.commands.activate_session') def test_handle_add_host_to_channel_list( self, activate_session_mock, stdout): list_arg = '--list' @@ -61,12 +58,12 @@ class TestAddHostToChannel(unittest.TestCase): # Run it and check immediate output # args: --list # expected: list all channel names - rv = cli.handle_add_host_to_channel(options, session, args) + rv = handle_add_host_to_channel(options, session, args) actual = stdout.getvalue() expected = 'channel1\nchannel2\n' self.assertMultiLineEqual(actual, expected) # Finally, assert that things were called as we expected. - activate_session_mock.assert_called_once_with(session) + activate_session_mock.assert_called_once_with(session, options) session.listChannels.assert_called_once() session.getChannel.assert_not_called() session.getHost.assert_not_called() @@ -74,7 +71,7 @@ class TestAddHostToChannel(unittest.TestCase): self.assertNotEqual(rv, 1) @mock.patch('sys.stdout', new_callable=six.StringIO) - @mock.patch('koji_cli.activate_session') + @mock.patch('koji_cli.commands.activate_session') def test_handle_add_host_to_channel_new( self, activate_session_mock, stdout): host = 'host' @@ -92,12 +89,12 @@ class TestAddHostToChannel(unittest.TestCase): # Run it and check immediate output # args: host, channel, --new # expected: success - rv = cli.handle_add_host_to_channel(options, session, args) + rv = handle_add_host_to_channel(options, session, args) actual = stdout.getvalue() expected = '' self.assertMultiLineEqual(actual, expected) # Finally, assert that things were called as we expected. - activate_session_mock.assert_called_once_with(session) + activate_session_mock.assert_called_once_with(session, options) session.getChannel.assert_not_called() session.getHost.assert_called_once_with(host) session.addHostToChannel.assert_called_once_with( @@ -105,7 +102,7 @@ class TestAddHostToChannel(unittest.TestCase): self.assertNotEqual(rv, 1) @mock.patch('sys.stdout', new_callable=six.StringIO) - @mock.patch('koji_cli.activate_session') + @mock.patch('koji_cli.commands.activate_session') def test_handle_add_host_to_channel_no_channel( self, activate_session_mock, stdout): host = 'host' @@ -121,19 +118,19 @@ class TestAddHostToChannel(unittest.TestCase): # Run it and check immediate output # args: host, channel # expected: failed, channel not found - rv = cli.handle_add_host_to_channel(options, session, args) + rv = handle_add_host_to_channel(options, session, args) actual = stdout.getvalue() expected = 'No such channel: channel\n' self.assertMultiLineEqual(actual, expected) # Finally, assert that things were called as we expected. - activate_session_mock.assert_called_once_with(session) + activate_session_mock.assert_called_once_with(session, options) session.getChannel.assert_called_once_with(channel) session.getHost.assert_not_called() session.addHostToChannel.assert_not_called() self.assertEqual(rv, 1) @mock.patch('sys.stdout', new_callable=six.StringIO) - @mock.patch('koji_cli.activate_session') + @mock.patch('koji_cli.commands.activate_session') def test_handle_add_host_to_channel_no_host( self, activate_session_mock, stdout): host = 'host' @@ -151,12 +148,12 @@ class TestAddHostToChannel(unittest.TestCase): # Run it and check immediate output # args: host, channel # expected: success - rv = cli.handle_add_host_to_channel(options, session, args) + rv = handle_add_host_to_channel(options, session, args) actual = stdout.getvalue() expected = 'No such host: host\n' self.assertMultiLineEqual(actual, expected) # Finally, assert that things were called as we expected. - activate_session_mock.assert_called_once_with(session) + activate_session_mock.assert_called_once_with(session, options) session.getChannel.assert_called_once_with(channel) session.getHost.assert_called_once_with(host) session.addHostToChannel.assert_not_called() @@ -164,7 +161,7 @@ class TestAddHostToChannel(unittest.TestCase): @mock.patch('sys.stdout', new_callable=six.StringIO) @mock.patch('sys.stderr', new_callable=six.StringIO) - @mock.patch('koji_cli.activate_session') + @mock.patch('koji_cli.commands.activate_session') def test_handle_add_host_to_channel_help( self, activate_session_mock, stderr, stdout): args = [] @@ -178,7 +175,7 @@ class TestAddHostToChannel(unittest.TestCase): # args: _empty_ # expected: failed, help msg shows with self.assertRaises(SystemExit) as cm: - cli.handle_add_host_to_channel(options, session, args) + handle_add_host_to_channel(options, session, args) actual_stdout = stdout.getvalue() actual_stderr = stderr.getvalue() expected_stdout = '' diff --git a/tests/test_cli/test_add_pkg.py b/tests/test_cli/test_add_pkg.py index 93965c8..8c807a6 100644 --- a/tests/test_cli/test_add_pkg.py +++ b/tests/test_cli/test_add_pkg.py @@ -1,16 +1,14 @@ from __future__ import absolute_import -import unittest -import os -import sys import mock +import os import six +import sys +import unittest from mock import call -from . import loadcli - -cli = loadcli.cli +from koji_cli.commands import handle_add_pkg class TestAddPkg(unittest.TestCase): @@ -19,7 +17,7 @@ class TestAddPkg(unittest.TestCase): maxDiff = None @mock.patch('sys.stdout', new_callable=six.StringIO) - @mock.patch('koji_cli.activate_session') + @mock.patch('koji_cli.commands.activate_session') def test_handle_add_pkg(self, activate_session_mock, stdout): tag = 'tag' dsttag = {'name': tag, 'id': 1} @@ -49,12 +47,12 @@ class TestAddPkg(unittest.TestCase): # Run it and check immediate output # args: --owner, --extra-arches='arch1,arch2 arch3, arch4', tag, package # expected: success - rv = cli.handle_add_pkg(options, session, args) + rv = handle_add_pkg(options, session, args) actual = stdout.getvalue() expected = 'Adding 1 packages to tag tag\n' self.assertMultiLineEqual(actual, expected) # Finally, assert that things were called as we expected. - activate_session_mock.assert_called_once_with(session) + activate_session_mock.assert_called_once_with(session, options) session.getUser.assert_called_once_with(owner) session.getTag.assert_called_once_with(tag) session.listPackages.assert_called_once_with(tagID=dsttag['id']) @@ -64,7 +62,7 @@ class TestAddPkg(unittest.TestCase): self.assertNotEqual(rv, 1) @mock.patch('sys.stdout', new_callable=six.StringIO) - @mock.patch('koji_cli.activate_session') + @mock.patch('koji_cli.commands.activate_session') def test_handle_add_pkg_multi_pkg(self, activate_session_mock, stdout): tag = 'tag' dsttag = {'name': tag, 'id': 1} @@ -92,12 +90,12 @@ class TestAddPkg(unittest.TestCase): # args: --owner, --extra-arches='arch1,arch2 arch3, arch4', # tag, package1, package2, package3 # expected: success - rv = cli.handle_add_pkg(options, session, args) + rv = handle_add_pkg(options, session, args) actual = stdout.getvalue() expected = 'Package package2 already exists in tag tag\nAdding 2 packages to tag tag\n' self.assertMultiLineEqual(actual, expected) # Finally, assert that things were called as we expected. - activate_session_mock.assert_called_once_with(session) + activate_session_mock.assert_called_once_with(session, options) self.assertEqual(session.mock_calls, [call.getUser(owner), call.getTag(tag), @@ -108,7 +106,7 @@ class TestAddPkg(unittest.TestCase): self.assertNotEqual(rv, 1) @mock.patch('sys.stdout', new_callable=six.StringIO) - @mock.patch('koji_cli.activate_session') + @mock.patch('koji_cli.commands.activate_session') def test_handle_add_pkg_owner_no_exists( self, activate_session_mock, stdout): tag = 'tag' @@ -130,7 +128,7 @@ class TestAddPkg(unittest.TestCase): # args: --owner, --extra-arches='arch1,arch2 arch3, arch4', # tag, package1, package2, package3 # expected: failed: owner does not exist - rv = cli.handle_add_pkg(options, session, args) + rv = handle_add_pkg(options, session, args) actual = stdout.getvalue() expected = 'User owner does not exist\n' self.assertMultiLineEqual(actual, expected) @@ -141,7 +139,7 @@ class TestAddPkg(unittest.TestCase): self.assertEqual(rv, 1) @mock.patch('sys.stdout', new_callable=six.StringIO) - @mock.patch('koji_cli.activate_session') + @mock.patch('koji_cli.commands.activate_session') def test_handle_add_pkg_tag_no_exists(self, activate_session_mock, stdout): tag = 'tag' dsttag = None @@ -165,12 +163,12 @@ class TestAddPkg(unittest.TestCase): # tag, package1, package2, package3 # expected: failed: tag does not exist with self.assertRaises(SystemExit) as cm: - cli.handle_add_pkg(options, session, args) + handle_add_pkg(options, session, args) actual = stdout.getvalue() expected = 'No such tag: tag\n' self.assertMultiLineEqual(actual, expected) # Finally, assert that things were called as we expected. - activate_session_mock.assert_called_once_with(session) + activate_session_mock.assert_called_once_with(session, options) self.assertEqual(session.mock_calls, [call.getUser(owner), call.getTag(tag)]) @@ -178,7 +176,7 @@ class TestAddPkg(unittest.TestCase): @mock.patch('sys.stdout', new_callable=six.StringIO) @mock.patch('sys.stderr', new_callable=six.StringIO) - @mock.patch('koji_cli.activate_session') + @mock.patch('koji_cli.commands.activate_session') def test_handle_add_pkg_no_owner( self, activate_session_mock, stderr, stdout): tag = 'tag' @@ -194,7 +192,7 @@ class TestAddPkg(unittest.TestCase): # Run it and check immediate output with self.assertRaises(SystemExit) as cm: - cli.handle_add_pkg(options, session, args) + handle_add_pkg(options, session, args) actual_stdout = stdout.getvalue() actual_stderr = stderr.getvalue() expected_stdout = '' @@ -216,7 +214,7 @@ class TestAddPkg(unittest.TestCase): @mock.patch('sys.stdout', new_callable=six.StringIO) @mock.patch('sys.stderr', new_callable=six.StringIO) - @mock.patch('koji_cli.activate_session') + @mock.patch('koji_cli.commands.activate_session') def test_handle_add_pkg_no_arg( self, activate_session_mock, stderr, stdout): args = [] @@ -228,7 +226,7 @@ class TestAddPkg(unittest.TestCase): # Run it and check immediate output with self.assertRaises(SystemExit) as cm: - cli.handle_add_pkg(options, session, args) + handle_add_pkg(options, session, args) actual_stdout = stdout.getvalue() actual_stderr = stderr.getvalue() expected_stdout = '' diff --git a/tests/test_cli/test_block_pkg.py b/tests/test_cli/test_block_pkg.py index 7b00615..914cda3 100644 --- a/tests/test_cli/test_block_pkg.py +++ b/tests/test_cli/test_block_pkg.py @@ -1,16 +1,13 @@ from __future__ import absolute_import -import unittest - -import os -import sys import mock +import os import six +import sys +import unittest from mock import call -from . import loadcli - -cli = loadcli.cli +from koji_cli.commands import handle_block_pkg class TestBlockPkg(unittest.TestCase): @@ -19,7 +16,7 @@ class TestBlockPkg(unittest.TestCase): maxDiff = None @mock.patch('sys.stdout', new_callable=six.StringIO) - @mock.patch('koji_cli.activate_session') + @mock.patch('koji_cli.commands.activate_session') def test_handle_block_pkg(self, activate_session_mock, stdout): tag = 'tag' dsttag = {'name': tag, 'id': 1} @@ -36,12 +33,12 @@ class TestBlockPkg(unittest.TestCase): # Run it and check immediate output # args: tag, package # expected: success - rv = cli.handle_block_pkg(options, session, args) + rv = handle_block_pkg(options, session, args) actual = stdout.getvalue() expected = '' self.assertMultiLineEqual(actual, expected) # Finally, assert that things were called as we expected. - activate_session_mock.assert_called_once_with(session) + activate_session_mock.assert_called_once_with(session, options) session.getTag.assert_called_once_with(tag) session.listPackages.assert_called_once_with( tagID=dsttag['id'], inherited=True) @@ -51,7 +48,7 @@ class TestBlockPkg(unittest.TestCase): self.assertNotEqual(rv, 1) @mock.patch('sys.stdout', new_callable=six.StringIO) - @mock.patch('koji_cli.activate_session') + @mock.patch('koji_cli.commands.activate_session') def test_handle_block_pkg_multi_pkg(self, activate_session_mock, stdout): tag = 'tag' dsttag = {'name': tag, 'id': 1} @@ -72,12 +69,12 @@ class TestBlockPkg(unittest.TestCase): # Run it and check immediate output # args: tag, package1, package2, package3 # expected: success - rv = cli.handle_block_pkg(options, session, args) + rv = handle_block_pkg(options, session, args) actual = stdout.getvalue() expected = '' self.assertMultiLineEqual(actual, expected) # Finally, assert that things were called as we expected. - activate_session_mock.assert_called_once_with(session) + activate_session_mock.assert_called_once_with(session, options) self.assertEqual( session.mock_calls, [ call.getTag(tag), call.listPackages( @@ -89,7 +86,7 @@ class TestBlockPkg(unittest.TestCase): self.assertNotEqual(rv, 1) @mock.patch('sys.stdout', new_callable=six.StringIO) - @mock.patch('koji_cli.activate_session') + @mock.patch('koji_cli.commands.activate_session') def test_handle_block_pkg_no_package(self, activate_session_mock, stdout): tag = 'tag' dsttag = {'name': tag, 'id': 1} @@ -108,12 +105,12 @@ class TestBlockPkg(unittest.TestCase): # Run it and check immediate output # args: tag, package1, package2, package3 # expected: failed: can not find package2 under tag - rv = cli.handle_block_pkg(options, session, args) + rv = handle_block_pkg(options, session, args) actual = stdout.getvalue() expected = 'Package package2 doesn\'t exist in tag tag\n' self.assertMultiLineEqual(actual, expected) # Finally, assert that things were called as we expected. - activate_session_mock.assert_called_once_with(session) + activate_session_mock.assert_called_once_with(session, options) session.getTag.assert_called_once_with(tag) session.listPackages.assert_called_once_with( tagID=dsttag['id'], inherited=True) @@ -122,7 +119,7 @@ class TestBlockPkg(unittest.TestCase): self.assertEqual(rv, 1) @mock.patch('sys.stdout', new_callable=six.StringIO) - @mock.patch('koji_cli.activate_session') + @mock.patch('koji_cli.commands.activate_session') def test_handle_block_pkg_tag_no_exists( self, activate_session_mock, stdout): tag = 'tag' @@ -138,12 +135,12 @@ class TestBlockPkg(unittest.TestCase): # Run it and check immediate output # args: tag, package1, package2, package3 # expected: failed: tag does not exist - rv = cli.handle_block_pkg(options, session, args) + rv = handle_block_pkg(options, session, args) actual = stdout.getvalue() expected = 'No such tag: tag\n' self.assertMultiLineEqual(actual, expected) # Finally, assert that things were called as we expected. - activate_session_mock.assert_called_once_with(session) + activate_session_mock.assert_called_once_with(session, options) session.getTag.assert_called_once_with(tag) session.listPackages.assert_not_called() session.packageListBlock.assert_not_called() @@ -151,7 +148,7 @@ class TestBlockPkg(unittest.TestCase): @mock.patch('sys.stdout', new_callable=six.StringIO) @mock.patch('sys.stderr', new_callable=six.StringIO) - @mock.patch('koji_cli.activate_session') + @mock.patch('koji_cli.commands.activate_session') def test_handle_block_pkg_help( self, activate_session_mock, stderr, stdout): args = [] @@ -164,7 +161,7 @@ class TestBlockPkg(unittest.TestCase): # Run it and check immediate output with self.assertRaises(SystemExit) as cm: - cli.handle_block_pkg(options, session, args) + handle_block_pkg(options, session, args) actual_stdout = stdout.getvalue() actual_stderr = stderr.getvalue() expected_stdout = '' diff --git a/tests/test_cli/test_build.py b/tests/test_cli/test_build.py index 2a10f44..1eff2e6 100644 --- a/tests/test_cli/test_build.py +++ b/tests/test_cli/test_build.py @@ -1,15 +1,11 @@ from __future__ import absolute_import -import unittest - -import os -import sys import mock +import os import six +import sys +import unittest -from . import loadcli - -cli = loadcli.cli - +from koji_cli.commands import handle_build, _progress_callback class TestBuild(unittest.TestCase): # Show long diffs in error output... @@ -20,14 +16,15 @@ class TestBuild(unittest.TestCase): self.options = mock.MagicMock() self.options.quiet = None self.options.weburl = 'weburl' + self.options.poll_interval = 0 # Mock out the xmlrpc server self.session = mock.MagicMock() @mock.patch('sys.stdout', new_callable=six.StringIO) - @mock.patch('koji_cli.activate_session') - @mock.patch('koji_cli._unique_path', return_value='random_path') - @mock.patch('koji_cli._running_in_bg', return_value=False) - @mock.patch('koji_cli.watch_tasks', return_value=0) + @mock.patch('koji_cli.commands.activate_session') + @mock.patch('koji_cli.commands._unique_path', return_value='random_path') + @mock.patch('koji_cli.commands._running_in_bg', return_value=False) + @mock.patch('koji_cli.commands.watch_tasks', return_value=0) def test_handle_build_from_srpm( self, watch_tasks_mock, @@ -51,7 +48,7 @@ class TestBuild(unittest.TestCase): # Run it and check immediate output # args: target, srpm # expected: success - rv = cli.handle_build(self.options, self.session, args) + rv = handle_build(self.options, self.session, args) actual = stdout.getvalue() expected = """Uploading srpm: srpm @@ -60,25 +57,26 @@ Task info: weburl/taskinfo?taskID=1 """ self.assertMultiLineEqual(actual, expected) # Finally, assert that things were called as we expected. - activate_session_mock.assert_called_once_with(self.session) + activate_session_mock.assert_called_once_with(self.session, self.options) self.session.getBuildTarget.assert_called_once_with(target) self.session.getTag.assert_called_once_with(dest_tag) unique_path_mock.assert_called_once_with('cli-build') self.assertEqual(running_in_bg_mock.call_count, 2) self.session.uploadWrapper.assert_called_once_with( - source, 'random_path', callback=cli._progress_callback) + source, 'random_path', callback=_progress_callback) self.session.build.assert_called_once_with( 'random_path/' + source, target, opts, priority=priority) self.session.logout.assert_called() watch_tasks_mock.assert_called_once_with( - self.session, [task_id], quiet=self.options.quiet) + self.session, [task_id], quiet=self.options.quiet, + poll_interval=self.options.poll_interval) self.assertEqual(rv, 0) @mock.patch('sys.stdout', new_callable=six.StringIO) - @mock.patch('koji_cli.activate_session') - @mock.patch('koji_cli._unique_path', return_value='random_path') - @mock.patch('koji_cli._running_in_bg', return_value=False) - @mock.patch('koji_cli.watch_tasks', return_value=0) + @mock.patch('koji_cli.commands.activate_session') + @mock.patch('koji_cli.commands._unique_path', return_value='random_path') + @mock.patch('koji_cli.commands._running_in_bg', return_value=False) + @mock.patch('koji_cli.commands.watch_tasks', return_value=0) def test_handle_build_from_scm( self, watch_tasks_mock, @@ -102,14 +100,14 @@ Task info: weburl/taskinfo?taskID=1 # Run it and check immediate output # args: target, http://scm # expected: success - rv = cli.handle_build(self.options, self.session, args) + rv = handle_build(self.options, self.session, args) actual = stdout.getvalue() expected = """Created task: 1 Task info: weburl/taskinfo?taskID=1 """ self.assertMultiLineEqual(actual, expected) # Finally, assert that things were called as we expected. - activate_session_mock.assert_called_once_with(self.session) + activate_session_mock.assert_called_once_with(self.session, self.options) self.session.getBuildTarget.assert_called_once_with(target) self.session.getTag.assert_called_once_with(dest_tag) unique_path_mock.assert_not_called() @@ -119,15 +117,16 @@ Task info: weburl/taskinfo?taskID=1 source, target, opts, priority=priority) self.session.logout.assert_called() watch_tasks_mock.assert_called_once_with( - self.session, [task_id], quiet=self.options.quiet) + self.session, [task_id], quiet=self.options.quiet, + poll_interval=self.options.poll_interval) self.assertEqual(rv, 0) @mock.patch('sys.stdout', new_callable=six.StringIO) @mock.patch('sys.stderr', new_callable=six.StringIO) - @mock.patch('koji_cli.activate_session') - @mock.patch('koji_cli._unique_path', return_value='random_path') - @mock.patch('koji_cli._running_in_bg', return_value=False) - @mock.patch('koji_cli.watch_tasks', return_value=0) + @mock.patch('koji_cli.commands.activate_session') + @mock.patch('koji_cli.commands._unique_path', return_value='random_path') + @mock.patch('koji_cli.commands._running_in_bg', return_value=False) + @mock.patch('koji_cli.commands.watch_tasks', return_value=0) def test_handle_build_no_arg( self, watch_tasks_mock, @@ -141,7 +140,7 @@ Task info: weburl/taskinfo?taskID=1 # Run it and check immediate output with self.assertRaises(SystemExit) as cm: - cli.handle_build(self.options, self.session, args) + handle_build(self.options, self.session, args) actual_stdout = stdout.getvalue() actual_stderr = stderr.getvalue() expected_stdout = '' @@ -167,10 +166,10 @@ Task info: weburl/taskinfo?taskID=1 @mock.patch('sys.stdout', new_callable=six.StringIO) @mock.patch('sys.stderr', new_callable=six.StringIO) - @mock.patch('koji_cli.activate_session') - @mock.patch('koji_cli._unique_path', return_value='random_path') - @mock.patch('koji_cli._running_in_bg', return_value=False) - @mock.patch('koji_cli.watch_tasks', return_value=0) + @mock.patch('koji_cli.commands.activate_session') + @mock.patch('koji_cli.commands._unique_path', return_value='random_path') + @mock.patch('koji_cli.commands._running_in_bg', return_value=False) + @mock.patch('koji_cli.commands.watch_tasks', return_value=0) def test_handle_build_help( self, watch_tasks_mock, @@ -184,7 +183,7 @@ Task info: weburl/taskinfo?taskID=1 # Run it and check immediate output with self.assertRaises(SystemExit) as cm: - cli.handle_build(self.options, self.session, args) + handle_build(self.options, self.session, args) actual_stdout = stdout.getvalue() actual_stderr = stderr.getvalue() expected_stdout = """Usage: %s build [options] target @@ -221,10 +220,10 @@ Options: @mock.patch('sys.stdout', new_callable=six.StringIO) @mock.patch('sys.stderr', new_callable=six.StringIO) - @mock.patch('koji_cli.activate_session') - @mock.patch('koji_cli._unique_path', return_value='random_path') - @mock.patch('koji_cli._running_in_bg', return_value=False) - @mock.patch('koji_cli.watch_tasks', return_value=0) + @mock.patch('koji_cli.commands.activate_session') + @mock.patch('koji_cli.commands._unique_path', return_value='random_path') + @mock.patch('koji_cli.commands._running_in_bg', return_value=False) + @mock.patch('koji_cli.commands.watch_tasks', return_value=0) def test_handle_build_arch_override_denied( self, watch_tasks_mock, @@ -241,7 +240,7 @@ Options: # Run it and check immediate output with self.assertRaises(SystemExit) as cm: - cli.handle_build(self.options, self.session, args) + handle_build(self.options, self.session, args) actual_stdout = stdout.getvalue() actual_stderr = stderr.getvalue() expected_stdout = '' @@ -266,10 +265,10 @@ Options: self.assertEqual(cm.exception.code, 2) @mock.patch('sys.stdout', new_callable=six.StringIO) - @mock.patch('koji_cli.activate_session') - @mock.patch('koji_cli._unique_path', return_value='random_path') - @mock.patch('koji_cli._running_in_bg', return_value=False) - @mock.patch('koji_cli.watch_tasks', return_value=0) + @mock.patch('koji_cli.commands.activate_session') + @mock.patch('koji_cli.commands._unique_path', return_value='random_path') + @mock.patch('koji_cli.commands._running_in_bg', return_value=False) + @mock.patch('koji_cli.commands.watch_tasks', return_value=0) def test_handle_build_none_tag( self, watch_tasks_mock, @@ -289,14 +288,14 @@ Options: # Run it and check immediate output # args: --repo-id=2, nOne, http://scm # expected: success - rv = cli.handle_build(self.options, self.session, args) + rv = handle_build(self.options, self.session, args) actual = stdout.getvalue() expected = """Created task: 1 Task info: weburl/taskinfo?taskID=1 """ self.assertMultiLineEqual(actual, expected) # Finally, assert that things were called as we expected. - activate_session_mock.assert_called_once_with(self.session) + activate_session_mock.assert_called_once_with(self.session, self.options) self.session.getBuildTarget.assert_not_called() self.session.getTag.assert_not_called() unique_path_mock.assert_not_called() @@ -307,14 +306,15 @@ Task info: weburl/taskinfo?taskID=1 source, None, opts, priority=priority) self.session.logout.assert_called() watch_tasks_mock.assert_called_once_with( - self.session, [task_id], quiet=self.options.quiet) + self.session, [task_id], quiet=self.options.quiet, + poll_interval=self.options.poll_interval) self.assertEqual(rv, 0) @mock.patch('sys.stderr', new_callable=six.StringIO) - @mock.patch('koji_cli.activate_session') - @mock.patch('koji_cli._unique_path', return_value='random_path') - @mock.patch('koji_cli._running_in_bg', return_value=False) - @mock.patch('koji_cli.watch_tasks', return_value=0) + @mock.patch('koji_cli.commands.activate_session') + @mock.patch('koji_cli.commands._unique_path', return_value='random_path') + @mock.patch('koji_cli.commands._running_in_bg', return_value=False) + @mock.patch('koji_cli.commands.watch_tasks', return_value=0) def test_handle_build_target_not_found( self, watch_tasks_mock, @@ -334,7 +334,7 @@ Task info: weburl/taskinfo?taskID=1 # args: target, http://scm # expected: failed, target not found with self.assertRaises(SystemExit) as cm: - cli.handle_build(self.options, self.session, args) + handle_build(self.options, self.session, args) actual = stderr.getvalue() expected = """Usage: %s build [options] target (Specify the --help global option for a list of other help options) @@ -343,7 +343,7 @@ Task info: weburl/taskinfo?taskID=1 """ % (progname, progname) self.assertMultiLineEqual(actual, expected) # Finally, assert that things were called as we expected. - activate_session_mock.assert_called_once_with(self.session) + activate_session_mock.assert_called_once_with(self.session, self.options) self.session.getBuildTarget.assert_called_once_with(target) self.session.getTag.assert_not_called() unique_path_mock.assert_not_called() @@ -355,10 +355,10 @@ Task info: weburl/taskinfo?taskID=1 self.assertEqual(cm.exception.code, 2) @mock.patch('sys.stderr', new_callable=six.StringIO) - @mock.patch('koji_cli.activate_session') - @mock.patch('koji_cli._unique_path', return_value='random_path') - @mock.patch('koji_cli._running_in_bg', return_value=False) - @mock.patch('koji_cli.watch_tasks', return_value=0) + @mock.patch('koji_cli.commands.activate_session') + @mock.patch('koji_cli.commands._unique_path', return_value='random_path') + @mock.patch('koji_cli.commands._running_in_bg', return_value=False) + @mock.patch('koji_cli.commands.watch_tasks', return_value=0) def test_handle_build_dest_tag_not_found( self, watch_tasks_mock, @@ -382,7 +382,7 @@ Task info: weburl/taskinfo?taskID=1 # args: target, http://scm # expected: failed, dest_tag not found with self.assertRaises(SystemExit) as cm: - cli.handle_build(self.options, self.session, args) + handle_build(self.options, self.session, args) actual = stderr.getvalue() expected = """Usage: %s build [options] target (Specify the --help global option for a list of other help options) @@ -391,7 +391,7 @@ Task info: weburl/taskinfo?taskID=1 """ % (progname, progname) self.assertMultiLineEqual(actual, expected) # Finally, assert that things were called as we expected. - activate_session_mock.assert_called_once_with(self.session) + activate_session_mock.assert_called_once_with(self.session, self.options) self.session.getBuildTarget.assert_called_once_with(target) self.session.getTag.assert_called_once_with(dest_tag) unique_path_mock.assert_not_called() @@ -403,10 +403,10 @@ Task info: weburl/taskinfo?taskID=1 self.assertEqual(cm.exception.code, 2) @mock.patch('sys.stderr', new_callable=six.StringIO) - @mock.patch('koji_cli.activate_session') - @mock.patch('koji_cli._unique_path', return_value='random_path') - @mock.patch('koji_cli._running_in_bg', return_value=False) - @mock.patch('koji_cli.watch_tasks', return_value=0) + @mock.patch('koji_cli.commands.activate_session') + @mock.patch('koji_cli.commands._unique_path', return_value='random_path') + @mock.patch('koji_cli.commands._running_in_bg', return_value=False) + @mock.patch('koji_cli.commands.watch_tasks', return_value=0) def test_handle_build_dest_tag_locked( self, watch_tasks_mock, @@ -430,7 +430,7 @@ Task info: weburl/taskinfo?taskID=1 # args: target, http://scm # expected: failed, dest_tag is locked with self.assertRaises(SystemExit) as cm: - cli.handle_build(self.options, self.session, args) + handle_build(self.options, self.session, args) actual = stderr.getvalue() expected = """Usage: %s build [options] target (Specify the --help global option for a list of other help options) @@ -439,7 +439,7 @@ Task info: weburl/taskinfo?taskID=1 """ % (progname, progname) self.assertMultiLineEqual(actual, expected) # Finally, assert that things were called as we expected. - activate_session_mock.assert_called_once_with(self.session) + activate_session_mock.assert_called_once_with(self.session, self.options) self.session.getBuildTarget.assert_called_once_with(target) self.session.getTag.assert_called_once_with(dest_tag) unique_path_mock.assert_not_called() @@ -451,10 +451,10 @@ Task info: weburl/taskinfo?taskID=1 self.assertEqual(cm.exception.code, 2) @mock.patch('sys.stdout', new_callable=six.StringIO) - @mock.patch('koji_cli.activate_session') - @mock.patch('koji_cli._unique_path', return_value='random_path') - @mock.patch('koji_cli._running_in_bg', return_value=False) - @mock.patch('koji_cli.watch_tasks', return_value=0) + @mock.patch('koji_cli.commands.activate_session') + @mock.patch('koji_cli.commands._unique_path', return_value='random_path') + @mock.patch('koji_cli.commands._running_in_bg', return_value=False) + @mock.patch('koji_cli.commands.watch_tasks', return_value=0) def test_handle_build_arch_override( self, watch_tasks_mock, @@ -484,14 +484,14 @@ Task info: weburl/taskinfo?taskID=1 # Run it and check immediate output # args: --arch-override=somearch, --scratch, target, http://scm # expected: success - rv = cli.handle_build(self.options, self.session, args) + rv = handle_build(self.options, self.session, args) actual = stdout.getvalue() expected = """Created task: 1 Task info: weburl/taskinfo?taskID=1 """ self.assertMultiLineEqual(actual, expected) # Finally, assert that things were called as we expected. - activate_session_mock.assert_called_once_with(self.session) + activate_session_mock.assert_called_once_with(self.session, self.options) self.session.getBuildTarget.assert_called_once_with(target) self.session.getTag.assert_called_once_with(dest_tag) unique_path_mock.assert_not_called() @@ -502,14 +502,15 @@ Task info: weburl/taskinfo?taskID=1 source, target, opts, priority=priority) self.session.logout.assert_called() watch_tasks_mock.assert_called_once_with( - self.session, [task_id], quiet=self.options.quiet) + self.session, [task_id], quiet=self.options.quiet, + poll_interval=self.options.poll_interval) self.assertEqual(rv, 0) @mock.patch('sys.stdout', new_callable=six.StringIO) - @mock.patch('koji_cli.activate_session') - @mock.patch('koji_cli._unique_path', return_value='random_path') - @mock.patch('koji_cli._running_in_bg', return_value=False) - @mock.patch('koji_cli.watch_tasks', return_value=0) + @mock.patch('koji_cli.commands.activate_session') + @mock.patch('koji_cli.commands._unique_path', return_value='random_path') + @mock.patch('koji_cli.commands._running_in_bg', return_value=False) + @mock.patch('koji_cli.commands.watch_tasks', return_value=0) def test_handle_build_background( self, watch_tasks_mock, @@ -533,14 +534,14 @@ Task info: weburl/taskinfo?taskID=1 # Run it and check immediate output # args: --background, target, http://scm # expected: success - rv = cli.handle_build(self.options, self.session, args) + rv = handle_build(self.options, self.session, args) actual = stdout.getvalue() expected = """Created task: 1 Task info: weburl/taskinfo?taskID=1 """ self.assertMultiLineEqual(actual, expected) # Finally, assert that things were called as we expected. - activate_session_mock.assert_called_once_with(self.session) + activate_session_mock.assert_called_once_with(self.session, self.options) self.session.getBuildTarget.assert_called_once_with(target) self.session.getTag.assert_called_once_with(dest_tag) unique_path_mock.assert_not_called() @@ -550,14 +551,15 @@ Task info: weburl/taskinfo?taskID=1 source, target, opts, priority=priority) self.session.logout.assert_called() watch_tasks_mock.assert_called_once_with( - self.session, [task_id], quiet=self.options.quiet) + self.session, [task_id], quiet=self.options.quiet, + poll_interval=self.options.poll_interval) self.assertEqual(rv, 0) @mock.patch('sys.stdout', new_callable=six.StringIO) - @mock.patch('koji_cli.activate_session') - @mock.patch('koji_cli._unique_path', return_value='random_path') - @mock.patch('koji_cli._running_in_bg', return_value=True) - @mock.patch('koji_cli.watch_tasks', return_value=0) + @mock.patch('koji_cli.commands.activate_session') + @mock.patch('koji_cli.commands._unique_path', return_value='random_path') + @mock.patch('koji_cli.commands._running_in_bg', return_value=True) + @mock.patch('koji_cli.commands.watch_tasks', return_value=0) def test_handle_build_running_in_bg( self, watch_tasks_mock, @@ -581,7 +583,7 @@ Task info: weburl/taskinfo?taskID=1 # Run it and check immediate output # args: target, srpm # expected: success - rv = cli.handle_build(self.options, self.session, args) + rv = handle_build(self.options, self.session, args) actual = stdout.getvalue() expected = """Uploading srpm: srpm @@ -590,7 +592,7 @@ Task info: weburl/taskinfo?taskID=1 """ self.assertMultiLineEqual(actual, expected) # Finally, assert that things were called as we expected. - activate_session_mock.assert_called_once_with(self.session) + activate_session_mock.assert_called_once_with(self.session, self.options) self.session.getBuildTarget.assert_called_once_with(target) self.session.getTag.assert_called_once_with(dest_tag) unique_path_mock.assert_called_once_with('cli-build') @@ -605,10 +607,10 @@ Task info: weburl/taskinfo?taskID=1 self.assertIsNone(rv) @mock.patch('sys.stdout', new_callable=six.StringIO) - @mock.patch('koji_cli.activate_session') - @mock.patch('koji_cli._unique_path', return_value='random_path') - @mock.patch('koji_cli._running_in_bg', return_value=False) - @mock.patch('koji_cli.watch_tasks', return_value=0) + @mock.patch('koji_cli.commands.activate_session') + @mock.patch('koji_cli.commands._unique_path', return_value='random_path') + @mock.patch('koji_cli.commands._running_in_bg', return_value=False) + @mock.patch('koji_cli.commands.watch_tasks', return_value=0) def test_handle_build_noprogress( self, watch_tasks_mock, @@ -632,7 +634,7 @@ Task info: weburl/taskinfo?taskID=1 # Run it and check immediate output # args: --noprogress, target, srpm # expected: success - rv = cli.handle_build(self.options, self.session, args) + rv = handle_build(self.options, self.session, args) actual = stdout.getvalue() expected = """Uploading srpm: srpm @@ -641,7 +643,7 @@ Task info: weburl/taskinfo?taskID=1 """ self.assertMultiLineEqual(actual, expected) # Finally, assert that things were called as we expected. - activate_session_mock.assert_called_once_with(self.session) + activate_session_mock.assert_called_once_with(self.session, self.options) self.session.getBuildTarget.assert_called_once_with(target) self.session.getTag.assert_called_once_with(dest_tag) unique_path_mock.assert_called_once_with('cli-build') @@ -653,14 +655,15 @@ Task info: weburl/taskinfo?taskID=1 'random_path/' + source, target, opts, priority=priority) self.session.logout.assert_called_once() watch_tasks_mock.assert_called_once_with( - self.session, [task_id], quiet=self.options.quiet) + self.session, [task_id], quiet=self.options.quiet, + poll_interval=self.options.poll_interval) self.assertEqual(rv, 0) @mock.patch('sys.stdout', new_callable=six.StringIO) - @mock.patch('koji_cli.activate_session') - @mock.patch('koji_cli._unique_path', return_value='random_path') - @mock.patch('koji_cli._running_in_bg', return_value=False) - @mock.patch('koji_cli.watch_tasks', return_value=0) + @mock.patch('koji_cli.commands.activate_session') + @mock.patch('koji_cli.commands._unique_path', return_value='random_path') + @mock.patch('koji_cli.commands._running_in_bg', return_value=False) + @mock.patch('koji_cli.commands.watch_tasks', return_value=0) def test_handle_build_quiet( self, watch_tasks_mock, @@ -685,12 +688,12 @@ Task info: weburl/taskinfo?taskID=1 # Run it and check immediate output # args: --quiet, target, srpm # expected: success - rv = cli.handle_build(self.options, self.session, args) + rv = handle_build(self.options, self.session, args) actual = stdout.getvalue() expected = '\n' self.assertMultiLineEqual(actual, expected) # Finally, assert that things were called as we expected. - activate_session_mock.assert_called_once_with(self.session) + activate_session_mock.assert_called_once_with(self.session, self.options) self.session.getBuildTarget.assert_called_once_with(target) self.session.getTag.assert_called_once_with(dest_tag) unique_path_mock.assert_called_once_with('cli-build') @@ -702,14 +705,15 @@ Task info: weburl/taskinfo?taskID=1 'random_path/' + source, target, opts, priority=priority) self.session.logout.assert_called_once() watch_tasks_mock.assert_called_once_with( - self.session, [task_id], quiet=quiet) + self.session, [task_id], quiet=quiet, + poll_interval=self.options.poll_interval) self.assertEqual(rv, 0) @mock.patch('sys.stdout', new_callable=six.StringIO) - @mock.patch('koji_cli.activate_session') - @mock.patch('koji_cli._unique_path', return_value='random_path') - @mock.patch('koji_cli._running_in_bg', return_value=False) - @mock.patch('koji_cli.watch_tasks', return_value=0) + @mock.patch('koji_cli.commands.activate_session') + @mock.patch('koji_cli.commands._unique_path', return_value='random_path') + @mock.patch('koji_cli.commands._running_in_bg', return_value=False) + @mock.patch('koji_cli.commands.watch_tasks', return_value=0) def test_handle_build_wait( self, watch_tasks_mock, @@ -734,7 +738,7 @@ Task info: weburl/taskinfo?taskID=1 # Run it and check immediate output # args: --wait, target, srpm # expected: success - rv = cli.handle_build(self.options, self.session, args) + rv = handle_build(self.options, self.session, args) actual = stdout.getvalue() expected = """Uploading srpm: srpm @@ -743,26 +747,27 @@ Task info: weburl/taskinfo?taskID=1 """ self.assertMultiLineEqual(actual, expected) # Finally, assert that things were called as we expected. - activate_session_mock.assert_called_once_with(self.session) + activate_session_mock.assert_called_once_with(self.session, self.options) self.session.getBuildTarget.assert_called_once_with(target) self.session.getTag.assert_called_once_with(dest_tag) unique_path_mock.assert_called_once_with('cli-build') # the second one won't be executed when wait==False self.assertEqual(running_in_bg_mock.call_count, 1) self.session.uploadWrapper.assert_called_once_with( - source, 'random_path', callback=cli._progress_callback) + source, 'random_path', callback=_progress_callback) self.session.build.assert_called_once_with( 'random_path/' + source, target, opts, priority=priority) self.session.logout.assert_called_once() watch_tasks_mock.assert_called_once_with( - self.session, [task_id], quiet=self.options.quiet) + self.session, [task_id], quiet=self.options.quiet, + poll_interval=self.options.poll_interval) self.assertEqual(rv, 0) @mock.patch('sys.stdout', new_callable=six.StringIO) - @mock.patch('koji_cli.activate_session') - @mock.patch('koji_cli._unique_path', return_value='random_path') - @mock.patch('koji_cli._running_in_bg', return_value=False) - @mock.patch('koji_cli.watch_tasks', return_value=0) + @mock.patch('koji_cli.commands.activate_session') + @mock.patch('koji_cli.commands._unique_path', return_value='random_path') + @mock.patch('koji_cli.commands._running_in_bg', return_value=False) + @mock.patch('koji_cli.commands.watch_tasks', return_value=0) def test_handle_build_nowait( self, watch_tasks_mock, @@ -786,7 +791,7 @@ Task info: weburl/taskinfo?taskID=1 # Run it and check immediate output # args: --nowait, target, srpm # expected: success - rv = cli.handle_build(self.options, self.session, args) + rv = handle_build(self.options, self.session, args) actual = stdout.getvalue() expected = """Uploading srpm: srpm @@ -795,14 +800,14 @@ Task info: weburl/taskinfo?taskID=1 """ self.assertMultiLineEqual(actual, expected) # Finally, assert that things were called as we expected. - activate_session_mock.assert_called_once_with(self.session) + activate_session_mock.assert_called_once_with(self.session, self.options) self.session.getBuildTarget.assert_called_once_with(target) self.session.getTag.assert_called_once_with(dest_tag) unique_path_mock.assert_called_once_with('cli-build') # the second one won't be executed when wait==False self.assertEqual(running_in_bg_mock.call_count, 1) self.session.uploadWrapper.assert_called_once_with( - source, 'random_path', callback=cli._progress_callback) + source, 'random_path', callback=_progress_callback) self.session.build.assert_called_once_with( 'random_path/' + source, target, opts, priority=priority) self.session.logout.assert_not_called() diff --git a/tests/test_cli/test_chain_build.py b/tests/test_cli/test_chain_build.py index 9ded5e6..dd616ad 100644 --- a/tests/test_cli/test_chain_build.py +++ b/tests/test_cli/test_chain_build.py @@ -1,15 +1,12 @@ from __future__ import absolute_import -import unittest -import os -import sys import mock +import os import six +import sys +import unittest -from . import loadcli - -cli = loadcli.cli - +from koji_cli.commands import handle_chain_build class TestChainBuild(unittest.TestCase): # Show long diffs in error output... @@ -20,13 +17,14 @@ class TestChainBuild(unittest.TestCase): self.options = mock.MagicMock() self.options.quiet = None self.options.weburl = 'weburl' + self.options.poll_interval = 0 # Mock out the xmlrpc server self.session = mock.MagicMock() @mock.patch('sys.stdout', new_callable=six.StringIO) - @mock.patch('koji_cli.activate_session') - @mock.patch('koji_cli._running_in_bg', return_value=False) - @mock.patch('koji_cli.watch_tasks', return_value=0) + @mock.patch('koji_cli.commands.activate_session') + @mock.patch('koji_cli.commands._running_in_bg', return_value=False) + @mock.patch('koji_cli.commands.watch_tasks', return_value=0) def test_handle_chain_build(self, watch_tasks_mock, running_in_bg_mock, activate_session_mock, stdout): target = 'target' @@ -63,14 +61,14 @@ class TestChainBuild(unittest.TestCase): # Run it and check immediate output # args: target http://scm1 : http://scm2 http://scm3 n-v-r-1 : n-v-r-2 n-v-r-3 # expected: success - rv = cli.handle_chain_build(self.options, self.session, args) + rv = handle_chain_build(self.options, self.session, args) actual = stdout.getvalue() expected = """Created task: 1 Task info: weburl/taskinfo?taskID=1 """ self.assertMultiLineEqual(actual, expected) # Finally, assert that things were called as we expected. - activate_session_mock.assert_called_once_with(self.session) + activate_session_mock.assert_called_once_with(self.session, self.options) self.session.getBuildTarget.assert_called_once_with(target) self.session.getTag.assert_called_once_with(dest_tag_id, strict=True) self.session.getFullInheritance.assert_called_once_with(build_tag_id) @@ -79,14 +77,15 @@ Task info: weburl/taskinfo?taskID=1 running_in_bg_mock.assert_called_once() self.session.logout.assert_called() watch_tasks_mock.assert_called_once_with( - self.session, [task_id], quiet=self.options.quiet) + self.session, [task_id], quiet=self.options.quiet, + poll_interval=self.options.poll_interval) self.assertEqual(rv, 0) @mock.patch('sys.stdout', new_callable=six.StringIO) @mock.patch('sys.stderr', new_callable=six.StringIO) - @mock.patch('koji_cli.activate_session') - @mock.patch('koji_cli._running_in_bg', return_value=False) - @mock.patch('koji_cli.watch_tasks', return_value=0) + @mock.patch('koji_cli.commands.activate_session') + @mock.patch('koji_cli.commands._running_in_bg', return_value=False) + @mock.patch('koji_cli.commands.watch_tasks', return_value=0) def test_handle_chain_build_no_arg( self, watch_tasks_mock, @@ -99,7 +98,7 @@ Task info: weburl/taskinfo?taskID=1 # Run it and check immediate output with self.assertRaises(SystemExit) as cm: - cli.handle_chain_build(self.options, self.session, args) + handle_chain_build(self.options, self.session, args) actual_stdout = stdout.getvalue() actual_stderr = stderr.getvalue() expected_stdout = '' @@ -124,9 +123,9 @@ Task info: weburl/taskinfo?taskID=1 @mock.patch('sys.stdout', new_callable=six.StringIO) @mock.patch('sys.stderr', new_callable=six.StringIO) - @mock.patch('koji_cli.activate_session') - @mock.patch('koji_cli._running_in_bg', return_value=False) - @mock.patch('koji_cli.watch_tasks', return_value=0) + @mock.patch('koji_cli.commands.activate_session') + @mock.patch('koji_cli.commands._running_in_bg', return_value=False) + @mock.patch('koji_cli.commands.watch_tasks', return_value=0) def test_handle_chain_build_help( self, watch_tasks_mock, @@ -139,7 +138,7 @@ Task info: weburl/taskinfo?taskID=1 # Run it and check immediate output with self.assertRaises(SystemExit) as cm: - cli.handle_chain_build(self.options, self.session, args) + handle_chain_build(self.options, self.session, args) actual_stdout = stdout.getvalue() actual_stderr = stderr.getvalue() expected_stdout = """Usage: %s chain-build [options] target URL [URL2 [:] URL3 [:] URL4 ...] @@ -167,9 +166,9 @@ Options: self.assertEqual(cm.exception.code, 0) @mock.patch('sys.stderr', new_callable=six.StringIO) - @mock.patch('koji_cli.activate_session') - @mock.patch('koji_cli._running_in_bg', return_value=False) - @mock.patch('koji_cli.watch_tasks', return_value=0) + @mock.patch('koji_cli.commands.activate_session') + @mock.patch('koji_cli.commands._running_in_bg', return_value=False) + @mock.patch('koji_cli.commands.watch_tasks', return_value=0) def test_handle_chain_build_target_not_found( self, watch_tasks_mock, @@ -196,7 +195,7 @@ Options: # args: target http://scm1 : http://scm2 http://scm3 n-v-r-1 : n-v-r-2 n-v-r-3 # expected: failed, target not found with self.assertRaises(SystemExit) as cm: - cli.handle_chain_build(self.options, self.session, args) + handle_chain_build(self.options, self.session, args) actual = stderr.getvalue() expected = """Usage: %s chain-build [options] target URL [URL2 [:] URL3 [:] URL4 ...] (Specify the --help global option for a list of other help options) @@ -205,7 +204,7 @@ Options: """ % (progname, progname) self.assertMultiLineEqual(actual, expected) # Finally, assert that things were called as we expected. - activate_session_mock.assert_called_once_with(self.session) + activate_session_mock.assert_called_once_with(self.session, self.options) self.session.getBuildTarget.assert_called_once_with(target) self.session.getTag.assert_not_called() self.session.getFullInheritance.assert_not_called() @@ -216,9 +215,9 @@ Options: self.assertEqual(cm.exception.code, 2) @mock.patch('sys.stderr', new_callable=six.StringIO) - @mock.patch('koji_cli.activate_session') - @mock.patch('koji_cli._running_in_bg', return_value=False) - @mock.patch('koji_cli.watch_tasks', return_value=0) + @mock.patch('koji_cli.commands.activate_session') + @mock.patch('koji_cli.commands._running_in_bg', return_value=False) + @mock.patch('koji_cli.commands.watch_tasks', return_value=0) def test_handle_build_dest_tag_locked( self, watch_tasks_mock, @@ -255,7 +254,7 @@ Options: # args: target http://scm1 : http://scm2 http://scm3 n-v-r-1 : n-v-r-2 n-v-r-3 # expected: failed, dest_tag is locked with self.assertRaises(SystemExit) as cm: - cli.handle_chain_build(self.options, self.session, args) + handle_chain_build(self.options, self.session, args) actual = stderr.getvalue() expected = """Usage: %s chain-build [options] target URL [URL2 [:] URL3 [:] URL4 ...] (Specify the --help global option for a list of other help options) @@ -264,7 +263,7 @@ Options: """ % (progname, progname) self.assertMultiLineEqual(actual, expected) # Finally, assert that things were called as we expected. - activate_session_mock.assert_called_once_with(self.session) + activate_session_mock.assert_called_once_with(self.session, self.options) self.session.getBuildTarget.assert_called_once_with(target) self.session.getTag.assert_called_once_with(dest_tag_id, strict=True) self.session.getFullInheritance.assert_not_called() @@ -275,9 +274,9 @@ Options: self.assertEqual(cm.exception.code, 2) @mock.patch('sys.stdout', new_callable=six.StringIO) - @mock.patch('koji_cli.activate_session') - @mock.patch('koji_cli._running_in_bg', return_value=False) - @mock.patch('koji_cli.watch_tasks', return_value=0) + @mock.patch('koji_cli.commands.activate_session') + @mock.patch('koji_cli.commands._running_in_bg', return_value=False) + @mock.patch('koji_cli.commands.watch_tasks', return_value=0) def test_handle_build_dest_tag_not_inherited_by_build_tag( self, watch_tasks_mock, running_in_bg_mock, activate_session_mock, stdout): target = 'target' @@ -310,14 +309,14 @@ Options: # Run it and check immediate output # args: target, target http://scm1 : http://scm2 http://scm3 n-v-r-1 : n-v-r-2 n-v-r-3 # expected: failed, dest_tag is not in build_tag's inheritance - rv = cli.handle_chain_build(self.options, self.session, args) + rv = handle_chain_build(self.options, self.session, args) actual = stdout.getvalue() expected = """Packages in destination tag dest_tag are not inherited by build tag build_tag Target target is not usable for a chain-build """ self.assertMultiLineEqual(actual, expected) # Finally, assert that things were called as we expected. - activate_session_mock.assert_called_once_with(self.session) + activate_session_mock.assert_called_once_with(self.session, self.options) self.session.getBuildTarget.assert_called_once_with(target) self.session.getTag.assert_called_once_with(dest_tag_id, strict=True) self.session.getFullInheritance.assert_called_once_with(build_tag_id) @@ -327,9 +326,9 @@ Target target is not usable for a chain-build watch_tasks_mock.assert_not_called() self.assertEqual(rv, 1) - @mock.patch('koji_cli.activate_session') - @mock.patch('koji_cli._running_in_bg', return_value=False) - @mock.patch('koji_cli.watch_tasks', return_value=0) + @mock.patch('koji_cli.commands.activate_session') + @mock.patch('koji_cli.commands._running_in_bg', return_value=False) + @mock.patch('koji_cli.commands.watch_tasks', return_value=0) def test_handle_chain_build_invalidated_src( self, watch_tasks_mock, @@ -365,12 +364,12 @@ Target target is not usable for a chain-build # Run it and check immediate output # args: target badnvr : http://scm2 http://scm3 n-v-r-1 : n-v-r-2 n-v-r-3 # expected: failed, src is neither scm nor good n-v-r - rv = cli.handle_chain_build(self.options, self.session, args) + rv = handle_chain_build(self.options, self.session, args) actual = stdout.getvalue() expected = '"badnvr" is not a SCM URL or package N-V-R\n' self.assertMultiLineEqual(actual, expected) # Finally, assert that things were called as we expected. - activate_session_mock.assert_called_once_with(self.session) + activate_session_mock.assert_called_once_with(self.session, self.options) self.session.getBuildTarget.assert_called_once_with(target) self.session.getTag.assert_called_once_with( dest_tag_id, strict=True) @@ -395,7 +394,7 @@ Target target is not usable for a chain-build args = [target] + source_args # args: target path/n-v-r : http://scm2 http://scm3 n-v-r-1 : n-v-r-2 n-v-r-3 # expected: failed - cli.handle_chain_build(self.options, self.session, args) + handle_chain_build(self.options, self.session, args) actual = stdout.getvalue() expected = '"path/n-v-r" is not a SCM URL or package N-V-R\n' self.assertMultiLineEqual(actual, expected) @@ -413,7 +412,7 @@ Target target is not usable for a chain-build args = [target] + source_args # args: target badn-vr : http://scm2 http://scm3 n-v-r-1 : n-v-r-2 n-v-r-3 # expected: failed - cli.handle_chain_build(self.options, self.session, args) + handle_chain_build(self.options, self.session, args) actual = stdout.getvalue() expected = '"badn-vr" is not a SCM URL or package N-V-R\n' self.assertMultiLineEqual(actual, expected) @@ -431,7 +430,7 @@ Target target is not usable for a chain-build args = [target] + source_args # args: target badn-v-r.rpm : http://scm2 http://scm3 n-v-r-1 : n-v-r-2 n-v-r-3 # expected: failed - cli.handle_chain_build(self.options, self.session, args) + handle_chain_build(self.options, self.session, args) actual = stdout.getvalue() expected = '"badn-v-r.rpm" is not a SCM URL or package N-V-R\n' self.assertMultiLineEqual(actual, expected) @@ -445,7 +444,7 @@ Target target is not usable for a chain-build # args: target http://scm # expected: failed, only one src found with self.assertRaises(SystemExit) as cm: - cli.handle_chain_build(self.options, self.session, args) + handle_chain_build(self.options, self.session, args) actual = stderr.getvalue() expected = """Usage: %s chain-build [options] target URL [URL2 [:] URL3 [:] URL4 ...] (Specify the --help global option for a list of other help options) @@ -457,9 +456,9 @@ If there are no dependencies, use the build command instead self.assertEqual(cm.exception.code, 2) @mock.patch('sys.stdout', new_callable=six.StringIO) - @mock.patch('koji_cli.activate_session') - @mock.patch('koji_cli._running_in_bg', return_value=False) - @mock.patch('koji_cli.watch_tasks', return_value=0) + @mock.patch('koji_cli.commands.activate_session') + @mock.patch('koji_cli.commands._running_in_bg', return_value=False) + @mock.patch('koji_cli.commands.watch_tasks', return_value=0) def test_handle_chain_build_background( self, watch_tasks_mock, @@ -500,14 +499,14 @@ If there are no dependencies, use the build command instead # Run it and check immediate output # args: target http://scm1 : http://scm2 http://scm3 n-v-r-1 : n-v-r-2 n-v-r-3 # expected: success - rv = cli.handle_chain_build(self.options, self.session, args) + rv = handle_chain_build(self.options, self.session, args) actual = stdout.getvalue() expected = """Created task: 1 Task info: weburl/taskinfo?taskID=1 """ self.assertMultiLineEqual(actual, expected) # Finally, assert that things were called as we expected. - activate_session_mock.assert_called_once_with(self.session) + activate_session_mock.assert_called_once_with(self.session, self.options) self.session.getBuildTarget.assert_called_once_with(target) self.session.getTag.assert_called_once_with(dest_tag_id, strict=True) self.session.getFullInheritance.assert_called_once_with(build_tag_id) @@ -516,13 +515,14 @@ Task info: weburl/taskinfo?taskID=1 running_in_bg_mock.assert_called_once() self.session.logout.assert_called() watch_tasks_mock.assert_called_once_with( - self.session, [task_id], quiet=self.options.quiet) + self.session, [task_id], quiet=self.options.quiet, + poll_interval=self.options.poll_interval) self.assertEqual(rv, 0) @mock.patch('sys.stdout', new_callable=six.StringIO) - @mock.patch('koji_cli.activate_session') - @mock.patch('koji_cli._running_in_bg', return_value=False) - @mock.patch('koji_cli.watch_tasks', return_value=0) + @mock.patch('koji_cli.commands.activate_session') + @mock.patch('koji_cli.commands._running_in_bg', return_value=False) + @mock.patch('koji_cli.commands.watch_tasks', return_value=0) def test_handle_chain_build_quiet( self, watch_tasks_mock, @@ -564,12 +564,12 @@ Task info: weburl/taskinfo?taskID=1 # Run it and check immediate output # args: target http://scm1 : http://scm2 http://scm3 n-v-r-1 : n-v-r-2 n-v-r-3 # expected: success - rv = cli.handle_chain_build(self.options, self.session, args) + rv = handle_chain_build(self.options, self.session, args) actual = stdout.getvalue() expected = '' self.assertMultiLineEqual(actual, expected) # Finally, assert that things were called as we expected. - activate_session_mock.assert_called_once_with(self.session) + activate_session_mock.assert_called_once_with(self.session, self.options) self.session.getBuildTarget.assert_called_once_with(target) self.session.getTag.assert_called_once_with(dest_tag_id, strict=True) self.session.getFullInheritance.assert_called_once_with(build_tag_id) @@ -578,13 +578,14 @@ Task info: weburl/taskinfo?taskID=1 running_in_bg_mock.assert_called_once() self.session.logout.assert_called() watch_tasks_mock.assert_called_once_with( - self.session, [task_id], quiet=self.options.quiet) + self.session, [task_id], quiet=self.options.quiet, + poll_interval=self.options.poll_interval) self.assertEqual(rv, 0) @mock.patch('sys.stdout', new_callable=six.StringIO) - @mock.patch('koji_cli.activate_session') - @mock.patch('koji_cli._running_in_bg', return_value=True) - @mock.patch('koji_cli.watch_tasks', return_value=0) + @mock.patch('koji_cli.commands.activate_session') + @mock.patch('koji_cli.commands._running_in_bg', return_value=True) + @mock.patch('koji_cli.commands.watch_tasks', return_value=0) def test_handle_chain_build_running_in_bg( self, watch_tasks_mock, @@ -625,14 +626,14 @@ Task info: weburl/taskinfo?taskID=1 # Run it and check immediate output # args: target http://scm1 : http://scm2 http://scm3 n-v-r-1 : n-v-r-2 n-v-r-3 # expected: success - rv = cli.handle_chain_build(self.options, self.session, args) + rv = handle_chain_build(self.options, self.session, args) actual = stdout.getvalue() expected = """Created task: 1 Task info: weburl/taskinfo?taskID=1 """ self.assertMultiLineEqual(actual, expected) # Finally, assert that things were called as we expected. - activate_session_mock.assert_called_once_with(self.session) + activate_session_mock.assert_called_once_with(self.session, self.options) self.session.getBuildTarget.assert_called_once_with(target) self.session.getTag.assert_called_once_with(dest_tag_id, strict=True) self.session.getFullInheritance.assert_called_once_with(build_tag_id) @@ -644,9 +645,9 @@ Task info: weburl/taskinfo?taskID=1 self.assertIsNone(rv) @mock.patch('sys.stdout', new_callable=six.StringIO) - @mock.patch('koji_cli.activate_session') - @mock.patch('koji_cli._running_in_bg', return_value=False) - @mock.patch('koji_cli.watch_tasks', return_value=0) + @mock.patch('koji_cli.commands.activate_session') + @mock.patch('koji_cli.commands._running_in_bg', return_value=False) + @mock.patch('koji_cli.commands.watch_tasks', return_value=0) def test_handle_chain_build_nowait( self, watch_tasks_mock, @@ -687,14 +688,14 @@ Task info: weburl/taskinfo?taskID=1 # Run it and check immediate output # args: target http://scm1 : http://scm2 http://scm3 n-v-r-1 : n-v-r-2 n-v-r-3 # expected: success - rv = cli.handle_chain_build(self.options, self.session, args) + rv = handle_chain_build(self.options, self.session, args) actual = stdout.getvalue() expected = """Created task: 1 Task info: weburl/taskinfo?taskID=1 """ self.assertMultiLineEqual(actual, expected) # Finally, assert that things were called as we expected. - activate_session_mock.assert_called_once_with(self.session) + activate_session_mock.assert_called_once_with(self.session, self.options) self.session.getBuildTarget.assert_called_once_with(target) self.session.getTag.assert_called_once_with(dest_tag_id, strict=True) self.session.getFullInheritance.assert_called_once_with(build_tag_id) diff --git a/tests/test_cli/test_edit_host.py b/tests/test_cli/test_edit_host.py index ccf0b7d..e6aca06 100644 --- a/tests/test_cli/test_edit_host.py +++ b/tests/test_cli/test_edit_host.py @@ -1,16 +1,12 @@ from __future__ import absolute_import -import unittest - -import os -import sys import mock +import os import six +import sys +import unittest from mock import call -from . import loadcli - -cli = loadcli.cli - +from koji_cli.commands import handle_edit_host class TestEditHost(unittest.TestCase): @@ -18,7 +14,7 @@ class TestEditHost(unittest.TestCase): maxDiff = None @mock.patch('sys.stdout', new_callable=six.StringIO) - @mock.patch('koji_cli.activate_session') + @mock.patch('koji_cli.commands.activate_session') def test_handle_edit_host(self, activate_session_mock, stdout): host = 'host' host_info = mock.ANY @@ -45,19 +41,19 @@ class TestEditHost(unittest.TestCase): # args: host, --arches='arch1 arch2', --capacity=0.22, # --description=description, --comment=comment # expected: success - rv = cli.handle_edit_host(options, session, args) + rv = handle_edit_host(options, session, args) actual = stdout.getvalue() expected = 'Edited host\n' self.assertMultiLineEqual(actual, expected) # Finally, assert that things were called as we expected. - activate_session_mock.assert_called_once_with(session) + activate_session_mock.assert_called_once_with(session, options) session.getHost.assert_called_once_with(host) session.editHost.assert_called_once_with(host, **kwargs) self.assertEqual(session.multiCall.call_count, 2) self.assertNotEqual(rv, 1) @mock.patch('sys.stdout', new_callable=six.StringIO) - @mock.patch('koji_cli.activate_session') + @mock.patch('koji_cli.commands.activate_session') def test_handle_edit_host_failed(self, activate_session_mock, stdout): host = 'host' host_info = mock.ANY @@ -84,19 +80,19 @@ class TestEditHost(unittest.TestCase): # args: host, --arches='arch1 arch2', --capacity=0.22, # --description=description, --comment=comment # expected: failed - session.editHost == False - rv = cli.handle_edit_host(options, session, args) + rv = handle_edit_host(options, session, args) actual = stdout.getvalue() expected = 'No changes made to host\n' self.assertMultiLineEqual(actual, expected) # Finally, assert that things were called as we expected. - activate_session_mock.assert_called_once_with(session) + activate_session_mock.assert_called_once_with(session, options) session.getHost.assert_called_once_with(host) session.editHost.assert_called_once_with(host, **kwargs) self.assertEqual(session.multiCall.call_count, 2) self.assertNotEqual(rv, 1) @mock.patch('sys.stdout', new_callable=six.StringIO) - @mock.patch('koji_cli.activate_session') + @mock.patch('koji_cli.commands.activate_session') def test_handle_edit_multi_host(self, activate_session_mock, stdout): hosts = ['host1', 'host2'] host_infos = [mock.ANY, mock.ANY] @@ -124,12 +120,12 @@ class TestEditHost(unittest.TestCase): # args: host1, host2, --arches='arch1 arch2', --capacity=0.22, # --description=description, --comment=comment # expected: success - rv = cli.handle_edit_host(options, session, args) + rv = handle_edit_host(options, session, args) actual = stdout.getvalue() expected = 'Edited host1\nEdited host2\n' self.assertMultiLineEqual(actual, expected) # Finally, assert that things were called as we expected. - activate_session_mock.assert_called_once_with(session) + activate_session_mock.assert_called_once_with(session, options) self.assertEqual(session.mock_calls, [call.getHost(hosts[0]), call.getHost(hosts[1]), @@ -143,7 +139,7 @@ class TestEditHost(unittest.TestCase): @mock.patch('sys.stdout', new_callable=six.StringIO) @mock.patch('sys.stderr', new_callable=six.StringIO) - @mock.patch('koji_cli.activate_session') + @mock.patch('koji_cli.commands.activate_session') def test_handle_edit_host_no_arg( self, activate_session_mock, stderr, stdout): args = [] @@ -158,7 +154,7 @@ class TestEditHost(unittest.TestCase): # args: _empty_ # expected: failed - should specify host with self.assertRaises(SystemExit) as cm: - cli.handle_edit_host(options, session, args) + handle_edit_host(options, session, args) actual_stdout = stdout.getvalue() actual_stderr = stderr.getvalue() expected_stdout = '' @@ -178,7 +174,7 @@ class TestEditHost(unittest.TestCase): self.assertEqual(cm.exception.code, 2) @mock.patch('sys.stdout', new_callable=six.StringIO) - @mock.patch('koji_cli.activate_session') + @mock.patch('koji_cli.commands.activate_session') def test_handle_edit_host_no_host(self, activate_session_mock, stdout): host = 'host' host_info = None @@ -201,14 +197,14 @@ class TestEditHost(unittest.TestCase): # args: host, --arches='arch1 arch2', --capacity=0.22, # --description=description, --comment=comment # expected: failed -- getHost() == None - rv = cli.handle_edit_host(options, session, args) + rv = handle_edit_host(options, session, args) actual = stdout.getvalue() expected = """Host host does not exist No changes made, please correct the command line """ self.assertMultiLineEqual(actual, expected) # Finally, assert that things were called as we expected. - activate_session_mock.assert_called_once_with(session) + activate_session_mock.assert_called_once_with(session, options) session.getHost.assert_called_once_with(host) session.editHost.assert_not_called() self.assertEqual(session.multiCall.call_count, 1) diff --git a/tests/test_cli/test_edit_tag.py b/tests/test_cli/test_edit_tag.py index 31d62c5..a2f79ba 100644 --- a/tests/test_cli/test_edit_tag.py +++ b/tests/test_cli/test_edit_tag.py @@ -1,15 +1,12 @@ from __future__ import absolute_import -import unittest - -import os -import sys import mock +import os import six +import sys +import unittest -from . import loadcli - -cli = loadcli.cli +from koji_cli.commands import handle_edit_tag progname = os.path.basename(sys.argv[0]) or 'koji' @@ -19,7 +16,7 @@ class TestEditTag(unittest.TestCase): maxDiff = None @mock.patch('sys.stdout', new_callable=six.StringIO) - @mock.patch('koji_cli.activate_session') + @mock.patch('koji_cli.commands.activate_session') def test_handle_edit_tag(self, activate_session_mock, stdout): tag = 'tag' arches = 'arch1 arch2' @@ -61,12 +58,12 @@ class TestEditTag(unittest.TestCase): # --rename=tag2 --maven-support --include-all # -x extraA=A -x extraB=True -r extraC -r extraD # expected: success - rv = cli.handle_edit_tag(options, session, args) + rv = handle_edit_tag(options, session, args) actual = stdout.getvalue() expected = '' self.assertMultiLineEqual(actual, expected) # Finally, assert that things were called as we expected. - activate_session_mock.assert_called_once_with(session) + activate_session_mock.assert_called_once_with(session, options) session.editTag2.assert_called_once_with(tag, **opts) self.assertEqual(rv, None) @@ -86,18 +83,18 @@ class TestEditTag(unittest.TestCase): # Run it and check immediate output # args: tag --no-perm --unlock --no-maven-support --no-include-all # expected: success - rv = cli.handle_edit_tag(options, session, args) + rv = handle_edit_tag(options, session, args) actual = stdout.getvalue() expected = '' self.assertMultiLineEqual(actual, expected) # Finally, assert that things were called as we expected. - activate_session_mock.assert_called_once_with(session) + activate_session_mock.assert_called_once_with(session, options) session.editTag2.assert_called_once_with(tag, **opts) self.assertEqual(rv, None) @mock.patch('sys.stdout', new_callable=six.StringIO) @mock.patch('sys.stderr', new_callable=six.StringIO) - @mock.patch('koji_cli.activate_session') + @mock.patch('koji_cli.commands.activate_session') def test_handle_edit_tag_help(self, activate_session_mock, stderr, stdout): args = ['--help'] options = mock.MagicMock() @@ -109,7 +106,7 @@ class TestEditTag(unittest.TestCase): # args: --help # expected: failed, help info shows with self.assertRaises(SystemExit) as cm: - cli.handle_edit_tag(options, session, args) + handle_edit_tag(options, session, args) actual_stdout = stdout.getvalue() actual_stderr = stderr.getvalue() expected_stdout = """Usage: %s edit-tag [options] name @@ -144,7 +141,7 @@ Options: @mock.patch('sys.stdout', new_callable=six.StringIO) @mock.patch('sys.stderr', new_callable=six.StringIO) - @mock.patch('koji_cli.activate_session') + @mock.patch('koji_cli.commands.activate_session') def test_handle_edit_tag_no_arg(self, activate_session_mock, stderr, stdout): args = [] options = mock.MagicMock() @@ -156,7 +153,7 @@ Options: # args: --help # expected: failed, help info shows with self.assertRaises(SystemExit) as cm: - cli.handle_edit_tag(options, session, args) + handle_edit_tag(options, session, args) actual_stdout = stdout.getvalue() actual_stderr = stderr.getvalue() expected_stdout = '' diff --git a/tests/test_cli/test_import_comps.py b/tests/test_cli/test_import_comps.py index 0a5404c..5cf9224 100644 --- a/tests/test_cli/test_import_comps.py +++ b/tests/test_cli/test_import_comps.py @@ -1,11 +1,10 @@ from __future__ import absolute_import import json -import unittest -import os -import sys import mock +import os import six -from . import loadcli +import sys +import unittest try: import libcomps @@ -16,18 +15,19 @@ try: except ImportError: yumcomps = None -cli = loadcli.cli - +import koji_cli.commands +from koji_cli.commands import handle_import_comps, _import_comps,\ + _import_comps_alt class TestImportComps(unittest.TestCase): # Show long diffs in error output... maxDiff = None @mock.patch('sys.stdout', new_callable=six.StringIO) - @mock.patch('koji_cli.libcomps') - @mock.patch('koji_cli.activate_session') - @mock.patch('koji_cli._import_comps') - @mock.patch('koji_cli._import_comps_alt') + @mock.patch('koji_cli.commands.libcomps') + @mock.patch('koji_cli.commands.activate_session') + @mock.patch('koji_cli.commands._import_comps') + @mock.patch('koji_cli.commands._import_comps_alt') def test_handle_import_comps_libcomps( self, mock_import_comps_alt, @@ -50,13 +50,13 @@ class TestImportComps(unittest.TestCase): # Run it and check immediate output # args: ./data/comps-example.xml, tag # expected: success - rv = cli.handle_import_comps(options, session, args) + rv = handle_import_comps(options, session, args) actual = stdout.getvalue() expected = '' self.assertMultiLineEqual(actual, expected) # Finally, assert that things were called as we expected. - mock_activate_session.assert_called_once_with(session) + mock_activate_session.assert_called_once_with(session, options) session.getTag.assert_called_once_with(tag) mock_import_comps.assert_called_once_with( session, filename, tag, kwargs) @@ -64,11 +64,11 @@ class TestImportComps(unittest.TestCase): self.assertNotEqual(rv, 1) @mock.patch('sys.stdout', new_callable=six.StringIO) - @mock.patch('koji_cli.libcomps', new=None) - @mock.patch('koji_cli.yumcomps', create=True) - @mock.patch('koji_cli.activate_session') - @mock.patch('koji_cli._import_comps') - @mock.patch('koji_cli._import_comps_alt') + @mock.patch('koji_cli.commands.libcomps', new=None) + @mock.patch('koji_cli.commands.yumcomps', create=True) + @mock.patch('koji_cli.commands.activate_session') + @mock.patch('koji_cli.commands._import_comps') + @mock.patch('koji_cli.commands._import_comps_alt') def test_handle_import_comps_yumcomps( self, mock_import_comps_alt, @@ -91,13 +91,13 @@ class TestImportComps(unittest.TestCase): # Run it and check immediate output # args: --force, ./data/comps-example.xml, tag # expected: success - rv = cli.handle_import_comps(options, session, args) + rv = handle_import_comps(options, session, args) actual = stdout.getvalue() expected = '' self.assertMultiLineEqual(actual, expected) # Finally, assert that things were called as we expected. - mock_activate_session.assert_called_once_with(session) + mock_activate_session.assert_called_once_with(session, options) session.getTag.assert_called_once_with(tag) mock_import_comps.assert_not_called() mock_import_comps_alt.assert_called_once_with( @@ -105,11 +105,11 @@ class TestImportComps(unittest.TestCase): self.assertNotEqual(rv, 1) @mock.patch('sys.stdout', new_callable=six.StringIO) - @mock.patch('koji_cli.libcomps', new=None) - @mock.patch('koji_cli.yumcomps', new=None, create=True) - @mock.patch('koji_cli.activate_session') - @mock.patch('koji_cli._import_comps') - @mock.patch('koji_cli._import_comps_alt') + @mock.patch('koji_cli.commands.libcomps', new=None) + @mock.patch('koji_cli.commands.yumcomps', new=None, create=True) + @mock.patch('koji_cli.commands.activate_session') + @mock.patch('koji_cli.commands._import_comps') + @mock.patch('koji_cli.commands._import_comps_alt') def test_handle_import_comps_comps_na( self, mock_import_comps_alt, @@ -129,22 +129,22 @@ class TestImportComps(unittest.TestCase): # Run it and check immediate output # args: --force, ./data/comps-example.xml, tag # expected: failed, no comps available - rv = cli.handle_import_comps(options, session, args) + rv = handle_import_comps(options, session, args) actual = stdout.getvalue() expected = 'comps module not available\n' self.assertMultiLineEqual(actual, expected) # Finally, assert that things were called as we expected. - mock_activate_session.assert_called_once_with(session) + mock_activate_session.assert_called_once_with(session, options) session.getTag.assert_called_once_with(tag) mock_import_comps.assert_not_called() mock_import_comps_alt.assert_not_called() self.assertEqual(rv, 1) @mock.patch('sys.stdout', new_callable=six.StringIO) - @mock.patch('koji_cli.activate_session') - @mock.patch('koji_cli._import_comps') - @mock.patch('koji_cli._import_comps_alt') + @mock.patch('koji_cli.commands.activate_session') + @mock.patch('koji_cli.commands._import_comps') + @mock.patch('koji_cli.commands._import_comps_alt') def test_handle_import_comps_tag_not_exists( self, mock_import_comps_alt, @@ -164,13 +164,13 @@ class TestImportComps(unittest.TestCase): # Run it and check immediate output # args: ./data/comps-example.xml, tag # expected: failed: tag does not exist - rv = cli.handle_import_comps(options, session, args) + rv = handle_import_comps(options, session, args) actual = stdout.getvalue() expected = 'No such tag: tag\n' self.assertMultiLineEqual(actual, expected) # Finally, assert that things were called as we expected. - mock_activate_session.assert_called_once_with(session) + mock_activate_session.assert_called_once_with(session, options) session.getTag.assert_called_once_with(tag) mock_import_comps.assert_not_called() mock_import_comps_alt.assert_not_called() @@ -178,9 +178,9 @@ class TestImportComps(unittest.TestCase): @mock.patch('sys.stdout', new_callable=six.StringIO) @mock.patch('sys.stderr', new_callable=six.StringIO) - @mock.patch('koji_cli.activate_session') - @mock.patch('koji_cli._import_comps') - @mock.patch('koji_cli._import_comps_alt') + @mock.patch('koji_cli.commands.activate_session') + @mock.patch('koji_cli.commands._import_comps') + @mock.patch('koji_cli.commands._import_comps_alt') def test_handle_import_comps_help( self, mock_import_comps_alt, mock_import_comps, @@ -196,7 +196,7 @@ class TestImportComps(unittest.TestCase): # Run it and check immediate output with self.assertRaises(SystemExit) as cm: - rv = cli.handle_import_comps(options, session, args) + rv = handle_import_comps(options, session, args) actual_stdout = stdout.getvalue() actual_stderr = stderr.getvalue() expected_stdout = '' @@ -224,7 +224,7 @@ class TestImportComps(unittest.TestCase): calls_file = os.path.dirname( __file__) + '/data/comps-example.libcomps.calls' self._test_import_comps( - cli._import_comps, + _import_comps, comps_file, stdout_file, calls_file, @@ -239,7 +239,7 @@ class TestImportComps(unittest.TestCase): calls_file = os.path.dirname( __file__) + '/data/comps-sample.libcomps.calls' self._test_import_comps( - cli._import_comps, + _import_comps, comps_file, stdout_file, calls_file, @@ -247,8 +247,8 @@ class TestImportComps(unittest.TestCase): @unittest.skipIf(yumcomps is None, "No yum.comps") @mock.patch('sys.stdout', new_callable=six.StringIO) - @mock.patch('koji_cli.libcomps', new=None) - @mock.patch('koji_cli.yumcomps', create=True, new=yumcomps) + @mock.patch('koji_cli.commands.libcomps', new=None) + @mock.patch('koji_cli.commands.yumcomps', create=True, new=yumcomps) def test_import_comps_yumcomps(self, stdout): comps_file = os.path.dirname(__file__) + '/data/comps-example.xml' stdout_file = os.path.dirname( @@ -256,7 +256,7 @@ class TestImportComps(unittest.TestCase): calls_file = os.path.dirname( __file__) + '/data/comps-example.yumcomps.calls' self._test_import_comps( - cli._import_comps_alt, + _import_comps_alt, comps_file, stdout_file, calls_file, @@ -264,8 +264,8 @@ class TestImportComps(unittest.TestCase): @unittest.skipIf(yumcomps is None, "No yum.comps") @mock.patch('sys.stdout', new_callable=six.StringIO) - @mock.patch('koji_cli.libcomps', new=None) - @mock.patch('koji_cli.yumcomps', create=True, new=yumcomps) + @mock.patch('koji_cli.commands.libcomps', new=None) + @mock.patch('koji_cli.commands.yumcomps', create=True, new=yumcomps) def test_import_comps_sample_yumcomps(self, stdout): comps_file = os.path.dirname(__file__) + '/data/comps-sample.xml' stdout_file = os.path.dirname( @@ -273,7 +273,7 @@ class TestImportComps(unittest.TestCase): calls_file = os.path.dirname( __file__) + '/data/comps-sample.yumcomps.calls' self._test_import_comps( - cli._import_comps_alt, + _import_comps_alt, comps_file, stdout_file, calls_file, @@ -342,20 +342,20 @@ def generate_out_calls(): comps_file = path + '/data/comps-example.xml' stdout_file = path + '/data/comps-example.libcomps.out' calls_file = path + '/data/comps-example.libcomps.calls' - _generate_out_calls(cli._import_comps, comps_file, stdout_file, calls_file) + _generate_out_calls(_import_comps, comps_file, stdout_file, calls_file) comps_file = path + '/data/comps-sample.xml' stdout_file = path + '/data/comps-sample.libcomps.out' calls_file = path + '/data/comps-sample.libcomps.calls' - _generate_out_calls(cli._import_comps, comps_file, stdout_file, calls_file) + _generate_out_calls(_import_comps, comps_file, stdout_file, calls_file) - cli.yumcomps = yumcomps + koji_cli.commands.yumcomps = yumcomps comps_file = path + '/data/comps-example.xml' stdout_file = path + '/data/comps-example.yumcomps.out' calls_file = path + '/data/comps-example.yumcomps.calls' _generate_out_calls( - cli._import_comps_alt, + _import_comps_alt, comps_file, stdout_file, calls_file) @@ -364,7 +364,7 @@ def generate_out_calls(): stdout_file = path + '/data/comps-sample.yumcomps.out' calls_file = path + '/data/comps-sample.yumcomps.calls' _generate_out_calls( - cli._import_comps_alt, + _import_comps_alt, comps_file, stdout_file, calls_file) diff --git a/tests/test_cli/test_list_channels.py b/tests/test_cli/test_list_channels.py index aa2674d..07f63e9 100644 --- a/tests/test_cli/test_list_channels.py +++ b/tests/test_cli/test_list_channels.py @@ -5,37 +5,28 @@ from six.moves import StringIO import koji -from . import loadcli -cli = loadcli.cli +from koji_cli.commands import anon_handle_list_channels class TestListChannels(unittest.TestCase): def setUp(self): self.options = mock.MagicMock() + self.options.quiet = True self.session = mock.MagicMock() self.session.getAPIVersion.return_value = koji.API_VERSION - self.args = mock.MagicMock() - self.original_parser = cli.OptionParser - cli.OptionParser = mock.MagicMock() - self.parser = cli.OptionParser.return_value - cli.options = self.options # globals!!! - - def tearDown(self): - cli.OptionParser = self.original_parser + self.args = [] @mock.patch('sys.stdout', new_callable=StringIO) - def test_list_channels(self, stdout): - options = mock.MagicMock() - options.quiet = True - self.parser.parse_args.return_value = [options, []] - - # mock xmlrpc + @mock.patch('koji_cli.commands.activate_session') + def test_list_channels(self, activate_session_mock, stdout): self.session.listChannels.return_value = [ {'id': 1, 'name': 'default'}, {'id': 2, 'name': 'test'}, ] self.session.multiCall.return_value = [[[1,2,3]], [[4,5]]] - cli.anon_handle_list_channels(self.options, self.session, self.args) + anon_handle_list_channels(self.options, self.session, self.args) + actual = stdout.getvalue() - expected = 'successfully connected to hub\ndefault 3\ntest 2\n' + expected = 'default 3\ntest 2\n' self.assertMultiLineEqual(actual, expected) + activate_session_mock.assert_called_once_with(self.session, self.options) diff --git a/tests/test_cli/test_list_commands.py b/tests/test_cli/test_list_commands.py index a26d1a6..49a9140 100644 --- a/tests/test_cli/test_list_commands.py +++ b/tests/test_cli/test_list_commands.py @@ -1,8 +1,8 @@ from __future__ import absolute_import import mock import os -import unittest import six +import unittest from . import loadcli cli = loadcli.cli diff --git a/tests/test_cli/test_maven_build.py b/tests/test_cli/test_maven_build.py index c3a0b07..7b01eb8 100644 --- a/tests/test_cli/test_maven_build.py +++ b/tests/test_cli/test_maven_build.py @@ -1,15 +1,13 @@ from __future__ import absolute_import -import unittest -import os -import sys import mock -import six - -from . import loadcli import optparse +import os +import six +import sys +import unittest -cli = loadcli.cli +from koji_cli.commands import handle_maven_build EMPTY_BUILD_OPTS = { 'specfile': None, @@ -40,13 +38,14 @@ class TestMavenBuild(unittest.TestCase): self.options = mock.MagicMock() self.options.quiet = None self.options.weburl = 'weburl' + self.options.poll_interval = 0 # Mock out the xmlrpc server self.session = mock.MagicMock() @mock.patch('sys.stdout', new_callable=six.StringIO) - @mock.patch('koji_cli.activate_session') - @mock.patch('koji_cli._running_in_bg', return_value=False) - @mock.patch('koji_cli.watch_tasks', return_value=0) + @mock.patch('koji_cli.commands.activate_session') + @mock.patch('koji_cli.commands._running_in_bg', return_value=False) + @mock.patch('koji_cli.commands.watch_tasks', return_value=0) def test_handle_maven_build(self, watch_tasks_mock, running_in_bg_mock, activate_session_mock, stdout): target = 'target' @@ -66,14 +65,14 @@ class TestMavenBuild(unittest.TestCase): # Run it and check immediate output # args: target http://scm # expected: success - rv = cli.handle_maven_build(self.options, self.session, args) + rv = handle_maven_build(self.options, self.session, args) actual = stdout.getvalue() expected = """Created task: 1 Task info: weburl/taskinfo?taskID=1 """ self.assertMultiLineEqual(actual, expected) # Finally, assert that things were called as we expected. - activate_session_mock.assert_called_once_with(self.session) + activate_session_mock.assert_called_once_with(self.session, self.options) self.session.getBuildTarget.assert_called_once_with(target) self.session.getTag.assert_called_once_with(dest_tag_id) self.session.mavenBuild.assert_called_once_with( @@ -81,14 +80,15 @@ Task info: weburl/taskinfo?taskID=1 running_in_bg_mock.assert_called_once() self.session.logout.assert_called() watch_tasks_mock.assert_called_once_with( - self.session, [task_id], quiet=self.options.quiet) + self.session, [task_id], quiet=self.options.quiet, + poll_interval=self.options.poll_interval) self.assertEqual(rv, 0) @mock.patch('sys.stdout', new_callable=six.StringIO) @mock.patch('sys.stderr', new_callable=six.StringIO) - @mock.patch('koji_cli.activate_session') - @mock.patch('koji_cli._running_in_bg', return_value=False) - @mock.patch('koji_cli.watch_tasks', return_value=0) + @mock.patch('koji_cli.commands.activate_session') + @mock.patch('koji_cli.commands._running_in_bg', return_value=False) + @mock.patch('koji_cli.commands.watch_tasks', return_value=0) def test_handle_maven_build_no_arg( self, watch_tasks_mock, @@ -101,7 +101,7 @@ Task info: weburl/taskinfo?taskID=1 # Run it and check immediate output with self.assertRaises(SystemExit) as cm: - cli.handle_maven_build(self.options, self.session, args) + handle_maven_build(self.options, self.session, args) actual_stdout = stdout.getvalue() actual_stderr = stderr.getvalue() expected_stdout = '' @@ -126,9 +126,9 @@ Task info: weburl/taskinfo?taskID=1 @mock.patch('sys.stdout', new_callable=six.StringIO) @mock.patch('sys.stderr', new_callable=six.StringIO) - @mock.patch('koji_cli.activate_session') - @mock.patch('koji_cli._running_in_bg', return_value=False) - @mock.patch('koji_cli.watch_tasks', return_value=0) + @mock.patch('koji_cli.commands.activate_session') + @mock.patch('koji_cli.commands._running_in_bg', return_value=False) + @mock.patch('koji_cli.commands.watch_tasks', return_value=0) def test_handle_maven_build_no_arg_with_ini( self, watch_tasks_mock, @@ -141,7 +141,7 @@ Task info: weburl/taskinfo?taskID=1 # Run it and check immediate output with self.assertRaises(SystemExit) as cm: - cli.handle_maven_build(self.options, self.session, args) + handle_maven_build(self.options, self.session, args) actual_stdout = stdout.getvalue() actual_stderr = stderr.getvalue() expected_stdout = '' @@ -166,9 +166,9 @@ Task info: weburl/taskinfo?taskID=1 @mock.patch('sys.stdout', new_callable=six.StringIO) @mock.patch('sys.stderr', new_callable=six.StringIO) - @mock.patch('koji_cli.activate_session') - @mock.patch('koji_cli._running_in_bg', return_value=False) - @mock.patch('koji_cli.watch_tasks', return_value=0) + @mock.patch('koji_cli.commands.activate_session') + @mock.patch('koji_cli.commands._running_in_bg', return_value=False) + @mock.patch('koji_cli.commands.watch_tasks', return_value=0) def test_handle_maven_build_help( self, watch_tasks_mock, @@ -181,7 +181,7 @@ Task info: weburl/taskinfo?taskID=1 # Run it and check immediate output with self.assertRaises(SystemExit) as cm: - cli.handle_maven_build(self.options, self.session, args) + handle_maven_build(self.options, self.session, args) actual_stdout = stdout.getvalue() actual_stderr = stderr.getvalue() expected_stdout = """Usage: %s maven-build [options] target URL @@ -233,9 +233,9 @@ Options: self.assertEqual(cm.exception.code, 0) @mock.patch('sys.stderr', new_callable=six.StringIO) - @mock.patch('koji_cli.activate_session') - @mock.patch('koji_cli._running_in_bg', return_value=False) - @mock.patch('koji_cli.watch_tasks', return_value=0) + @mock.patch('koji_cli.commands.activate_session') + @mock.patch('koji_cli.commands._running_in_bg', return_value=False) + @mock.patch('koji_cli.commands.watch_tasks', return_value=0) def test_handle_maven_build_target_not_found( self, watch_tasks_mock, @@ -254,7 +254,7 @@ Options: # args: target http://scm # expected: failed, target not found with self.assertRaises(SystemExit) as cm: - cli.handle_maven_build(self.options, self.session, args) + handle_maven_build(self.options, self.session, args) actual = stderr.getvalue() expected = """Usage: %s maven-build [options] target URL %s maven-build --ini=CONFIG... [options] target @@ -264,7 +264,7 @@ Options: """ % (progname, progname, progname) self.assertMultiLineEqual(actual, expected) # Finally, assert that things were called as we expected. - activate_session_mock.assert_called_once_with(self.session) + activate_session_mock.assert_called_once_with(self.session, self.options) self.session.getBuildTarget.assert_called_once_with(target) self.session.getTag.assert_not_called() running_in_bg_mock.assert_not_called() @@ -274,9 +274,9 @@ Options: self.assertEqual(cm.exception.code, 2) @mock.patch('sys.stderr', new_callable=six.StringIO) - @mock.patch('koji_cli.activate_session') - @mock.patch('koji_cli._running_in_bg', return_value=False) - @mock.patch('koji_cli.watch_tasks', return_value=0) + @mock.patch('koji_cli.commands.activate_session') + @mock.patch('koji_cli.commands._running_in_bg', return_value=False) + @mock.patch('koji_cli.commands.watch_tasks', return_value=0) def test_handle_build_dest_tag_not_found( self, watch_tasks_mock, @@ -299,7 +299,7 @@ Options: # args: target http://scm # expected: failed, dest_tag not found with self.assertRaises(SystemExit) as cm: - cli.handle_maven_build(self.options, self.session, args) + handle_maven_build(self.options, self.session, args) actual = stderr.getvalue() expected = """Usage: %s maven-build [options] target URL %s maven-build --ini=CONFIG... [options] target @@ -309,7 +309,7 @@ Options: """ % (progname, progname, progname) self.assertMultiLineEqual(actual, expected) # Finally, assert that things were called as we expected. - activate_session_mock.assert_called_once_with(self.session) + activate_session_mock.assert_called_once_with(self.session, self.options) self.session.getBuildTarget.assert_called_once_with(target) self.session.getTag.assert_called_once_with(dest_tag_id) running_in_bg_mock.assert_not_called() @@ -319,9 +319,9 @@ Options: self.assertEqual(cm.exception.code, 2) @mock.patch('sys.stderr', new_callable=six.StringIO) - @mock.patch('koji_cli.activate_session') - @mock.patch('koji_cli._running_in_bg', return_value=False) - @mock.patch('koji_cli.watch_tasks', return_value=0) + @mock.patch('koji_cli.commands.activate_session') + @mock.patch('koji_cli.commands._running_in_bg', return_value=False) + @mock.patch('koji_cli.commands.watch_tasks', return_value=0) def test_handle_build_dest_tag_locked( self, watch_tasks_mock, @@ -344,7 +344,7 @@ Options: # args: target http://scm # expected: failed, dest_tag is locked with self.assertRaises(SystemExit) as cm: - cli.handle_maven_build(self.options, self.session, args) + handle_maven_build(self.options, self.session, args) actual = stderr.getvalue() expected = """Usage: %s maven-build [options] target URL %s maven-build --ini=CONFIG... [options] target @@ -354,7 +354,7 @@ Options: """ % (progname, progname, progname) self.assertMultiLineEqual(actual, expected) # Finally, assert that things were called as we expected. - activate_session_mock.assert_called_once_with(self.session) + activate_session_mock.assert_called_once_with(self.session, self.options) self.session.getBuildTarget.assert_called_once_with(target) self.session.getTag.assert_called_once_with(dest_tag_id) running_in_bg_mock.assert_not_called() @@ -365,7 +365,7 @@ Options: @mock.patch('sys.stderr', new_callable=six.StringIO) @mock.patch('sys.stdout', new_callable=six.StringIO) - @mock.patch('koji_cli.activate_session') + @mock.patch('koji_cli.commands.activate_session') @mock.patch( 'koji.util.parse_maven_param', return_value={ @@ -375,8 +375,8 @@ Options: 'pkg1', 'pkg2']}}) @mock.patch('koji.util.maven_opts') - @mock.patch('koji_cli._running_in_bg', return_value=False) - @mock.patch('koji_cli.watch_tasks', return_value=0) + @mock.patch('koji_cli.commands._running_in_bg', return_value=False) + @mock.patch('koji_cli.commands.watch_tasks', return_value=0) def test_handle_build_inis( self, watch_tasks_mock, @@ -415,14 +415,14 @@ Options: # Run it and check immediate output # args: --ini=config1.ini --ini=config2.ini --section=section target # expected: success - rv = cli.handle_maven_build(self.options, self.session, args) + rv = handle_maven_build(self.options, self.session, args) actual = stdout.getvalue() expected = """Created task: 1 Task info: weburl/taskinfo?taskID=1 """ self.assertMultiLineEqual(actual, expected) # Finally, assert that things were called as we expected. - activate_session_mock.assert_called_once_with(self.session) + activate_session_mock.assert_called_once_with(self.session, self.options) self.session.getBuildTarget.assert_called_once_with(target) self.session.getTag.assert_called_once_with(dest_tag_id) parse_maven_param_mock.assert_called_once_with( @@ -433,7 +433,8 @@ Task info: weburl/taskinfo?taskID=1 running_in_bg_mock.assert_called_once() self.session.logout.assert_called_once() watch_tasks_mock.assert_called_once_with( - self.session, [task_id], quiet=build_opts.quiet) + self.session, [task_id], quiet=build_opts.quiet, + poll_interval=self.options.poll_interval) self.assertEqual(rv, 0) stdout.seek(0) @@ -452,7 +453,7 @@ Task info: weburl/taskinfo?taskID=1 # args: --ini=config1.ini --ini=config2.ini --section=section target # expected: failed, no type == 'maven' found with self.assertRaises(SystemExit) as cm: - cli.handle_maven_build(self.options, self.session, args) + handle_maven_build(self.options, self.session, args) actual = stderr.getvalue() expected = """Usage: %s maven-build [options] target URL %s maven-build --ini=CONFIG... [options] target @@ -480,7 +481,7 @@ Task info: weburl/taskinfo?taskID=1 # args: --ini=config1.ini --ini=config2.ini --section=section target # expected: failed, ValueError raised when parsing .ini files with self.assertRaises(SystemExit) as cm: - cli.handle_maven_build(self.options, self.session, args) + handle_maven_build(self.options, self.session, args) actual = stderr.getvalue() expected = """Usage: %s maven-build [options] target URL %s maven-build --ini=CONFIG... [options] target @@ -497,11 +498,11 @@ Task info: weburl/taskinfo?taskID=1 self.assertEqual(cm.exception.code, 2) @mock.patch('sys.stderr', new_callable=six.StringIO) - @mock.patch('koji_cli.activate_session') + @mock.patch('koji_cli.commands.activate_session') @mock.patch('koji.util.parse_maven_param') @mock.patch('koji.util.maven_opts') - @mock.patch('koji_cli._running_in_bg', return_value=False) - @mock.patch('koji_cli.watch_tasks', return_value=0) + @mock.patch('koji_cli.commands._running_in_bg', return_value=False) + @mock.patch('koji_cli.commands.watch_tasks', return_value=0) def test_handle_build_invalid_scm( self, watch_tasks_mock, @@ -527,7 +528,7 @@ Task info: weburl/taskinfo?taskID=1 # args: target badscm # expected: failed, scm is invalid with self.assertRaises(SystemExit) as cm: - cli.handle_maven_build(self.options, self.session, args) + handle_maven_build(self.options, self.session, args) actual = stderr.getvalue() expected = """Usage: %s maven-build [options] target URL %s maven-build --ini=CONFIG... [options] target @@ -537,7 +538,7 @@ Task info: weburl/taskinfo?taskID=1 """ % (progname, progname, progname) self.assertMultiLineEqual(actual, expected) # Finally, assert that things were called as we expected. - activate_session_mock.assert_called_once_with(self.session) + activate_session_mock.assert_called_once_with(self.session, self.options) self.session.getBuildTarget.assert_called_once_with(target) self.session.getTag.assert_called_once_with(dest_tag_id) parse_maven_param_mock.assert_not_called() @@ -549,11 +550,11 @@ Task info: weburl/taskinfo?taskID=1 self.assertEqual(cm.exception.code, 2) @mock.patch('sys.stdout', new_callable=six.StringIO) - @mock.patch('koji_cli.activate_session') + @mock.patch('koji_cli.commands.activate_session') @mock.patch('koji.util.parse_maven_param') @mock.patch('koji.util.maven_opts', return_value={}) - @mock.patch('koji_cli._running_in_bg', return_value=False) - @mock.patch('koji_cli.watch_tasks', return_value=0) + @mock.patch('koji_cli.commands._running_in_bg', return_value=False) + @mock.patch('koji_cli.commands.watch_tasks', return_value=0) def test_handle_build_other_params( self, watch_tasks_mock, @@ -585,14 +586,14 @@ Task info: weburl/taskinfo?taskID=1 # Run it and check immediate output # args: --debug --skip-tag --background target http://scm # expected: success - rv = cli.handle_maven_build(self.options, self.session, args) + rv = handle_maven_build(self.options, self.session, args) actual = stdout.getvalue() expected = """Created task: 1 Task info: weburl/taskinfo?taskID=1 """ self.assertMultiLineEqual(actual, expected) # Finally, assert that things were called as we expected. - activate_session_mock.assert_called_once_with(self.session) + activate_session_mock.assert_called_once_with(self.session, self.options) self.session.getBuildTarget.assert_called_once_with(target) self.session.getTag.assert_called_once_with(dest_tag_id) parse_maven_param_mock.assert_not_called() @@ -602,7 +603,8 @@ Task info: weburl/taskinfo?taskID=1 source, target, opts, priority=priority) self.session.logout.assert_called_once() watch_tasks_mock.assert_called_once_with( - self.session, [task_id], quiet=build_opts['quiet']) + self.session, [task_id], quiet=build_opts['quiet'], + poll_interval=self.options.poll_interval) self.assertEqual(rv, 0) stdout.seek(0) @@ -624,16 +626,16 @@ Task info: weburl/taskinfo?taskID=1 # Run it and check immediate output # args: --debug --skip-tag --background -Mtest -Mtest2=val target http://scm # expected: success - cli.handle_maven_build(self.options, self.session, args) + handle_maven_build(self.options, self.session, args) self.assertMultiLineEqual(actual, expected) maven_opts_mock.assert_called_once_with(build_opts, scratch=scratch) self.session.mavenBuild.assert_called_once_with( source, target, opts, priority=priority) @mock.patch('sys.stdout', new_callable=six.StringIO) - @mock.patch('koji_cli.activate_session') - @mock.patch('koji_cli._running_in_bg', return_value=False) - @mock.patch('koji_cli.watch_tasks', return_value=0) + @mock.patch('koji_cli.commands.activate_session') + @mock.patch('koji_cli.commands._running_in_bg', return_value=False) + @mock.patch('koji_cli.commands.watch_tasks', return_value=0) def test_handle_maven_build_quiet( self, watch_tasks_mock, @@ -658,12 +660,12 @@ Task info: weburl/taskinfo?taskID=1 # Run it and check immediate output # args: --quiet target http://scm # expected: success - rv = cli.handle_maven_build(self.options, self.session, args) + rv = handle_maven_build(self.options, self.session, args) actual = stdout.getvalue() expected = '' self.assertMultiLineEqual(actual, expected) # Finally, assert that things were called as we expected. - activate_session_mock.assert_called_once_with(self.session) + activate_session_mock.assert_called_once_with(self.session, self.options) self.session.getBuildTarget.assert_called_once_with(target) self.session.getTag.assert_called_once_with(dest_tag_id) self.session.mavenBuild.assert_called_once_with( @@ -671,13 +673,14 @@ Task info: weburl/taskinfo?taskID=1 running_in_bg_mock.assert_called_once() self.session.logout.assert_called() watch_tasks_mock.assert_called_once_with( - self.session, [task_id], quiet=self.options.quiet) + self.session, [task_id], quiet=self.options.quiet, + poll_interval=self.options.poll_interval) self.assertEqual(rv, 0) @mock.patch('sys.stdout', new_callable=six.StringIO) - @mock.patch('koji_cli.activate_session') - @mock.patch('koji_cli._running_in_bg', return_value=True) - @mock.patch('koji_cli.watch_tasks', return_value=0) + @mock.patch('koji_cli.commands.activate_session') + @mock.patch('koji_cli.commands._running_in_bg', return_value=True) + @mock.patch('koji_cli.commands.watch_tasks', return_value=0) def test_handle_maven_build_quiet( self, watch_tasks_mock, @@ -701,14 +704,14 @@ Task info: weburl/taskinfo?taskID=1 # Run it and check immediate output # args: target http://scm # expected: success - rv = cli.handle_maven_build(self.options, self.session, args) + rv = handle_maven_build(self.options, self.session, args) actual = stdout.getvalue() expected = """Created task: 1 Task info: weburl/taskinfo?taskID=1 """ self.assertMultiLineEqual(actual, expected) # Finally, assert that things were called as we expected. - activate_session_mock.assert_called_once_with(self.session) + activate_session_mock.assert_called_once_with(self.session, self.options) self.session.getBuildTarget.assert_called_once_with(target) self.session.getTag.assert_called_once_with(dest_tag_id) self.session.mavenBuild.assert_called_once_with( @@ -719,11 +722,11 @@ Task info: weburl/taskinfo?taskID=1 self.assertIsNone(rv) @mock.patch('sys.stdout', new_callable=six.StringIO) - @mock.patch('koji_cli.activate_session') + @mock.patch('koji_cli.commands.activate_session') @mock.patch('koji.util.parse_maven_param') @mock.patch('koji.util.maven_opts', return_value={}) - @mock.patch('koji_cli._running_in_bg', return_value=False) - @mock.patch('koji_cli.watch_tasks', return_value=0) + @mock.patch('koji_cli.commands._running_in_bg', return_value=False) + @mock.patch('koji_cli.commands.watch_tasks', return_value=0) def test_handle_maven_build_nowait( self, watch_tasks_mock, @@ -752,14 +755,14 @@ Task info: weburl/taskinfo?taskID=1 # Run it and check immediate output # args: target http://scm # expected: success - rv = cli.handle_maven_build(self.options, self.session, args) + rv = handle_maven_build(self.options, self.session, args) actual = stdout.getvalue() expected = """Created task: 1 Task info: weburl/taskinfo?taskID=1 """ self.assertMultiLineEqual(actual, expected) # Finally, assert that things were called as we expected. - activate_session_mock.assert_called_once_with(self.session) + activate_session_mock.assert_called_once_with(self.session, self.options) self.session.getBuildTarget.assert_called_once_with(target) self.session.getTag.assert_called_once_with(dest_tag_id) parse_maven_param_mock.assert_not_called() diff --git a/tests/test_cli/test_remove_channel.py b/tests/test_cli/test_remove_channel.py index 8967158..9a16a2f 100644 --- a/tests/test_cli/test_remove_channel.py +++ b/tests/test_cli/test_remove_channel.py @@ -1,14 +1,11 @@ from __future__ import absolute_import -import unittest - -import os -import sys import mock +import os import six +import sys +import unittest -from . import loadcli - -cli = loadcli.cli +from koji_cli.commands import handle_remove_channel class TestRemoveChannel(unittest.TestCase): @@ -17,7 +14,7 @@ class TestRemoveChannel(unittest.TestCase): maxDiff = None @mock.patch('sys.stdout', new_callable=six.StringIO) - @mock.patch('koji_cli.activate_session') + @mock.patch('koji_cli.commands.activate_session') def test_handle_remove_channel(self, activate_session_mock, stdout): channel = 'channel' channel_info = mock.ANY @@ -31,18 +28,18 @@ class TestRemoveChannel(unittest.TestCase): # Run it and check immediate output # args: channel # expected: success - rv = cli.handle_remove_channel(options, session, args) + rv = handle_remove_channel(options, session, args) actual = stdout.getvalue() expected = '' self.assertMultiLineEqual(actual, expected) # Finally, assert that things were called as we expected. - activate_session_mock.assert_called_once_with(session) + activate_session_mock.assert_called_once_with(session, options) session.getChannel.assert_called_once_with(channel) session.removeChannel.assert_called_once_with(channel, force=None) self.assertNotEqual(rv, 1) @mock.patch('sys.stdout', new_callable=six.StringIO) - @mock.patch('koji_cli.activate_session') + @mock.patch('koji_cli.commands.activate_session') def test_handle_remove_channel_force(self, activate_session_mock, stdout): channel = 'channel' channel_info = mock.ANY @@ -57,18 +54,18 @@ class TestRemoveChannel(unittest.TestCase): # Run it and check immediate output # args: --force, channel # expected: success - rv = cli.handle_remove_channel(options, session, args) + rv = handle_remove_channel(options, session, args) actual = stdout.getvalue() expected = '' self.assertMultiLineEqual(actual, expected) # Finally, assert that things were called as we expected. - activate_session_mock.assert_called_once_with(session) + activate_session_mock.assert_called_once_with(session, options) session.getChannel.assert_called_once_with(channel) session.removeChannel.assert_called_once_with(channel, force=True) self.assertNotEqual(rv, 1) @mock.patch('sys.stdout', new_callable=six.StringIO) - @mock.patch('koji_cli.activate_session') + @mock.patch('koji_cli.commands.activate_session') def test_handle_remove_channel_no_channel( self, activate_session_mock, stdout): channel = 'channel' @@ -83,19 +80,19 @@ class TestRemoveChannel(unittest.TestCase): # Run it and check immediate output # args: channel # expected: failed: no such channel - rv = cli.handle_remove_channel(options, session, args) + rv = handle_remove_channel(options, session, args) actual = stdout.getvalue() expected = 'No such channel: channel\n' self.assertMultiLineEqual(actual, expected) # Finally, assert that things were called as we expected. - activate_session_mock.assert_called_once_with(session) + activate_session_mock.assert_called_once_with(session, options) session.getChannel.assert_called_once_with(channel) session.removeChannel.assert_not_called() self.assertEqual(rv, 1) @mock.patch('sys.stdout', new_callable=six.StringIO) @mock.patch('sys.stderr', new_callable=six.StringIO) - @mock.patch('koji_cli.activate_session') + @mock.patch('koji_cli.commands.activate_session') def test_handle_remove_channel_help( self, activate_session_mock, stderr, stdout): args = [] @@ -107,7 +104,7 @@ class TestRemoveChannel(unittest.TestCase): # Run it and check immediate output with self.assertRaises(SystemExit) as cm: - cli.handle_remove_channel(options, session, args) + handle_remove_channel(options, session, args) actual_stdout = stdout.getvalue() actual_stderr = stderr.getvalue() expected_stdout = '' diff --git a/tests/test_cli/test_remove_host_from_channel.py b/tests/test_cli/test_remove_host_from_channel.py index 68eb508..b44b083 100644 --- a/tests/test_cli/test_remove_host_from_channel.py +++ b/tests/test_cli/test_remove_host_from_channel.py @@ -1,15 +1,11 @@ from __future__ import absolute_import -import unittest - -import os -import sys import mock +import os import six +import sys +import unittest -from . import loadcli - -cli = loadcli.cli - +from koji_cli.commands import handle_remove_host_from_channel class TestRemoveHostFromChannel(unittest.TestCase): @@ -17,7 +13,7 @@ class TestRemoveHostFromChannel(unittest.TestCase): maxDiff = None @mock.patch('sys.stdout', new_callable=six.StringIO) - @mock.patch('koji_cli.activate_session') + @mock.patch('koji_cli.commands.activate_session') def test_handle_remove_host_from_channel( self, activate_session_mock, stdout): host = 'host' @@ -35,19 +31,19 @@ class TestRemoveHostFromChannel(unittest.TestCase): # Run it and check immediate output # args: host, channel # expected: success - rv = cli.handle_remove_host_from_channel(options, session, args) + rv = handle_remove_host_from_channel(options, session, args) actual = stdout.getvalue() expected = '' self.assertMultiLineEqual(actual, expected) # Finally, assert that things were called as we expected. - activate_session_mock.assert_called_once_with(session) + activate_session_mock.assert_called_once_with(session, options) session.getHost.assert_called_once_with(host) session.listChannels.assert_called_once_with(host_info['id']) session.removeHostFromChannel.assert_called_once_with(host, channel) self.assertNotEqual(rv, 1) @mock.patch('sys.stdout', new_callable=six.StringIO) - @mock.patch('koji_cli.activate_session') + @mock.patch('koji_cli.commands.activate_session') def test_handle_remove_host_from_channel_no_host( self, activate_session_mock, stdout): host = 'host' @@ -63,19 +59,19 @@ class TestRemoveHostFromChannel(unittest.TestCase): # Run it and check immediate output # args: host, channel # expected: failed: no such host - rv = cli.handle_remove_host_from_channel(options, session, args) + rv = handle_remove_host_from_channel(options, session, args) actual = stdout.getvalue() expected = 'No such host: host\n' self.assertMultiLineEqual(actual, expected) # Finally, assert that things were called as we expected. - activate_session_mock.assert_called_once_with(session) + activate_session_mock.assert_called_once_with(session, options) session.getHost.assert_called_once_with(host) session.listChannels.assert_not_called() session.removeHostFromChannel.assert_not_called() self.assertEqual(rv, 1) @mock.patch('sys.stdout', new_callable=six.StringIO) - @mock.patch('koji_cli.activate_session') + @mock.patch('koji_cli.commands.activate_session') def test_handle_remove_host_from_channel_not_a_member( self, activate_session_mock, stdout): host = 'host' @@ -94,12 +90,12 @@ class TestRemoveHostFromChannel(unittest.TestCase): # Run it and check immediate output # args: host, channel # expected: success: host isn't belong to channel - rv = cli.handle_remove_host_from_channel(options, session, args) + rv = handle_remove_host_from_channel(options, session, args) actual = stdout.getvalue() expected = 'Host host is not a member of channel channel\n' self.assertMultiLineEqual(actual, expected) # Finally, assert that things were called as we expected. - activate_session_mock.assert_called_once_with(session) + activate_session_mock.assert_called_once_with(session, options) session.getHost.assert_called_once_with(host) session.listChannels.assert_called_once_with(host_info['id']) session.removeHostFromChannel.assert_not_called() @@ -107,7 +103,7 @@ class TestRemoveHostFromChannel(unittest.TestCase): @mock.patch('sys.stdout', new_callable=six.StringIO) @mock.patch('sys.stderr', new_callable=six.StringIO) - @mock.patch('koji_cli.activate_session') + @mock.patch('koji_cli.commands.activate_session') def test_handle_remove_host_from_channel_help( self, activate_session_mock, stderr, stdout): args = [] @@ -121,7 +117,7 @@ class TestRemoveHostFromChannel(unittest.TestCase): # args: _empty_ # expected: failed, help msg shows with self.assertRaises(SystemExit) as cm: - cli.handle_remove_host_from_channel(options, session, args) + handle_remove_host_from_channel(options, session, args) actual_stdout = stdout.getvalue() actual_stderr = stderr.getvalue() expected_stdout = '' diff --git a/tests/test_cli/test_remove_pkg.py b/tests/test_cli/test_remove_pkg.py index e21fb6f..f6b6d91 100644 --- a/tests/test_cli/test_remove_pkg.py +++ b/tests/test_cli/test_remove_pkg.py @@ -1,17 +1,13 @@ from __future__ import absolute_import -import unittest - - -import os -import sys import mock +import os import six +import sys +import unittest from mock import call -from . import loadcli - -cli = loadcli.cli +from koji_cli.commands import handle_remove_pkg class TestRemovePkg(unittest.TestCase): @@ -19,7 +15,7 @@ class TestRemovePkg(unittest.TestCase): maxDiff = None @mock.patch('sys.stdout', new_callable=six.StringIO) - @mock.patch('koji_cli.activate_session') + @mock.patch('koji_cli.commands.activate_session') def test_handle_remove_pkg(self, activate_session_mock, stdout): tag = 'tag' dsttag = {'name': tag, 'id': 1} @@ -37,12 +33,12 @@ class TestRemovePkg(unittest.TestCase): # Run it and check immediate output # args: tag, package # expected: success - rv = cli.handle_remove_pkg(options, session, args) + rv = handle_remove_pkg(options, session, args) actual = stdout.getvalue() expected = '' self.assertMultiLineEqual(actual, expected) # Finally, assert that things were called as we expected. - activate_session_mock.assert_called_once_with(session) + activate_session_mock.assert_called_once_with(session, options) session.getTag.assert_called_once_with(tag) session.listPackages.assert_called_once_with( tagID=dsttag['id']) @@ -52,7 +48,7 @@ class TestRemovePkg(unittest.TestCase): self.assertNotEqual(rv, 1) @mock.patch('sys.stdout', new_callable=six.StringIO) - @mock.patch('koji_cli.activate_session') + @mock.patch('koji_cli.commands.activate_session') def test_handle_remove_pkg_multi_pkg(self, activate_session_mock, stdout): tag = 'tag' dsttag = {'name': tag, 'id': 1} @@ -74,12 +70,12 @@ class TestRemovePkg(unittest.TestCase): # Run it and check immediate output # args: tag, package1, package2, package3 # expected: success - rv = cli.handle_remove_pkg(options, session, args) + rv = handle_remove_pkg(options, session, args) actual = stdout.getvalue() expected = '' self.assertMultiLineEqual(actual, expected) # Finally, assert that things were called as we expected. - activate_session_mock.assert_called_once_with(session) + activate_session_mock.assert_called_once_with(session, options) self.assertEqual( session.mock_calls, [ call.getTag(tag), call.listPackages( @@ -91,7 +87,7 @@ class TestRemovePkg(unittest.TestCase): self.assertNotEqual(rv, 1) @mock.patch('sys.stdout', new_callable=six.StringIO) - @mock.patch('koji_cli.activate_session') + @mock.patch('koji_cli.commands.activate_session') def test_handle_remove_pkg_force(self, activate_session_mock, stdout): tag = 'tag' dsttag = {'name': tag, 'id': 1} @@ -113,12 +109,12 @@ class TestRemovePkg(unittest.TestCase): # Run it and check immediate output # args: --force, tag, package1, package2, package3 # expected: success - rv = cli.handle_remove_pkg(options, session, args) + rv = handle_remove_pkg(options, session, args) actual = stdout.getvalue() expected = '' self.assertMultiLineEqual(actual, expected) # Finally, assert that things were called as we expected. - activate_session_mock.assert_called_once_with(session) + activate_session_mock.assert_called_once_with(session, options) self.assertEqual( session.mock_calls, [ call.getTag(tag), call.listPackages( @@ -130,7 +126,7 @@ class TestRemovePkg(unittest.TestCase): self.assertNotEqual(rv, 1) @mock.patch('sys.stdout', new_callable=six.StringIO) - @mock.patch('koji_cli.activate_session') + @mock.patch('koji_cli.commands.activate_session') def test_handle_remove_pkg_no_package(self, activate_session_mock, stdout): tag = 'tag' dsttag = {'name': tag, 'id': 1} @@ -149,12 +145,12 @@ class TestRemovePkg(unittest.TestCase): # Run it and check immediate output # args: tag, package1, package2, package3 # expected: failed: can not find package2 under tag - rv = cli.handle_remove_pkg(options, session, args) + rv = handle_remove_pkg(options, session, args) actual = stdout.getvalue() expected = 'Package package2 is not in tag tag\n' self.assertMultiLineEqual(actual, expected) # Finally, assert that things were called as we expected. - activate_session_mock.assert_called_once_with(session) + activate_session_mock.assert_called_once_with(session, options) session.getTag.assert_called_once_with(tag) session.listPackages.assert_called_once_with( tagID=dsttag['id']) @@ -163,7 +159,7 @@ class TestRemovePkg(unittest.TestCase): self.assertEqual(rv, 1) @mock.patch('sys.stdout', new_callable=six.StringIO) - @mock.patch('koji_cli.activate_session') + @mock.patch('koji_cli.commands.activate_session') def test_handle_remove_pkg_tag_no_exists( self, activate_session_mock, stdout): tag = 'tag' @@ -179,12 +175,12 @@ class TestRemovePkg(unittest.TestCase): # Run it and check immediate output # args: tag, package1, package2, package3 # expected: failed: tag does not exist - rv = cli.handle_remove_pkg(options, session, args) + rv = handle_remove_pkg(options, session, args) actual = stdout.getvalue() expected = 'No such tag: tag\n' self.assertMultiLineEqual(actual, expected) # Finally, assert that things were called as we expected. - activate_session_mock.assert_called_once_with(session) + activate_session_mock.assert_called_once_with(session, options) session.getTag.assert_called_once_with(tag) session.listPackages.assert_not_called() session.packageListRemove.assert_not_called() @@ -192,7 +188,7 @@ class TestRemovePkg(unittest.TestCase): @mock.patch('sys.stdout', new_callable=six.StringIO) @mock.patch('sys.stderr', new_callable=six.StringIO) - @mock.patch('koji_cli.activate_session') + @mock.patch('koji_cli.commands.activate_session') def test_handle_remove_pkg_help( self, activate_session_mock, stderr, stdout): args = [] @@ -205,7 +201,7 @@ class TestRemovePkg(unittest.TestCase): # Run it and check immediate output with self.assertRaises(SystemExit) as cm: - cli.handle_remove_pkg(options, session, args) + handle_remove_pkg(options, session, args) actual_stdout = stdout.getvalue() actual_stderr = stderr.getvalue() expected_stdout = '' diff --git a/tests/test_cli/test_rename_channel.py b/tests/test_cli/test_rename_channel.py index 9b0402d..65a7c91 100644 --- a/tests/test_cli/test_rename_channel.py +++ b/tests/test_cli/test_rename_channel.py @@ -1,14 +1,11 @@ from __future__ import absolute_import -import unittest - -import os -import sys import mock +import os import six +import sys +import unittest -from . import loadcli - -cli = loadcli.cli +from koji_cli.commands import handle_rename_channel class TestRenameChannel(unittest.TestCase): @@ -17,7 +14,7 @@ class TestRenameChannel(unittest.TestCase): maxDiff = None @mock.patch('sys.stdout', new_callable=six.StringIO) - @mock.patch('koji_cli.activate_session') + @mock.patch('koji_cli.commands.activate_session') def test_handle_rename_channel(self, activate_session_mock, stdout): old_name = 'old_name' new_name = 'new_name' @@ -32,18 +29,18 @@ class TestRenameChannel(unittest.TestCase): # Run it and check immediate output # args: old_name, new_name # expected: success - rv = cli.handle_rename_channel(options, session, args) + rv = handle_rename_channel(options, session, args) actual = stdout.getvalue() expected = '' self.assertMultiLineEqual(actual, expected) # Finally, assert that things were called as we expected. - activate_session_mock.assert_called_once_with(session) + activate_session_mock.assert_called_once_with(session, options) session.getChannel.assert_called_once_with(old_name) session.renameChannel.assert_called_once_with(old_name, new_name) self.assertNotEqual(rv, 1) @mock.patch('sys.stdout', new_callable=six.StringIO) - @mock.patch('koji_cli.activate_session') + @mock.patch('koji_cli.commands.activate_session') def test_handle_rename_channel_no_channel( self, activate_session_mock, stdout): old_name = 'old_name' @@ -59,19 +56,19 @@ class TestRenameChannel(unittest.TestCase): # Run it and check immediate output # args: old_name, new_name # expected: failed: no such channel - rv = cli.handle_rename_channel(options, session, args) + rv = handle_rename_channel(options, session, args) actual = stdout.getvalue() expected = 'No such channel: old_name\n' self.assertMultiLineEqual(actual, expected) # Finally, assert that things were called as we expected. - activate_session_mock.assert_called_once_with(session) + activate_session_mock.assert_called_once_with(session, options) session.getChannel.assert_called_once_with(old_name) session.renameChannel.assert_not_called() self.assertEqual(rv, 1) @mock.patch('sys.stdout', new_callable=six.StringIO) @mock.patch('sys.stderr', new_callable=six.StringIO) - @mock.patch('koji_cli.activate_session') + @mock.patch('koji_cli.commands.activate_session') def test_handle_rename_channel_help( self, activate_session_mock, stderr, stdout): args = [] @@ -83,7 +80,7 @@ class TestRenameChannel(unittest.TestCase): # Run it and check immediate output with self.assertRaises(SystemExit) as cm: - cli.handle_rename_channel(options, session, args) + handle_rename_channel(options, session, args) actual_stdout = stdout.getvalue() actual_stderr = stderr.getvalue() expected_stdout = '' diff --git a/tests/test_cli/test_running_in_bg.py b/tests/test_cli/test_running_in_bg.py index 64d21d4..69497bd 100644 --- a/tests/test_cli/test_running_in_bg.py +++ b/tests/test_cli/test_running_in_bg.py @@ -3,41 +3,38 @@ import unittest import mock -from . import loadcli - -cli = loadcli.cli - +from koji_cli.lib import _running_in_bg class TestRunningInBg(unittest.TestCase): - @mock.patch('koji_cli.os') + @mock.patch('koji_cli.lib.os') def test_running_in_bg(self, os_mock): os_mock.isatty.return_value = False - self.assertTrue(cli._running_in_bg()) + self.assertTrue(_running_in_bg()) os_mock.isatty.return_value = True os_mock.getpgrp.return_value = 0 os_mock.tcgetpgrp.return_value = 1 - self.assertTrue(cli._running_in_bg()) + self.assertTrue(_running_in_bg()) os_mock.tcgetpgrp.return_value = 0 - self.assertFalse(cli._running_in_bg()) + self.assertFalse(_running_in_bg()) os_mock.reset_mock() os_mock.tcgetpgrp.side_effect = OSError - self.assertTrue(cli._running_in_bg()) + self.assertTrue(_running_in_bg()) os_mock.isatty.assert_called() os_mock.getpgrp.assert_called() os_mock.tcgetpgrp.assert_called() os_mock.reset_mock() os_mock.getpgrp.side_effect = OSError - self.assertTrue(cli._running_in_bg()) + self.assertTrue(_running_in_bg()) os_mock.isatty.assert_called() os_mock.getpgrp.assert_called() os_mock.tcgetpgrp.assert_not_called() os_mock.reset_mock() os_mock.isatty.side_effect = OSError - self.assertTrue(cli._running_in_bg()) + self.assertTrue(_running_in_bg()) os_mock.isatty.assert_called() os_mock.getpgrp.assert_not_called() os_mock.tcgetpgrp.assert_not_called() diff --git a/tests/test_cli/test_runroot.py b/tests/test_cli/test_runroot.py deleted file mode 100644 index 9b1392c..0000000 --- a/tests/test_cli/test_runroot.py +++ /dev/null @@ -1,64 +0,0 @@ -from __future__ import absolute_import -import os -import sys -import unittest -import koji -import six - -import mock - -from . import loadcli -cli = loadcli.cli - - -class TestListCommands(unittest.TestCase): - - def setUp(self): - self.options = mock.MagicMock() - self.session = mock.MagicMock() - self.session.getAPIVersion.return_value = koji.API_VERSION - self.args = mock.MagicMock() - self.original_parser = cli.OptionParser - cli.OptionParser = mock.MagicMock() - self.parser = cli.OptionParser.return_value - cli.options = self.options # globals!!! - - def tearDown(self): - cli.OptionParser = self.original_parser - - # Show long diffs in error output... - maxDiff = None - - @mock.patch('sys.stdout', new_callable=six.StringIO) - def test_handle_runroot(self, stdout): - tag = 'tag' - arch = 'arch' - command = 'command' - arguments = [tag, arch, command] - options = mock.MagicMock() - options.new_chroot = False - self.parser.parse_args.return_value = [options, arguments] - - # Mock out the xmlrpc server - self.session.getTaskInfo.return_value = {'state': 1} - self.session.downloadTaskOutput.return_value = 'task output' - self.session.listTaskOutput.return_value = {'runroot.log': ['DEFAULT']} - self.session.runroot.return_value = 1 - - # Run it and check immediate output - cli.handle_runroot(self.options, self.session, self.args) - actual = stdout.getvalue() - actual = actual.replace('nosetests', 'koji') - expected = 'successfully connected to hub\n1\ntask output' - self.assertMultiLineEqual(actual, expected) - - # Finally, assert that things were called as we expected. - self.session.getTaskInfo.assert_called_once_with(1) - self.session.listTaskOutput.assert_called_once_with(1, all_volumes=True) - self.session.downloadTaskOutput.assert_called_once_with( - 1, 'runroot.log', volume='DEFAULT') - self.session.runroot.assert_called_once_with( - tag, arch, command, repo_id=mock.ANY, weight=mock.ANY, - mounts=mock.ANY, packages=mock.ANY, skip_setarch=mock.ANY, - channel=mock.ANY, - ) diff --git a/tests/test_cli/test_save_failed_tree.py b/tests/test_cli/test_save_failed_tree.py deleted file mode 100644 index cdb8927..0000000 --- a/tests/test_cli/test_save_failed_tree.py +++ /dev/null @@ -1,164 +0,0 @@ -from __future__ import absolute_import -import unittest -import koji -import mock -import six - -from . import loadcli -cli = loadcli.cli - - -class TestSaveFailedTree(unittest.TestCase): - def setUp(self): - self.options = mock.MagicMock() - self.session = mock.MagicMock() - self.args = mock.MagicMock() - self.original_parser = cli.OptionParser - cli.OptionParser = mock.MagicMock() - self.parser = cli.OptionParser.return_value - cli.options = self.options # globals!!! - - def tearDown(self): - cli.OptionParser = self.original_parser - - # Show long diffs in error output... - maxDiff = None - - @mock.patch('koji_cli.activate_session') - def test_handle_save_failed_tree_simple(self, activate_session_mock): - # koji save-failed-tree 123456 - task_id = 123456 - broot_id = 321 - arguments = [task_id] - options = mock.MagicMock() - options.full = False - options.nowait = True - self.parser.parse_args.return_value = [options, arguments] - self.session.getAPIVersion.return_value = koji.API_VERSION - self.session.listBuildroots.return_value = [{'id': 321}] - - # Mock out the xmlrpc server - self.session.saveFailedTree.return_value = 123 - - # Run it and check immediate output - cli.handle_save_failed_tree(self.options, self.session, self.args) - - # Finally, assert that things were called as we expected. - activate_session_mock.assert_called_once_with(self.session) - self.session.listBuildroots.assert_called_once_with(taskID=task_id) - self.session.saveFailedTree.assert_called_once_with(broot_id, options.full) - - @mock.patch('koji_cli.activate_session') - def test_handle_save_failed_tree_buildroots(self, activate_session_mock): - # koji save-failed-tree --buildroot 123456 - broot_id = 321 - arguments = [broot_id] - options = mock.MagicMock() - options.full = False - options.nowait = True - options.mode = "buildroot" - self.parser.parse_args.return_value = [options, arguments] - self.session.getAPIVersion.return_value = koji.API_VERSION - self.session.listBuildroots.return_value = [{'id': 321}] - - # Mock out the xmlrpc server - self.session.saveFailedTree.return_value = 123 - - # Run it and check immediate output - cli.handle_save_failed_tree(self.options, self.session, self.args) - - # Finally, assert that things were called as we expected. - activate_session_mock.assert_called_once_with(self.session) - self.session.listBuildroots.assert_not_called() - self.session.saveFailedTree.assert_called_once_with(broot_id, options.full) - - - @mock.patch('koji_cli.activate_session') - def test_handle_save_failed_tree_full(self, activate_session_mock): - # koji save-failed-tree 123456 --full - task_id = 123456 - broot_id = 321 - arguments = [task_id] - options = mock.MagicMock() - options.full = True - options.nowait = True - self.parser.parse_args.return_value = [options, arguments] - self.session.getAPIVersion.return_value = koji.API_VERSION - self.session.listBuildroots.return_value = [{'id': 321}] - - # Mock out the xmlrpc server - self.session.saveFailedTree.return_value = 123 - - # Run it and check immediate output - cli.handle_save_failed_tree(self.options, self.session, self.args) - - # Finally, assert that things were called as we expected. - activate_session_mock.assert_called_once_with(self.session) - self.session.listBuildroots.assert_called_once_with(taskID=task_id) - self.session.saveFailedTree.assert_called_once_with(broot_id, options.full) - - @mock.patch('koji_cli.activate_session') - @mock.patch('koji_cli.watch_tasks') - def test_handle_save_failed_tree_wait(self, watch_tasks_mock, activate_session_mock): - # koji save-failed-tree 123456 --full - task_id = 123456 - broot_id = 321 - arguments = [task_id] - options = mock.MagicMock() - options.full = True - options.nowait = False - options.quiet = False - self.parser.parse_args.return_value = [options, arguments] - self.session.getAPIVersion.return_value = koji.API_VERSION - self.session.listBuildroots.return_value = [{'id': 321}] - - # Mock out the xmlrpc server - spawned_id = 123 - self.session.saveFailedTree.return_value = spawned_id - - # Run it and check immediate output - cli.handle_save_failed_tree(self.options, self.session, self.args) - - # Finally, assert that things were called as we expected. - self.session.listBuildroots.assert_called_once_with(taskID=task_id) - self.session.saveFailedTree.assert_called_once_with(broot_id, options.full) - activate_session_mock.assert_called_once_with(self.session) - self.session.logout.assert_called_once_with() - watch_tasks_mock.assert_called_once_with(self.session, [spawned_id], - quiet=options.quiet) - - @mock.patch('sys.stdout', new_callable=six.StringIO) - @mock.patch('koji_cli.activate_session') - @mock.patch('koji_cli.watch_tasks') - def test_handle_save_failed_tree_errors(self, watch_tasks_mock, activate_session_mock, stdout): - # koji save-failed-tree 123 456 - arguments = [123, 456] - options = mock.MagicMock() - self.parser.parse_args.return_value = [options, arguments] - self.parser.error.side_effect = Exception() - self.session.getAPIVersion.return_value = koji.API_VERSION - self.session.listBuildroots.return_value = [{'id': 321}] - - self.assertRaises(Exception, cli.handle_save_failed_tree, - self.options, self.session, self.args) - - arguments = ["text"] - self.parser.parse_args.return_value = [options, arguments] - self.assertRaises(Exception, cli.handle_save_failed_tree, self.options, - self.session, self.args) - cli.logger = mock.MagicMock() - - # plugin not installed - arguments = [123] - self.parser.parse_args.return_value = [options, arguments] - self.session.saveFailedTree.side_effect = koji.GenericError("Invalid method") - cli.handle_save_failed_tree(self.options, self.session, self.args) - actual = stdout.getvalue() - self.assertTrue('The save_failed_tree plugin appears to not be installed' in actual) - - # Task which is not FAILED, disabled in config, wrong owner - self.session.saveFailedTree.side_effect = koji.PreBuildError('placeholder') - with self.assertRaises(koji.PreBuildError) as cm: - cli.handle_save_failed_tree(self.options, self.session, self.args) - e = cm.exception - self.assertEqual(e, self.session.saveFailedTree.side_effect) diff --git a/tests/test_cli/test_unique_path.py b/tests/test_cli/test_unique_path.py index ed413b5..247fb3e 100644 --- a/tests/test_cli/test_unique_path.py +++ b/tests/test_cli/test_unique_path.py @@ -1,21 +1,18 @@ from __future__ import absolute_import import unittest - -from . import loadcli from six.moves import range -cli = loadcli.cli - +from koji_cli.lib import _unique_path class TestUniquePath(unittest.TestCase): def test_unique_path(self): for i in range(1000): self.assertNotEqual( - cli._unique_path('prefix'), - cli._unique_path('prefix')) + _unique_path('prefix'), + _unique_path('prefix')) self.assertRegexpMatches( - cli._unique_path('prefix'), + _unique_path('prefix'), '^prefix/\d{10}\.\d{1,7}\.[a-zA-Z]{8}$') if __name__ == '__main__': diff --git a/tests/test_cli/test_upload_progress_callback.py b/tests/test_cli/test_upload_progress_callback.py index 5f0823d..89cb466 100644 --- a/tests/test_cli/test_upload_progress_callback.py +++ b/tests/test_cli/test_upload_progress_callback.py @@ -1,41 +1,37 @@ from __future__ import absolute_import -import unittest import mock -import sys import six +import unittest -from . import loadcli - -cli = loadcli.cli - +from koji_cli.lib import _format_size, _format_secs, _progress_callback class TestUploadProgressCallBack(unittest.TestCase): maxDiff = None def test_format_size(self): - self.assertEqual(cli._format_size(2000000000), '1.86 GiB') - self.assertEqual(cli._format_size(1073741824), '1.00 GiB') - self.assertEqual(cli._format_size(3000000), '2.86 MiB') - self.assertEqual(cli._format_size(1048576), '1.00 MiB') - self.assertEqual(cli._format_size(4000), '3.91 KiB') - self.assertEqual(cli._format_size(1024), '1.00 KiB') - self.assertEqual(cli._format_size(500), '500.00 B') + self.assertEqual(_format_size(2000000000), '1.86 GiB') + self.assertEqual(_format_size(1073741824), '1.00 GiB') + self.assertEqual(_format_size(3000000), '2.86 MiB') + self.assertEqual(_format_size(1048576), '1.00 MiB') + self.assertEqual(_format_size(4000), '3.91 KiB') + self.assertEqual(_format_size(1024), '1.00 KiB') + self.assertEqual(_format_size(500), '500.00 B') def test_format_secs(self): - self.assertEqual(cli._format_secs(0), '00:00:00') - self.assertEqual(cli._format_secs(60), '00:01:00') - self.assertEqual(cli._format_secs(3600), '01:00:00') - self.assertEqual(cli._format_secs(7283294), '2023:08:14') - self.assertEqual(cli._format_secs(1234), '00:20:34') - self.assertEqual(cli._format_secs(4321), '01:12:01') - self.assertEqual(cli._format_secs(4321.567), '01:12:01') + self.assertEqual(_format_secs(0), '00:00:00') + self.assertEqual(_format_secs(60), '00:01:00') + self.assertEqual(_format_secs(3600), '01:00:00') + self.assertEqual(_format_secs(7283294), '2023:08:14') + self.assertEqual(_format_secs(1234), '00:20:34') + self.assertEqual(_format_secs(4321), '01:12:01') + self.assertEqual(_format_secs(4321.567), '01:12:01') @mock.patch('sys.stdout', new_callable=six.StringIO) def test_progress_callback(self, stdout): - cli._progress_callback(12300, 234000, 5670, 80, 900) - cli._progress_callback(45600, 234000, 5670, 0, 900) - cli._progress_callback(234000, 234000, 5670, 80, 900) + _progress_callback(12300, 234000, 5670, 80, 900) + _progress_callback(45600, 234000, 5670, 0, 900) + _progress_callback(234000, 234000, 5670, 80, 900) self.assertMultiLineEqual( stdout.getvalue(), '[= ] 05% 00:15:00 12.01 KiB 70.88 B/sec\r' diff --git a/tests/test_cli/test_watch_tasks.py b/tests/test_cli/test_watch_tasks.py index f967c8a..931cab7 100644 --- a/tests/test_cli/test_watch_tasks.py +++ b/tests/test_cli/test_watch_tasks.py @@ -1,49 +1,38 @@ from __future__ import absolute_import -import unittest - +import mock import os +import six import sys - -import mock +import unittest from mock import call - -from . import loadcli from six.moves import range -import six -cli = loadcli.cli +from koji_cli.lib import watch_tasks class TestWatchTasks(unittest.TestCase): def setUp(self): self.options = mock.MagicMock() - cli.options = self.options self.session = mock.MagicMock(name='sessionMock') self.args = mock.MagicMock() - self.original_parser = cli.OptionParser - cli.OptionParser = mock.MagicMock() - self.parser = cli.OptionParser.return_value - - def tearDown(self): - cli.OptionParser = self.original_parser # Show long diffs in error output... maxDiff = None @mock.patch('sys.stdout', new_callable=six.StringIO) def test_watch_tasks_no_tasklist(self, stdout): - returned = cli.watch_tasks(self.session, []) + returned = watch_tasks(self.session, [], poll_interval=0) actual = stdout.getvalue() expected = "" self.assertIsNone(returned) self.assertEqual(actual, expected) @mock.patch('sys.stdout', new_callable=six.StringIO) - @mock.patch('koji_cli.TaskWatcher') - @mock.patch('koji_cli.display_tasklist_status') - @mock.patch('koji_cli.display_task_results') + @mock.patch('koji_cli.lib.TaskWatcher') + @mock.patch('koji_cli.lib.display_tasklist_status') + @mock.patch('koji_cli.lib.display_task_results') def test_watch_tasks(self, dtrMock, dtsMock, twClzMock, stdout): self.options.poll_interval = 0 manager = mock.MagicMock() @@ -78,7 +67,7 @@ class TestWatchTasks(unittest.TestCase): return rt twClzMock.side_effect = side_effect - rv = cli.watch_tasks(self.session, list(range(2)), quiet=False) + rv = watch_tasks(self.session, list(range(2)), quiet=False, poll_interval=0) actual = stdout.getvalue() self.assertMultiLineEqual( actual, "Watching tasks (this may be safely interrupted)...\n\n") @@ -171,9 +160,9 @@ class TestWatchTasks(unittest.TestCase): ]) @mock.patch('sys.stdout', new_callable=six.StringIO) - @mock.patch('koji_cli.TaskWatcher') - @mock.patch('koji_cli.display_tasklist_status') - @mock.patch('koji_cli.display_task_results') + @mock.patch('koji_cli.lib.TaskWatcher') + @mock.patch('koji_cli.lib.display_tasklist_status') + @mock.patch('koji_cli.lib.display_task_results') def test_watch_tasks_with_keyboardinterrupt( self, dtrMock, dtsMock, twClzMock, stdout): """Raise KeyboardInterrupt inner watch_tasks. @@ -217,7 +206,7 @@ class TestWatchTasks(unittest.TestCase): twClzMock.side_effect = side_effect with self.assertRaises(KeyboardInterrupt): - cli.watch_tasks(self.session, list(range(2)), quiet=False) + watch_tasks(self.session, list(range(2)), quiet=False, poll_interval=0) actual = stdout.getvalue() self.assertMultiLineEqual( diff --git a/tests/test_plugins/load_plugin.py b/tests/test_plugins/load_plugin.py new file mode 100644 index 0000000..dcc3f25 --- /dev/null +++ b/tests/test_plugins/load_plugin.py @@ -0,0 +1,28 @@ +import os +import sys + +def load_plugin(plugin_type, plugin_name): + # We have to do this craziness because 'import koji' is ambiguous. Is it the + # koji module, or the koji cli module. Jump through hoops accordingly. + # https://stackoverflow.com/questions/67631/how-to-import-a-module-given-the-full-path + mod_name = "%s_%s" % (plugin_name, plugin_type) + CLI_FILENAME = os.path.join( + os.path.dirname(__file__), + "../../plugins", + plugin_type, + "%s.py" % plugin_name) + sys.path = [os.path.dirname(CLI_FILENAME), + os.path.join(os.path.dirname(__file__), "../..", plugin_type)] + \ + sys.path + if sys.version_info[0] >= 3: + import importlib.machinery + loader = importlib.machinery.SourceFileLoader(mod_name, CLI_FILENAME) + spec = importlib.util.spec_from_loader(loader.name, loader) + kojid = importlib.util.module_from_spec(spec) + spec.loader.exec_module(kojid) + loader.exec_module(kojid) + sys.modules[mod_name] = kojid + else: + import imp + plugin = imp.load_source(mod_name, CLI_FILENAME) + return plugin diff --git a/tests/test_plugins/test_runroot_cli.py b/tests/test_plugins/test_runroot_cli.py new file mode 100644 index 0000000..905e5dc --- /dev/null +++ b/tests/test_plugins/test_runroot_cli.py @@ -0,0 +1,56 @@ +from __future__ import absolute_import +import mock +import six +import unittest + +import koji +from . import load_plugin + +runroot = load_plugin.load_plugin('cli', 'runroot') + +class TestListCommands(unittest.TestCase): + + def setUp(self): + self.options = mock.MagicMock() + self.session = mock.MagicMock() + self.session.getAPIVersion.return_value = koji.API_VERSION + self.args = mock.MagicMock() + runroot.OptionParser = mock.MagicMock() + self.parser = runroot.OptionParser.return_value + + # Show long diffs in error output... + maxDiff = None + + @mock.patch('sys.stdout', new_callable=six.StringIO) + def test_handle_runroot(self, stdout): + tag = 'tag' + arch = 'arch' + command = 'command' + arguments = [tag, arch, command] + options = mock.MagicMock() + options.new_chroot = False + self.parser.parse_args.return_value = [options, arguments] + + # Mock out the xmlrpc server + self.session.getTaskInfo.return_value = {'state': 1} + self.session.downloadTaskOutput.return_value = 'task output' + self.session.listTaskOutput.return_value = {'runroot.log': ['DEFAULT']} + self.session.runroot.return_value = 1 + + # Run it and check immediate output + runroot.handle_runroot(self.options, self.session, self.args) + actual = stdout.getvalue() + actual = actual.replace('nosetests', 'koji') + expected = 'successfully connected to hub\n1\ntask output' + self.assertMultiLineEqual(actual, expected) + + # Finally, assert that things were called as we expected. + self.session.getTaskInfo.assert_called_once_with(1) + self.session.listTaskOutput.assert_called_once_with(1, all_volumes=True) + self.session.downloadTaskOutput.assert_called_once_with( + 1, 'runroot.log', volume='DEFAULT') + self.session.runroot.assert_called_once_with( + tag, arch, command, repo_id=mock.ANY, weight=mock.ANY, + mounts=mock.ANY, packages=mock.ANY, skip_setarch=mock.ANY, + channel=mock.ANY, + ) diff --git a/tests/test_plugins/test_save_failed_tree_cli.py b/tests/test_plugins/test_save_failed_tree_cli.py new file mode 100644 index 0000000..1160975 --- /dev/null +++ b/tests/test_plugins/test_save_failed_tree_cli.py @@ -0,0 +1,155 @@ +import mock +import StringIO +import unittest + +import koji + +import load_plugin +save_failed_tree = load_plugin.load_plugin('cli', 'save_failed_tree') + + +class TestSaveFailedTree(unittest.TestCase): + def setUp(self): + self.session = mock.MagicMock() + self.args = mock.MagicMock() + self.parser = mock.MagicMock() + save_failed_tree.activate_session = mock.MagicMock() + save_failed_tree.OptionParser = mock.MagicMock() + save_failed_tree.watch_tasks = mock.MagicMock() + self.parser = save_failed_tree.OptionParser.return_value + + # Show long diffs in error output... + maxDiff = None + + def test_handle_save_failed_tree_simple(self ): + # koji save-failed-tree 123456 + task_id = 123456 + broot_id = 321 + arguments = [task_id] + options = mock.MagicMock() + options.full = False + options.nowait = True + self.parser.parse_args.return_value = [options, arguments] + self.session.getAPIVersion.return_value = koji.API_VERSION + self.session.listBuildroots.return_value = [{'id': 321}] + + # Mock out the xmlrpc server + self.session.saveFailedTree.return_value = 123 + + # Run it and check immediate output + save_failed_tree.handle_save_failed_tree(options, self.session, self.args) + + # Finally, assert that things were called as we expected. + save_failed_tree.activate_session.assert_called_once_with(self.session, options) + self.session.listBuildroots.assert_called_once_with(taskID=task_id) + self.session.saveFailedTree.assert_called_once_with(broot_id, options.full) + + def test_handle_save_failed_tree_buildroots(self): + # koji save-failed-tree --buildroot 123456 + broot_id = 321 + arguments = [broot_id] + options = mock.MagicMock() + options.full = False + options.nowait = True + options.mode = "buildroot" + self.parser.parse_args.return_value = [options, arguments] + self.session.getAPIVersion.return_value = koji.API_VERSION + self.session.listBuildroots.return_value = [{'id': 321}] + + # Mock out the xmlrpc server + self.session.saveFailedTree.return_value = 123 + + # Run it and check immediate output + save_failed_tree.handle_save_failed_tree(options, self.session, self.args) + + # Finally, assert that things were called as we expected. + save_failed_tree.activate_session.assert_called_once_with(self.session, options) + self.session.listBuildroots.assert_not_called() + self.session.saveFailedTree.assert_called_once_with(broot_id, options.full) + + + def test_handle_save_failed_tree_full(self): + # koji save-failed-tree 123456 --full + task_id = 123456 + broot_id = 321 + arguments = [task_id] + options = mock.MagicMock() + options.full = True + options.nowait = True + self.parser.parse_args.return_value = [options, arguments] + self.session.getAPIVersion.return_value = koji.API_VERSION + self.session.listBuildroots.return_value = [{'id': 321}] + + # Mock out the xmlrpc server + self.session.saveFailedTree.return_value = 123 + + # Run it and check immediate output + save_failed_tree.handle_save_failed_tree(options, self.session, self.args) + + # Finally, assert that things were called as we expected. + save_failed_tree.activate_session.assert_called_once_with(self.session, options) + self.session.listBuildroots.assert_called_once_with(taskID=task_id) + self.session.saveFailedTree.assert_called_once_with(broot_id, options.full) + + def test_handle_save_failed_tree_wait(self): + # koji save-failed-tree 123456 --full + task_id = 123456 + broot_id = 321 + arguments = [task_id] + options = mock.MagicMock() + options.full = True + options.nowait = False + options.quiet = False + self.parser.parse_args.return_value = [options, arguments] + self.session.getAPIVersion.return_value = koji.API_VERSION + self.session.listBuildroots.return_value = [{'id': 321}] + + # Mock out the xmlrpc server + spawned_id = 123 + self.session.saveFailedTree.return_value = spawned_id + + # Run it and check immediate output + save_failed_tree.handle_save_failed_tree(options, self.session, self.args) + + # Finally, assert that things were called as we expected. + self.session.listBuildroots.assert_called_once_with(taskID=task_id) + self.session.saveFailedTree.assert_called_once_with(broot_id, options.full) + save_failed_tree.activate_session.assert_called_once_with(self.session, options) + self.session.logout.assert_called_once_with() + save_failed_tree.watch_tasks.assert_called_once_with(self.session, [spawned_id], + poll_interval=options.poll_interval, + quiet=options.quiet) + + @mock.patch('sys.stdout', new_callable=StringIO.StringIO) + def test_handle_save_failed_tree_errors(self, stdout): + # koji save-failed-tree 123 456 + arguments = [123, 456] + options = mock.MagicMock() + self.parser.parse_args.return_value = [options, arguments] + self.parser.error.side_effect = Exception() + self.session.getAPIVersion.return_value = koji.API_VERSION + self.session.listBuildroots.return_value = [{'id': 321}] + + self.assertRaises(Exception, save_failed_tree.handle_save_failed_tree, + options, self.session, self.args) + + arguments = ["text"] + self.parser.parse_args.return_value = [options, arguments] + self.assertRaises(Exception, save_failed_tree.handle_save_failed_tree, options, + self.session, self.args) + save_failed_tree.logger = mock.MagicMock() + + # plugin not installed + arguments = [123] + self.parser.parse_args.return_value = [options, arguments] + self.session.saveFailedTree.side_effect = koji.GenericError("Invalid method") + save_failed_tree.handle_save_failed_tree(options, self.session, self.args) + actual = stdout.getvalue() + self.assertTrue('The save_failed_tree plugin appears to not be installed' in actual) + + # Task which is not FAILED, disabled in config, wrong owner + self.session.saveFailedTree.side_effect = koji.PreBuildError('placeholder') + with self.assertRaises(koji.PreBuildError) as cm: + save_failed_tree.handle_save_failed_tree(options, self.session, self.args) + e = cm.exception + self.assertEqual(e, self.session.saveFailedTree.side_effect) From 1c0d5011c79bc3de2fabe9fd82f34d96ab09915d Mon Sep 17 00:00:00 2001 From: Tomas Kopecek Date: Jun 13 2017 09:42:04 +0000 Subject: [PATCH 9/19] Makefile updates --- diff --git a/cli/Makefile b/cli/Makefile index 073b24b..144c19d 100644 --- a/cli/Makefile +++ b/cli/Makefile @@ -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 @@ install: 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 + install -p -m 644 koji.conf $(DESTDIR)/etc/koji.conf mkdir -p $(DESTDIR)/etc/koji.conf.d install -p -m 644 koji.conf $(DESTDIR)/etc/koji.conf diff --git a/cli/koji_cli/Makefile b/cli/koji_cli/Makefile new file mode 100644 index 0000000..42dcdcf --- /dev/null +++ b/cli/koji_cli/Makefile @@ -0,0 +1,28 @@ +PYFILES = $(wildcard *.py) + +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/$(PACKAGE) + +_default: + @echo "nothing to make. try make install" + +clean: + rm -f *.o *.so *.pyc *~ + +install: + @if [ "$(DESTDIR)" = "" ]; then \ + echo " "; \ + echo "ERROR: A destdir is required"; \ + exit 1; \ + fi + + mkdir -p $(DESTDIR)/$(PKGDIR) + for p in $(PYFILES) ; do \ + install -p -m 644 $$p $(DESTDIR)/$(PKGDIR)/$$p; \ + done + + $(PYTHON) -c "import compileall; compileall.compile_dir('$(DESTDIR)/$(PKGDIR)', 1, '$(PYDIR)', 1)" diff --git a/plugins/Makefile b/plugins/Makefile index d4b1861..cddab66 100644 --- a/plugins/Makefile +++ b/plugins/Makefile @@ -1,10 +1,19 @@ PYTHON=python +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 + +CLIPLUGINDIR = $(PKGDIR)/koji_cli_plugins HUBPLUGINDIR = /usr/lib/koji-hub-plugins BUILDERPLUGINDIR = /usr/lib/koji-builder-plugins +CLIFILES = $(wildcard cli/*.py) HUBFILES = $(wildcard hub/*.py) BUILDERFILES = $(wildcard builder/*.py) +CLICONFDIR = /etc/koji/plugins HUBCONFDIR = /etc/koji-hub/plugins BUILDERCONFDIR = /etc/kojid/plugins +CLICONFFILES = $(wildcard cli/*.conf) HUBCONFFILES = $(wildcard hub/*.conf) BUILDERCONFFILES = $(wildcard builder/*.conf) @@ -20,14 +29,23 @@ install: echo "ERROR: A destdir is required"; \ exit 1; \ fi + if [ "$(PYTHON)" == "python" ] ; then \ + mkdir -p $(DESTDIR)/$(HUBPLUGINDIR); \ + mkdir -p $(DESTDIR)/$(BUILDERPLUGINDIR); \ + install -p -m 644 $(HUBFILES) $(DESTDIR)/$(HUBPLUGINDIR); \ + install -p -m 644 $(BUILDERFILES) $(DESTDIR)/$(BUILDERPLUGINDIR); \ + $(PYTHON) -c "import compileall; compileall.compile_dir('$(DESTDIR)/$(HUBPLUGINDIR)', 1, '$(HUBPLUGINDIR)', 1)"; \ + $(PYTHON) -c "import compileall; compileall.compile_dir('$(DESTDIR)/$(BUILDERPLUGINDIR)', 1, '$(BUILDERPLUGINDIR)', 1)"; \ + mkdir -p $(DESTDIR)/$(HUBCONFDIR); \ + mkdir -p $(DESTDIR)/$(BUILDERCONFDIR); \ + install -p -m 644 $(HUBCONFFILES) $(DESTDIR)/$(HUBCONFDIR); \ + install -p -m 644 $(BUILDERCONFFILES) $(DESTDIR)/$(BUILDERCONFDIR); \ + fi - mkdir -p $(DESTDIR)/$(HUBPLUGINDIR) - mkdir -p $(DESTDIR)/$(BUILDERPLUGINDIR) - install -p -m 644 $(HUBFILES) $(DESTDIR)/$(HUBPLUGINDIR) - install -p -m 644 $(BUILDERFILES) $(DESTDIR)/$(BUILDERPLUGINDIR) - $(PYTHON) -c "import compileall; compileall.compile_dir('$(DESTDIR)/$(HUBPLUGINDIR)', 1, '$(HUBPLUGINDIR)', 1)" - $(PYTHON) -c "import compileall; compileall.compile_dir('$(DESTDIR)/$(BUILDERPLUGINDIR)', 1, '$(BUILDERPLUGINDIR)', 1)" - mkdir -p $(DESTDIR)/$(HUBCONFDIR) - mkdir -p $(DESTDIR)/$(BUILDERCONFDIR) - install -p -m 644 $(HUBCONFFILES) $(DESTDIR)/$(HUBCONFDIR) - install -p -m 644 $(BUILDERCONFFILES) $(DESTDIR)/$(BUILDERCONFDIR) + mkdir -p $(DESTDIR)/$(CLIPLUGINDIR) + install -p -m 644 $(CLIFILES) $(DESTDIR)/$(CLIPLUGINDIR) + $(PYTHON) -c "import compileall; compileall.compile_dir('$(DESTDIR)/$(CLIPLUGINDIR)', 1, '$(CLIPLUGINDIR)', 1)" + mkdir -p $(DESTDIR)/$(CLICONFDIR) + ifneq "$(CLICONFFILES)" "" + install -p -m 644 $(CLICONFFILES) $(DESTDIR)/$(CLICONFDIR) + endif From c8db9b9945dfd99c1974022941f619b25667640e Mon Sep 17 00:00:00 2001 From: Tomas Kopecek Date: Jun 13 2017 09:42:04 +0000 Subject: [PATCH 10/19] spec update --- diff --git a/koji.spec b/koji.spec index 52c5832..094e7cf 100644 --- a/koji.spec +++ b/koji.spec @@ -93,6 +93,26 @@ Requires: python%{python3_pkgversion}-six desc %endif +%package -n python2-%{name}-cli-plugins +Summary: Koji client plugins +Group: Applications/Internet +License: LGPLv2 +Requires: %{name} = %{version}-%{release} + +%description -n python2-%{name}-cli-plugins +Plugins to the koji command-line interface + +%if 0%{with python3} +%package -n python3-%{name}-cli-plugins +Summary: Koji client plugins +Group: Applications/Internet +License: LGPLv2 +Requires: %{name} = %{version}-%{release} + +%description -n python3-%{name}-cli-plugins +Plugins to the koji command-line interface +%endif + %package hub Summary: Koji XMLRPC interface Group: Applications/Internet @@ -257,6 +277,10 @@ make DESTDIR=$RPM_BUILD_ROOT %{?install_opt} install %if 0%{with python3} cd koji make DESTDIR=$RPM_BUILD_ROOT PYTHON=python3 %{?install_opt} install +cd ../cli +make DESTDIR=$RPM_BUILD_ROOT PYTHON=python3 %{?install_opt} install +cd ../plugins +make DESTDIR=$RPM_BUILD_ROOT PYTHON=python3 %{?install_opt} install # alter python interpreter in koji CLI sed -i 's/\#\!\/usr\/bin\/python/\#\!\/usr\/bin\/python3/' $RPM_BUILD_ROOT/usr/bin/koji %endif @@ -274,10 +298,28 @@ rm -rf $RPM_BUILD_ROOT %files -n python2-%{name} %defattr(-,root,root) %{python2_sitelib}/%{name} +%{python2_sitelib}/koji_cli %if 0%{with python3} %files -n python%{python3_pkgversion}-koji %{python3_sitelib}/%{name} +%{python3_sitelib}/koji_cli +%endif + +%files -n python2-%{name}-cli-plugins +%defattr(-,root,root) +%{python2_sitelib}/koji_cli_plugins +# we don't have config files for default plugins yet +#%%dir %{_sysconfdir}/koji/plugins +#%%config(noreplace) %{_sysconfdir}/koji/plugins/*.conf + +%if 0%{with python3} +%files -n python%{python3_pkgversion}-%{name}-cli-plugins +%defattr(-,root,root) +%{python3_sitelib}/koji_cli_plugins +# we don't have config files for default plugins yet +#%%dir %{_sysconfdir}/koji/plugins +#%%config(noreplace) %{_sysconfdir}/koji/plugins/*.conf %endif %files hub From df6aadec594453b419d960ce96123cfe81edfe7b Mon Sep 17 00:00:00 2001 From: Tomas Kopecek Date: Jun 13 2017 12:53:39 +0000 Subject: [PATCH 11/19] remove CLI plugins config options --- diff --git a/cli/koji b/cli/koji index 25af487..0ed9645 100755 --- a/cli/koji +++ b/cli/koji @@ -70,16 +70,16 @@ def load_plugins(options): is that system plugins are first, so they can be overriden by user-specified ones with same name.""" logger = logging.getLogger('koji.plugins') - pluginpaths = options.pluginpath.split() 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): - pluginpaths = [syspath] + pluginpaths - tracker = koji.plugin.PluginTracker(path=pluginpaths) - for name in options.plugins.split(): - logger.info('Loading plugin: %s', name) - tracker.load(name) - register_plugin(tracker.get(name)) + tracker = koji.plugin.PluginTracker(path=syspath) + for name in sorted(os.listdir(syspath)): + if not name.endswith('.py'): + continue + logger.info('Loading plugin: %s', name) + tracker.load(name) + register_plugin(tracker.get(name)) def get_options(): @@ -140,7 +140,7 @@ def get_options(): if getattr(options, name, None) is None: setattr(options, name, value) - dir_opts = ('topdir', 'cert', 'serverca', 'pluginpath') + dir_opts = ('topdir', 'cert', 'serverca') for name in dir_opts: # expand paths here, so we don't have to worry about it later value = os.path.expanduser(getattr(options, name)) diff --git a/koji/__init__.py b/koji/__init__.py index afe35a1..9e1e5ce 100644 --- a/koji/__init__.py +++ b/koji/__init__.py @@ -1636,8 +1636,6 @@ def read_config(profile_name, user_config=None): 'authtype': None, 'debug': False, 'debug_xmlrpc': False, - 'pluginpath': '', - 'plugins': '', } result = config_defaults.copy() From 4ef0506b5cc790904b49933ad589e194d785f8d6 Mon Sep 17 00:00:00 2001 From: Tomas Kopecek Date: Jun 13 2017 13:28:43 +0000 Subject: [PATCH 12/19] CLI plugin dev docs --- diff --git a/docs/source/writing_a_plugin.rst b/docs/source/writing_a_plugin.rst index 3611302..69ea1de 100644 --- a/docs/source/writing_a_plugin.rst +++ b/docs/source/writing_a_plugin.rst @@ -188,3 +188,63 @@ tagging a build: :: $ koji tag-build mytag mypkg-1.0-1 + +New command for CLI +------------------- + +When you add new XMLRPC call or just wanted to do some more complicated +things with API, you can benefit from writing a new command for CLI. + +Most simple command would look like this: + +:: + + from koji.plugin import export_cli + + @export_cli + def anon_handle_echo(options, session, args): + usage = _("usage: %prog echo ") + parser = OptionParser(usage=usage) + (opts, args) = parser.parse_args(args) + print(args[0]) + +`@export_cli` is decorator which will register plugin's command with name +derived from name of the function. Function name needs to follow one rule: It +has to start with `anon_handle_` or `handle_`. Rest of the name is name of +the command. First one will not authenticate against hub (user can still override +this behaviour with `--force-auth` or `--mine` options where it is relevant) +- it is simply same as using `--noath` option. Second variant doesn't presume + anything about authentication. + +Compared to this simple example is real usage with API calls. Koji provides +some important functions via client library `koji_cli.lib` which can be +imported and used inside command's function. Feel free to examine, what is +provided there. Some notable examples are: + + * `activate_session(session, options)` - It is needed to authenticate + against hub. Both parameters are same as those passed to handler. + * `watch_tasks(session, tasklist, quiet=False, poll_interval=60)` - It is + the same function used e.g. in `build` command for waiting for spawned + tasks. + * `list_task_output_all_volumes(session, task_id)` - wrapper function for + `listTaskOutput` with different versions of hub. + +Final command has to be saved in python system-wide library path - e.g. in +`/usr/lib/python3.4/site-packages/koji_cli_plugins`. Filename doesn't matter +as all files in this directory are searched for `@export_cli` macros. Note, +that python 3 variant of CLI is looking to different directory than python 2 +one. + +CLI plugins structure will be extended (made configurable and allowing more +than just adding commands - e.g. own authentication methods, etc.) in future. + +Pull requests +^^^^^^^^^^^^^ + +These plugins have to be written in python 2.6+/3.x compatible way. We are +using `six` library to support this, so we will also prefer pull requests +written this way. CLI (and client library) is meant to be fully compatible +with python 3 from koji 1.13. + +Tests are also recommended for PR. For example one see +`tests/test_plugins/test_runroot_cli.py`. From 03ec96803a8f3b3e59577f45eab77ff0280b12f6 Mon Sep 17 00:00:00 2001 From: Tomas Kopecek Date: Jun 13 2017 13:44:38 +0000 Subject: [PATCH 13/19] Fix blank lines (pep8) split_cli.py caused some missing or additional empty lines. Fixed according to pep8. --- diff --git a/cli/koji_cli/commands.py b/cli/koji_cli/commands.py index af8f400..25f5949 100644 --- a/cli/koji_cli/commands.py +++ b/cli/koji_cli/commands.py @@ -46,6 +46,8 @@ def _printable_unicode(s): return s.encode('utf-8') else: return s + + def handle_add_group(goptions, session, args): "[admin] Add a group to a tag" usage = _("usage: %prog add-group ") @@ -76,6 +78,7 @@ def handle_add_group(goptions, session, args): session.groupListAdd(tag, group) + def handle_assign_task(goptions, session, args): "[admin] Assign a task to a host" usage = _('usage: %prog assign-task task_id hostname') @@ -139,6 +142,7 @@ def handle_add_host(goptions, session, args): 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]") @@ -183,6 +187,7 @@ def handle_edit_host(options, session, args): else: print(_("No changes made to %s") % host) + def handle_add_host_to_channel(goptions, session, args): "[admin] Add a host to a channel" usage = _("usage: %prog add-host-to-channel [options] hostname channel") @@ -215,6 +220,7 @@ def handle_add_host_to_channel(goptions, session, args): kwargs['create'] = True session.addHostToChannel(host, channel, **kwargs) + def handle_remove_host_from_channel(goptions, session, args): "[admin] Remove a host from a channel" usage = _("usage: %prog remove-host-from-channel [options] hostname channel") @@ -239,6 +245,7 @@ def handle_remove_host_from_channel(goptions, session, args): session.removeHostFromChannel(host, channel) + def handle_remove_channel(goptions, session, args): "[admin] Remove a channel entirely" usage = _("usage: %prog remove-channel [options] channel") @@ -256,6 +263,7 @@ def handle_remove_channel(goptions, session, args): return 1 session.removeChannel(args[0], force=options.force) + def handle_rename_channel(goptions, session, args): "[admin] Rename a channel" usage = _("usage: %prog rename-channel [options] old-name new-name") @@ -272,6 +280,7 @@ def handle_rename_channel(goptions, session, args): return 1 session.renameChannel(args[0], args[1]) + def handle_add_pkg(goptions, session, args): "[admin] Add a package to the listing for tag" usage = _("usage: %prog add-pkg [options] tag package [package2 ...]") @@ -349,6 +358,7 @@ def handle_block_pkg(goptions, session, args): session.packageListBlock(tag, package) session.multiCall(strict=True) + def handle_remove_pkg(goptions, session, args): "[admin] Remove a package from the listing for tag" usage = _("usage: %prog remove-pkg [options] tag package [package2 ...]") @@ -381,6 +391,8 @@ def handle_remove_pkg(goptions, session, args): for package in args[1:]: session.packageListRemove(tag, package, **opts) session.multiCall(strict=True) + + def handle_build(options, session, args): "[build] Build a package from source" usage = _("usage: %prog build [options] target ") @@ -458,6 +470,7 @@ def handle_build(options, session, args): 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" @@ -534,6 +547,7 @@ def handle_chain_build(options, session, args): return watch_tasks(session, [task_id], quiet=build_opts.quiet, poll_interval=options.poll_interval) + def handle_maven_build(options, session, args): "[build] Build a Maven package from source" usage = _("usage: %prog maven-build [options] target URL") @@ -633,6 +647,7 @@ def handle_maven_build(options, session, args): return watch_tasks(session, [task_id], quiet=build_opts.quiet, poll_interval=options.poll_interval) + 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") @@ -700,6 +715,7 @@ def handle_wrapper_rpm(options, session, args): return watch_tasks(session, [task_id], quiet=options.quiet, poll_interval=options.poll_interval) + 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...") @@ -753,6 +769,7 @@ def handle_maven_chain(options, session, args): return watch_tasks(session, [task_id], quiet=options.quiet, poll_interval=options.poll_interval) + def handle_resubmit(goptions, session, args): """[build] Retry a canceled or failed task, using the same parameter as the original task.""" usage = _("usage: %prog resubmit [options] taskID") @@ -781,6 +798,7 @@ def handle_resubmit(goptions, session, args): return watch_tasks(session, [newID], quiet=options.quiet, poll_interval=options.poll_interval) + def handle_call(goptions, session, args): "Execute an arbitrary XML-RPC call" usage = _("usage: %prog call [options] name [arg...]") @@ -820,6 +838,7 @@ def handle_call(goptions, session, args): else: pprint.pprint(response) + def anon_handle_mock_config(goptions, session, args): "[info] Create a mock config" usage = _("usage: %prog mock-config [options]") @@ -940,6 +959,7 @@ def anon_handle_mock_config(goptions, session, args): else: print(output) + def handle_disable_host(goptions, session, args): "[admin] Mark one or more hosts as disabled" usage = _("usage: %prog disable-host [options] hostname ...") @@ -967,6 +987,7 @@ def handle_disable_host(goptions, session, args): session.editHost(host, comment=options.comment) session.multiCall(strict=True) + def handle_enable_host(goptions, session, args): "[admin] Mark one or more hosts as enabled" usage = _("usage: %prog enable-host [options] hostname ...") @@ -1016,6 +1037,8 @@ def handle_restart_hosts(options, session, args): poll_interval=options.poll_interval) else: return + + def handle_import(goptions, session, args): "[admin] Import externally built RPMs into the database" usage = _("usage: %prog import [options] package [package...]") @@ -1488,6 +1511,7 @@ def handle_prune_signed_copies(options, session, args): builds = [("%(name)s-%(version)s-%(release)s" % binfo, binfo)] total_files = 0 total_space = 0 + def _histline(event_id, x): if event_id == x['revoke_event']: ts = x['revoke_ts'] @@ -1760,6 +1784,7 @@ def handle_prune_signed_copies(options, session, args): print("Files: %i" % total_files) print("Bytes: %i" % total_space) + def handle_set_build_volume(goptions, session, args): "[admin] Move a build to a different volume" usage = _("usage: %prog set-build-volume volume n-v-r [n-v-r ...]") @@ -1791,6 +1816,7 @@ def handle_set_build_volume(goptions, session, args): if options.verbose: print("%s: %s -> %s" % (binfo['nvr'], binfo['volume_name'], volinfo['name'])) + def handle_add_volume(goptions, session, args): "[admin] Add a new storage volume" usage = _("usage: %prog add-volume volume-name") @@ -1808,6 +1834,7 @@ def handle_add_volume(goptions, session, args): volinfo = session.addVolume(name) print("Added volume %(name)s with id %(id)i" % volinfo) + def handle_list_volumes(options, session, args): "[info] List storage volumes" usage = _("usage: %prog list-volumes") @@ -1817,6 +1844,7 @@ def handle_list_volumes(options, session, args): for volinfo in session.listVolumes(): print(volinfo['name']) + def handle_list_permissions(goptions, session, args): "[info] List user permissions" usage = _("usage: %prog list-permissions [options]") @@ -1842,6 +1870,7 @@ def handle_list_permissions(goptions, session, args): for perm in perms: print(perm) + def handle_add_user(goptions, session, args): "[admin] Add a user" usage = _("usage: %prog add-user username [options]") @@ -1863,6 +1892,7 @@ def handle_add_user(goptions, session, args): user_id = session.createUser(username, status=status, krb_principal=options.principal) print("Added user %s (%i)" % (username, user_id)) + def handle_enable_user(goptions, session, args): "[admin] Enable logins by a user" usage = _("usage: %prog enable-user username") @@ -1877,6 +1907,7 @@ def handle_enable_user(goptions, session, args): activate_session(session, goptions) session.enableUser(username) + def handle_disable_user(goptions, session, args): "[admin] Disable logins by a user" usage = _("usage: %prog disable-user username") @@ -1891,6 +1922,7 @@ def handle_disable_user(goptions, session, args): activate_session(session, goptions) session.disableUser(username) + def handle_list_signed(goptions, session, args): "[admin] List signed copies of rpms" usage = _("usage: %prog list-signed [options]") @@ -1963,6 +1995,7 @@ def handle_list_signed(goptions, session, args): continue print(signedpath) + def handle_import_in_place(goptions, session, args): "[admin] Import RPMs that are already in place" usage = _("usage: %prog import-in-place [options] package [package...]") @@ -1985,6 +2018,7 @@ def handle_import_in_place(goptions, session, args): print(_("done")) sys.stdout.flush() + def handle_import_archive(options, session, args): "[admin] Import an archive file and associate it with a build" usage = _("usage: %prog import-archive build-id|n-v-r /path/to/archive...") @@ -2089,6 +2123,7 @@ def handle_import_archive(options, session, args): session.importArchive(serverpath, buildinfo, suboptions.type, suboptions.type_info) print("Imported: %s" % filename) + def handle_grant_permission(goptions, session, args): "[admin] Grant a permission to a user" usage = _("usage: %prog grant-permission [ ...]") @@ -2115,6 +2150,7 @@ def handle_grant_permission(goptions, session, args): for user in users: session.grantPermission(user['name'], perm, **kwargs) + def handle_revoke_permission(goptions, session, args): "[admin] Revoke a permission from a user" usage = _("usage: %prog revoke-permission [ ...]") @@ -2285,6 +2321,7 @@ def anon_handle_list_api(goptions, session, args): if x['doc']: print(" description: %s" % x['doc']) + def anon_handle_list_tagged(goptions, session, args): "[info] List the builds or rpms in a tag" usage = _("usage: %prog list-tagged [options] tag [package]") @@ -2387,6 +2424,7 @@ def anon_handle_list_tagged(goptions, session, args): for line in output: print(line) + def anon_handle_list_buildroot(goptions, session, args): "[info] List the rpms used in or built in a buildroot" usage = _("usage: %prog list-buildroot [options] buildroot-id") @@ -2417,6 +2455,7 @@ def anon_handle_list_buildroot(goptions, session, args): else: print(nvra) + def anon_handle_list_untagged(goptions, session, args): "[info] List untagged builds" usage = _("usage: %prog list-untagged [options] [package]") @@ -2467,12 +2506,15 @@ def anon_handle_list_untagged(goptions, session, args): for line in output: print(line) + def print_group_list_req_group(group): print(" @%(name)s [%(tag_name)s]" % group) + def print_group_list_req_package(pkg): print(" %(package)s: %(basearchonly)s, %(type)s [%(tag_name)s]" % pkg) + def anon_handle_list_groups(goptions, session, args): "[info] Print the group listings" usage = _("usage: %prog list-groups [options] [group]") @@ -2511,6 +2553,7 @@ def anon_handle_list_groups(goptions, session, args): x['tag_name'] = tags.get(x['tag_id'], x['tag_id']) print_group_list_req_package(x) + def handle_add_group_pkg(goptions, session, args): "[admin] Add a package to a group's package listing" usage = _("usage: %prog add-group-pkg [options] [...]") @@ -2526,6 +2569,7 @@ def handle_add_group_pkg(goptions, session, args): for pkg in args[2:]: session.groupPackageListAdd(tag, group, pkg) + def handle_block_group_pkg(goptions, session, args): "[admin] Block a package from a group's package listing" usage = _("usage: %prog block-group-pkg [options] [...]") @@ -2541,6 +2585,7 @@ def handle_block_group_pkg(goptions, session, args): for pkg in args[2:]: session.groupPackageListBlock(tag, group, pkg) + def handle_unblock_group_pkg(goptions, session, args): "[admin] Unblock a package from a group's package listing" usage = _("usage: %prog unblock-group-pkg [options] [...]") @@ -2556,6 +2601,7 @@ def handle_unblock_group_pkg(goptions, session, args): for pkg in args[2:]: session.groupPackageListUnblock(tag, group, pkg) + def handle_add_group_req(goptions, session, args): "[admin] Add a group to a group's required list" usage = _("usage: %prog add-group-req [options] ") @@ -2571,6 +2617,7 @@ def handle_add_group_req(goptions, session, args): activate_session(session, goptions) session.groupReqListAdd(tag, group, req) + def handle_block_group_req(goptions, session, args): "[admin] Block a group's requirement listing" usage = _("usage: %prog block-group-req [options] ") @@ -2586,6 +2633,7 @@ def handle_block_group_req(goptions, session, args): activate_session(session, goptions) session.groupReqListBlock(tag, group, req) + def handle_unblock_group_req(goptions, session, args): "[admin] Unblock a group's requirement listing" usage = _("usage: %prog unblock-group-req [options] ") @@ -2601,6 +2649,7 @@ def handle_unblock_group_req(goptions, session, args): activate_session(session, goptions) session.groupReqListUnblock(tag, group, req) + def anon_handle_list_channels(goptions, session, args): "[info] Print channels listing" usage = _("usage: %prog list-channels") @@ -2620,6 +2669,7 @@ def anon_handle_list_channels(goptions, session, args): for channel in channels: print("%(name)-15s %(hosts) 5d" % channel) + def anon_handle_list_hosts(goptions, session, args): "[info] Print the host listing" usage = _("usage: %prog list-hosts [options]") @@ -2675,6 +2725,7 @@ def anon_handle_list_hosts(goptions, session, args): for host in hosts: print("%(name)-28s %(enabled)-3s %(ready)-3s %(task_load)4.1f/%(capacity)-3.1f %(arches)-16s %(update)s" % host) + def anon_handle_list_pkgs(goptions, session, args): "[info] Print the package listing for tag or for owner" usage = _("usage: %prog list-pkgs [options]") @@ -2752,6 +2803,7 @@ def anon_handle_list_pkgs(goptions, session, args): fmt = "%(package_name)s" print(fmt % pkg) + def anon_handle_rpminfo(goptions, session, args): "[info] Print basic information about an RPM" usage = _("usage: %prog rpminfo [options] [ ...]") @@ -2909,6 +2961,7 @@ def anon_handle_buildinfo(goptions, session, args): print("Changelog:") print(koji.util.formatChangelog(changelog)) + def anon_handle_hostinfo(goptions, session, args): "[info] Print basic information about a host" usage = _("usage: %prog hostinfo [options] [ ...]") @@ -2967,6 +3020,7 @@ def anon_handle_hostinfo(goptions, session, args): else: print("None") + def handle_clone_tag(goptions, session, args): "[admin] Duplicate the contents of one tag onto another tag" usage = _("usage: %prog clone-tag [options] ") @@ -3362,6 +3416,7 @@ def handle_add_target(goptions, session, args): session.createBuildTarget(name, build_tag, dest_tag) + def handle_edit_target(goptions, session, args): "[admin] Set the name, build_tag, and/or dest_tag of an existing build target to new values" usage = _("usage: %prog edit-target [options] name") @@ -3408,6 +3463,7 @@ def handle_edit_target(goptions, session, args): session.editBuildTarget(targetInfo['orig_name'], targetInfo['name'], targetInfo['build_tag_name'], targetInfo['dest_tag_name']) + def handle_remove_target(goptions, session, args): "[admin] Remove a build target" usage = _("usage: %prog remove-target [options] name") @@ -3432,6 +3488,7 @@ def handle_remove_target(goptions, session, args): session.deleteBuildTarget(target_info['id']) + def handle_remove_tag(goptions, session, args): "[admin] Remove a tag" usage = _("usage: %prog remove-tag [options] name") @@ -3456,6 +3513,7 @@ def handle_remove_tag(goptions, session, args): session.deleteTag(tag_info['id']) + def anon_handle_list_targets(goptions, session, args): "[info] List the build targets" usage = _("usage: %prog list-targets [options]") @@ -3480,6 +3538,7 @@ def anon_handle_list_targets(goptions, session, args): print(fmt % target) #pprint.pprint(session.getBuildTargets()) + def _printInheritance(tags, sibdepths=None, reverse=False): if len(tags) == 0: return @@ -3520,6 +3579,7 @@ def _printInheritance(tags, sibdepths=None, reverse=False): _printInheritance(tags, sibdepths, reverse) + def anon_handle_list_tag_inheritance(goptions, session, args): "[info] Print the inheritance information for a tag" usage = _("usage: %prog list-tag-inheritance [options] ") @@ -3575,6 +3635,7 @@ def anon_handle_list_tag_inheritance(goptions, session, args): data = session.getFullInheritance(tag['id'], **opts) _printInheritance(data, None, opts['reverse']) + def anon_handle_list_tags(goptions, session, args): "[info] Print the list of tags" usage = _("usage: %prog list-tags [options] [pattern]") @@ -3631,6 +3692,7 @@ def anon_handle_list_tags(goptions, session, args): sys.stdout.write(' [%(perm)s perm required]' % tag) print('') + def anon_handle_list_tag_history(goptions, session, args): "[info] Print a history of tag operations" usage = _("usage: %prog list-tag-history [options]") @@ -3670,6 +3732,7 @@ def anon_handle_list_tag_history(goptions, session, args): event_id = x['create_event'] timeline.append((event_id, x)) timeline.sort() + def _histline(event_id, x): if event_id == x['revoke_event']: ts = x['revoke_ts'] @@ -3692,6 +3755,7 @@ def anon_handle_list_tag_history(goptions, session, args): print("%r" % x) print(_histline(event_id, x)) + def _print_histline(entry, **kwargs): options = kwargs['options'] event_id, table, create, x = entry @@ -3841,6 +3905,7 @@ def _print_histline(entry, **kwargs): print(' '.join(parts)) hidden_fields = ['active', 'create_event', 'revoke_event', 'creator_id', 'revoker_id', 'creator_name', 'revoker_name', 'create_ts', 'revoke_ts'] + def get_nkey(key): if key == 'perm_id': return 'permission.name' @@ -3902,6 +3967,7 @@ _table_keys = { 'group_package_listing' : ['group_id', 'tag_id', 'package'], } + def anon_handle_list_history(goptions, session, args): "[info] Display historical data" usage = _("usage: %prog list-history [options]") @@ -3983,6 +4049,7 @@ def anon_handle_list_history(goptions, session, args): while True: histdata = session.queryHistory(tables=tables, **kwargs) timeline = [] + def distinguish_match(x, name): """determine if create or revoke event matched""" if options.context: @@ -4031,11 +4098,13 @@ def anon_handle_list_history(goptions, session, args): if last_event: kwargs['afterEvent'] = last_event + def _handleMap(lines, data, prefix=''): for key, val in data.items(): if key != '__starstar': lines.append(' %s%s: %s' % (prefix, key, val)) + def _handleOpts(lines, opts, prefix=''): if opts: lines.append("%sOptions:" % prefix) @@ -4201,6 +4270,7 @@ def _do_parseTaskParams(session, method, task_id, topdir): return lines + def _printTaskInfo(session, task_id, topdir, level=0, recurse=True, verbose=True): """Recursive function to print information about a task and its children.""" @@ -4275,6 +4345,7 @@ def _printTaskInfo(session, task_id, topdir, level=0, recurse=True, verbose=True for child in children: _printTaskInfo(session, child['id'], topdir, level, verbose=verbose) + def anon_handle_taskinfo(goptions, session, args): """[info] Show information about a task""" usage = _("usage: %prog taskinfo [options] taskID [taskID...]") @@ -4293,6 +4364,7 @@ def anon_handle_taskinfo(goptions, session, args): task_id = int(arg) _printTaskInfo(session, task_id, goptions.topdir, 0, options.recurse, options.verbose) + def anon_handle_taginfo(goptions, session, args): "[info] Print basic information about a tag" usage = _("usage: %prog taginfo [options] [ ...]") @@ -4431,6 +4503,7 @@ def handle_add_tag(goptions, session, args): opts['extra'] = extra session.createTag(args[0],**opts) + def handle_edit_tag(goptions, session, args): "[admin] Alter tag information" usage = _("usage: %prog edit-tag [options] name") @@ -4489,6 +4562,7 @@ def handle_edit_tag(goptions, session, args): #XXX change callname session.editTag2(tag, **opts) + def handle_lock_tag(goptions, session, args): "[admin] Lock a tag" usage = _("usage: %prog lock-tag [options] [ ...] ") @@ -4539,6 +4613,7 @@ def handle_lock_tag(goptions, session, args): continue session.editTag2(tag['id'], perm=perm_id) + def handle_unlock_tag(goptions, session, args): "[admin] Unlock a tag" usage = _("usage: %prog unlock-tag [options] [ ...]") @@ -4583,6 +4658,7 @@ def handle_unlock_tag(goptions, session, args): else: session.editTag2(tag['id'], locked=False, perm_id=None) + def handle_add_tag_inheritance(goptions, session, args): """[admin] Add to a tag's inheritance""" usage = _("usage: %prog add-tag-inheritance [options] tag parent-tag") @@ -4726,6 +4802,7 @@ def handle_edit_tag_inheritance(goptions, session, args): inheritanceData[index] = new_data session.setInheritanceData(tag['id'], inheritanceData) + def handle_remove_tag_inheritance(goptions, session, args): """[admin] Remove a tag inheritance link""" usage = _("usage: %prog remove-tag-inheritance tag ") @@ -4789,6 +4866,7 @@ def handle_remove_tag_inheritance(goptions, session, args): inheritanceData[index] = new_data session.setInheritanceData(tag['id'], inheritanceData) + def anon_handle_show_groups(goptions, session, args): "[info] Show groups data for a tag" usage = _("usage: %prog show-groups [options] ") @@ -4812,6 +4890,7 @@ def anon_handle_show_groups(goptions, session, args): else: pprint.pprint(groups) + def anon_handle_list_external_repos(goptions, session, args): "[info] List external repos" usage = _("usage: %prog list-external-repos [options]") @@ -4886,6 +4965,7 @@ def anon_handle_list_external_repos(goptions, session, args): for rinfo in data: print(format % rinfo) + def _pick_external_repo_priority(session, tag): """pick priority after current ones, leaving space for later insertions""" repolist = session.getTagExternalRepos(tag_info=tag) @@ -4897,6 +4977,7 @@ def _pick_external_repo_priority(session, tag): #at least 3 higher than current max and a multiple of 5 return priority + def _parse_tagpri(tagpri): parts = tagpri.rsplit('::', 1) tag = parts[0] @@ -4911,6 +4992,7 @@ def _parse_tagpri(tagpri): raise koji.GenericError("Invalid priority: %s" % parts[1]) return tag, pri + def handle_add_external_repo(goptions, session, args): "[admin] Create an external repo and/or add one to a tag" usage = _("usage: %prog add-external-repo [options] name [url]") @@ -4946,6 +5028,7 @@ def handle_add_external_repo(goptions, session, args): print("Added external repo %s to tag %s (priority %i)" \ % (rinfo['name'], tag, priority)) + def handle_edit_external_repo(goptions, session, args): "[admin] Edit data for an external repo" usage = _("usage: %prog edit-external-repo name") @@ -4968,6 +5051,7 @@ def handle_edit_external_repo(goptions, session, args): activate_session(session, goptions) session.editExternalRepo(args[0], **opts) + def handle_remove_external_repo(goptions, session, args): "[admin] Remove an external repo from a tag or tags, or remove entirely" usage = _("usage: %prog remove-external-repo repo [tag ...]") @@ -5008,6 +5092,7 @@ def handle_remove_external_repo(goptions, session, args): continue session.removeExternalRepoFromTag(tag, repo) + # This handler is for spinning livecd images # def handle_spin_livecd(options, session, args): @@ -5103,6 +5188,7 @@ def handle_spin_livemedia(options, session, args): assert False # pragma: no cover _build_image(options, task_options, session, args, 'livemedia') + # This handler is for spinning appliance images # def handle_spin_appliance(options, session, args): @@ -5158,6 +5244,7 @@ def handle_spin_appliance(options, session, args): assert False # pragma: no cover _build_image(options, task_options, session, args, 'appliance') + def handle_image_build_indirection(options, session, args): """[build] Create a disk image using other disk images via the Indirection plugin""" usage = _("usage: %prog image-build-indirection [base_image] " + @@ -5423,6 +5510,7 @@ def handle_image_build(options, session, args): "SL-6.4 or CentOS-6.4")) _build_image_oz(options, task_options, session, args) + def _build_image(options, task_opts, session, args, img_type): """ A private helper function that houses common CLI code for building @@ -5499,6 +5587,7 @@ def _build_image(options, task_opts, session, args, img_type): else: return + def _build_image_oz(options, task_opts, session, args): """ A private helper function that houses common CLI code for building @@ -5569,6 +5658,7 @@ def _build_image_oz(options, task_opts, session, args): else: return + def handle_win_build(options, session, args): """[build] Build a Windows package from source""" # Usage & option parsing @@ -5647,6 +5737,7 @@ def handle_win_build(options, session, args): else: return + def handle_free_task(goptions, session, args): "[admin] Free a task" usage = _("usage: %prog free-task [options] [ ...]") @@ -5664,6 +5755,7 @@ def handle_free_task(goptions, session, args): for task_id in tlist: session.freeTask(task_id) + def handle_cancel(goptions, session, args): "[build] Cancel tasks and/or builds" usage = _("usage: %prog cancel [options] [ ...]") @@ -5703,6 +5795,7 @@ def handle_cancel(goptions, session, args): for build in blist: session.cancelBuild(build) + def handle_set_task_priority(goptions, session, args): "[admin] Set task priority" usage = _("usage: %prog set-task-priority [options] --priority= [task-id]...") @@ -5728,6 +5821,7 @@ def handle_set_task_priority(goptions, session, args): for task_id in tasks: session.setTaskPriority(task_id, options.priority, options.recurse) + def _list_tasks(options, session): "Retrieve a list of tasks" @@ -5811,6 +5905,7 @@ def handle_list_tasks(goptions, session, args): continue print_task_recurse(t) + def handle_set_pkg_arches(goptions, session, args): "[admin] Set the list of extra arches for a package" usage = _("usage: %prog set-pkg-arches [options] arches tag package [package2 ...]") @@ -5828,6 +5923,7 @@ def handle_set_pkg_arches(goptions, session, args): #really should implement multicall... session.packageListSetArches(tag,package,arches,force=options.force) + def handle_set_pkg_owner(goptions, session, args): "[admin] Set the owner for a package" usage = _("usage: %prog set-pkg-owner [options] owner tag package [package2 ...]") @@ -5845,6 +5941,7 @@ def handle_set_pkg_owner(goptions, session, args): #really should implement multicall... session.packageListSetOwner(tag,package,owner,force=options.force) + def handle_set_pkg_owner_global(goptions, session, args): "[admin] Set the owner for a package globally" usage = _("usage: %prog set-pkg-owner-global [options] owner package [package2 ...]") @@ -5904,6 +6001,7 @@ def handle_set_pkg_owner_global(goptions, session, args): % (entry['package_name'], entry['tag_name'], entry['owner_name'], user['name'])) session.packageListSetOwner(entry['tag_id'], entry['package_name'], user['id']) + def anon_handle_watch_task(goptions, session, args): "[monitor] Track progress of particular tasks" usage = _("usage: %prog watch-task [options] [...]") @@ -5946,6 +6044,7 @@ def anon_handle_watch_task(goptions, session, args): return watch_tasks(session, tasks, quiet=options.quiet, poll_interval=goptions.poll_interval) + def anon_handle_watch_logs(goptions, session, args): "[monitor] Watch logs in realtime" usage = _("usage: %prog watch-logs [options] [...]") @@ -5966,6 +6065,7 @@ def anon_handle_watch_logs(goptions, session, args): watch_logs(session, tasks, options, goptions.poll_interval) + def handle_make_task(opts, session, args): "[admin] Create an arbitrary task" usage = _("usage: %prog make-task [options] [...]") @@ -5994,6 +6094,7 @@ def handle_make_task(opts, session, args): return watch_tasks(session, [task_id], quiet=opts.quiet, poll_interval=opts.poll_interval) + def handle_tag_build(opts, session, args): "[bind] Apply a tag to one or more builds" usage = _("usage: %prog tag-build [options] [...]") @@ -6019,6 +6120,7 @@ def handle_tag_build(opts, session, args): return watch_tasks(session, tasks, quiet=opts.quiet, poll_interval=opts.poll_interval) + def handle_move_build(opts, session, args): "[bind] 'Move' one or more builds between tags" usage = _("usage: %prog move-build [options] [...]") @@ -6066,6 +6168,7 @@ def handle_move_build(opts, session, args): return watch_tasks(session, tasks, quiet=opts.quiet, poll_interval=opts.poll_interval) + def handle_untag_build(goptions, session, args): "[bind] Remove a tag from one or more builds" usage = _("usage: %prog untag-build [options] [...]") @@ -6136,6 +6239,7 @@ def handle_untag_build(goptions, session, args): print(_("untagging %(nvr)s") % binfo) session.untagBuild(tag['name'], binfo['nvr'], force=options.force) + def handle_unblock_pkg(goptions, session, args): "[admin] Unblock a package in the listing for tag" usage = _("usage: %prog unblock-pkg [options] tag package [package2 ...]") @@ -6151,6 +6255,7 @@ def handle_unblock_pkg(goptions, session, args): #really should implement multicall... session.packageListUnblock(tag,package) + def anon_handle_download_build(options, session, args): "[download] Download a built package" usage = _("usage: %prog download-build [options] ") @@ -6503,6 +6608,7 @@ def anon_handle_download_task(options, session, args): output_file.write(session.downloadTaskOutput(task["id"], filename, volume=volume)) output_file.close() + def anon_handle_wait_repo(options, session, args): "[monitor] Wait for a repo to be regenerated" usage = _("usage: %prog wait-repo [options] ") @@ -6587,6 +6693,7 @@ def anon_handle_wait_repo(options, session, args): _search_types = ('package', 'build', 'tag', 'target', 'user', 'host', 'rpm', 'maven', 'win') + def handle_regen_repo(options, session, args): "[admin] Force a repo to be regenerated" usage = _("usage: %prog regen-repo [options] ") @@ -6642,6 +6749,7 @@ def handle_regen_repo(options, session, args): return watch_tasks(session, [task_id], quiet=options.quiet, poll_interval=options.poll_interval) + def handle_dist_repo(options, session, args): """Create a yum repo with distribution options""" usage = _("usage: %prog dist-repo [options] tag keyID [keyID...]") @@ -6787,6 +6895,7 @@ def anon_handle_search(options, session, args): for row in data: print(row['name']) + def handle_moshimoshi(options, session, args): "[misc] Introduce yourself" usage = _("usage: %prog moshimoshi [options]") diff --git a/cli/koji_cli/lib.py b/cli/koji_cli/lib.py index 3ce1a85..a11d1fa 100644 --- a/cli/koji_cli/lib.py +++ b/cli/koji_cli/lib.py @@ -56,9 +56,12 @@ ARGMAP = {'None': None, 'True': True, 'False': False} + def _(args): """Stub function for translation""" return args + + def arg_filter(arg): try: return int(arg) @@ -73,6 +76,7 @@ def arg_filter(arg): #handle lists/dicts? return arg + categories = { 'admin' : 'admin commands', 'build' : 'build commands', @@ -84,6 +88,7 @@ categories = { 'misc' : 'miscellaneous commands', } + def get_epilog_str(progname=None): if progname is None: progname = os.path.basename(sys.argv[0]) or 'koji' @@ -96,6 +101,8 @@ Try "%(progname)s help " to get commands under a particular category Available categories are: %(categories)s ''' % ({'progname': progname, 'categories': categories_ordered}) return _(epilog_str) + + def ensure_connection(session): try: ret = session.getAPIVersion() @@ -104,10 +111,12 @@ def ensure_connection(session): 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() @@ -120,12 +129,14 @@ def print_task(task,depth=0): 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.""" @@ -135,6 +146,7 @@ def parse_arches(arches, to_list=False): else: return ' '.join(arches) + class TaskWatcher(object): def __init__(self,task_id,session,level=0,quiet=False): @@ -226,6 +238,7 @@ class TaskWatcher(object): else: return koji.TASK_STATES[info['state']].lower() + def display_tasklist_status(tasks): free = 0 open = 0 @@ -243,6 +256,7 @@ def display_tasklist_status(tasks): 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'] @@ -258,6 +272,7 @@ def display_task_results(tasks): # shouldn't happen print('%s has not completed' % task_label) + def watch_tasks(session, tasklist, quiet=False, poll_interval=60): if not tasklist: return @@ -310,8 +325,10 @@ Running Tasks: raise return rv + def watch_logs(session, tasklist, opts, poll_interval): print("Watching logs (this may be safely interrupted)...") + def _isDone(session, taskId): info = session.getTaskInfo(taskId) if info is None: @@ -375,6 +392,8 @@ def list_task_output_all_volumes(session, task_id): # otherwise leave off the option and fake it output = session.listTaskOutput(task_id) return dict([fn, ['DEFAULT']] for fn in output) + + 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 @@ -386,6 +405,7 @@ def _unique_path(prefix): return '%s/%r.%s' % (prefix, time.time(), ''.join([random.choice(string.ascii_letters) for i in range(8)])) + def _format_size(size): if (size / 1073741824 >= 1): return "%0.2f GiB" % (size / 1073741824.0) @@ -395,6 +415,7 @@ def _format_size(size): return "%0.2f KiB" % (size / 1024.0) return "%0.2f B" % (size) + def _format_secs(t): h = t / 3600 t %= 3600 @@ -402,6 +423,7 @@ def _format_secs(t): 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 @@ -422,11 +444,14 @@ def _progress_callback(uploaded, total, piece, time, total_time): 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: return True + + def linked_upload(localfile, path, name=None): """Link a file into the (locally writable) workdir, bypassing upload""" old_umask = os.umask(0o02) @@ -443,16 +468,20 @@ def linked_upload(localfile, path, name=None): os.link(localfile, dst) finally: os.umask(old_umask) + + def error(msg=None, code=1): if msg: sys.stderr.write(msg + "\n") sys.stderr.flush() sys.exit(code) + def warn(msg): sys.stderr.write(msg + "\n") sys.stderr.flush() + def has_krb_creds(): if krbV is None: return False @@ -464,6 +493,7 @@ def has_krb_creds(): except krbV.Krb5Error: return False + def activate_session(session, options): """Test and login the session is applicable""" if options.authtype == "noauth" or options.noauth: From bd6f5f3aa666596d0433fdde61c8da09c3739462 Mon Sep 17 00:00:00 2001 From: Mike McLean Date: Jun 16 2017 08:16:07 +0000 Subject: [PATCH 14/19] include plugin.py in py3 lib --- diff --git a/koji/Makefile b/koji/Makefile index f1ae8d4..10ac256 100644 --- a/koji/Makefile +++ b/koji/Makefile @@ -2,7 +2,7 @@ PYTHON=python PACKAGE = $(shell basename `pwd`) ifeq ($(PYTHON), python3) # for python3 we fully support only basic library + CLI - PYFILES = __init__.py util.py + PYFILES = __init__.py util.py plugin.py PYSCRIPTS = SUBDIRS = else From ada23043ee8b948caf1893994b48d3ab44458723 Mon Sep 17 00:00:00 2001 From: Mike McLean Date: Jun 16 2017 08:16:07 +0000 Subject: [PATCH 15/19] cli makefile: make sure we create dir before installing to it --- diff --git a/cli/Makefile b/cli/Makefile index 144c19d..c9112f3 100644 --- a/cli/Makefile +++ b/cli/Makefile @@ -27,6 +27,6 @@ install: mkdir -p $(DESTDIR)/usr/bin install -p -m 755 $(FILES) $(DESTDIR)/usr/bin - install -p -m 644 koji.conf $(DESTDIR)/etc/koji.conf 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 From bd7c52afbf99942ef417757f62e234641e04b6b2 Mon Sep 17 00:00:00 2001 From: Mike McLean Date: Jun 16 2017 08:16:07 +0000 Subject: [PATCH 16/19] avoid sorting dictionary fields python3 is pickier about this than python2 --- diff --git a/cli/koji_cli/commands.py b/cli/koji_cli/commands.py index 25f5949..199ae9f 100644 --- a/cli/koji_cli/commands.py +++ b/cli/koji_cli/commands.py @@ -1572,7 +1572,7 @@ def handle_prune_signed_copies(options, session, args): #at the same event, revokes happen first if x['revoke_event'] is not None: timeline.append((x['revoke_event'], 0, x)) - timeline.sort() + timeline.sort(key=lambda entry: entry[:2]) #find most recent creation entry for our build and crop there latest_ts = None for i in range(len(timeline)-1, -1, -1): @@ -3731,7 +3731,7 @@ def anon_handle_list_tag_history(goptions, session, args): timeline.append((event_id, x)) event_id = x['create_event'] timeline.append((event_id, x)) - timeline.sort() + timeline.sort(key=lambda entry: entry[0]) def _histline(event_id, x): if event_id == x['revoke_event']: @@ -4069,7 +4069,7 @@ def anon_handle_list_history(goptions, session, args): #pprint.pprint(timeline[-1]) if distinguish_match(x, 'created'): timeline.append((x['create_event'], table, 1, x)) - timeline.sort() + timeline.sort(key=lambda entry: entry[:3]) #group edits together new_timeline = [] last_event = None From 3c548a99234cdbcaaef00649562fa6ecd2d3f203 Mon Sep 17 00:00:00 2001 From: Mike McLean Date: Jun 16 2017 08:16:07 +0000 Subject: [PATCH 17/19] fix command options that default to global option value --- diff --git a/cli/koji_cli/commands.py b/cli/koji_cli/commands.py index 199ae9f..dfb1822 100644 --- a/cli/koji_cli/commands.py +++ b/cli/koji_cli/commands.py @@ -778,7 +778,8 @@ def handle_resubmit(goptions, session, args): 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) + parser.add_option("--quiet", action="store_true", default=goptions.quiet, + help=_("Do not print the task information")) (options, args) = parser.parse_args(args) if len(args) != 1: parser.error(_("Please specify a single task ID")) @@ -855,7 +856,7 @@ def anon_handle_mock_config(goptions, session, args): help=_("Specify mockdir")) parser.add_option("--topdir", metavar="DIR", help=_("Specify topdir")) - parser.add_option("--topurl", metavar="URL", default=options.topurl, + parser.add_option("--topurl", metavar="URL", default=goptions.topurl, help=_("URL under which Koji files are accessible")) parser.add_option("--distribution", default="Koji Testing", help=_("Change the distribution macro")) @@ -2223,7 +2224,8 @@ def anon_handle_latest_build(goptions, session, args): parser = OptionParser(usage=usage) parser.add_option("--arch", help=_("List all of the latest packages for this arch")) parser.add_option("--all", action="store_true", help=_("List all of the latest packages for this tag")) - parser.add_option("--quiet", action="store_true", help=_("Do not print the header information"), default=options.quiet) + parser.add_option("--quiet", action="store_true", default=goptions.quiet, + help=_("Do not print the header information")) parser.add_option("--paths", action="store_true", help=_("Show the file paths")) parser.add_option("--type", help=_("Show builds of the given type only. Currently supported types: maven")) (options, args) = parser.parse_args(args) @@ -2332,7 +2334,8 @@ def anon_handle_list_tagged(goptions, session, args): parser.add_option("--inherit", action="store_true", help=_("Follow inheritance")) parser.add_option("--latest", action="store_true", help=_("Only show the latest builds/rpms")) parser.add_option("--latest-n", type='int', metavar="N", help=_("Only show the latest N builds/rpms")) - parser.add_option("--quiet", action="store_true", help=_("Do not print the header information"), default=options.quiet) + parser.add_option("--quiet", action="store_true", default=goptions.quiet, + help=_("Do not print the header information")) parser.add_option("--paths", action="store_true", help=_("Show the file paths")) parser.add_option("--sigs", action="store_true", help=_("Show signatures")) parser.add_option("--type", help=_("Show builds of the given type only. Currently supported types: maven, win, image")) @@ -2655,7 +2658,8 @@ def anon_handle_list_channels(goptions, session, args): usage = _("usage: %prog list-channels") usage += _("\n(Specify the --help global option for a list of other help options)") parser = OptionParser(usage=usage) - parser.add_option("--quiet", action="store_true", help=_("Do not print header information"), default=goptions.quiet) + parser.add_option("--quiet", action="store_true", default=goptions.quiet, + help=_("Do not print header information")) (options, args) = parser.parse_args(args) activate_session(session, goptions) channels = session.listChannels() @@ -2681,7 +2685,8 @@ def anon_handle_list_hosts(goptions, session, args): parser.add_option("--not-ready", action="store_false", dest="ready", help=_("Limit to not ready hosts")) parser.add_option("--enabled", action="store_true", help=_("Limit to enabled hosts")) parser.add_option("--not-enabled", action="store_false", dest="enabled", help=_("Limit to not enabled hosts")) - parser.add_option("--quiet", action="store_true", help=_("Do not print header information"), default=options.quiet) + parser.add_option("--quiet", action="store_true", default=goptions.quiet, + help=_("Do not print header information")) (options, args) = parser.parse_args(args) opts = {} activate_session(session, goptions) @@ -2734,7 +2739,8 @@ def anon_handle_list_pkgs(goptions, session, args): parser.add_option("--owner", help=_("Specify owner")) parser.add_option("--tag", help=_("Specify tag")) parser.add_option("--package", help=_("Specify package")) - parser.add_option("--quiet", action="store_true", help=_("Do not print header information"), default=options.quiet) + parser.add_option("--quiet", action="store_true", default=goptions.quiet, + help=_("Do not print header information")) parser.add_option("--noinherit", action="store_true", help=_("Don't follow inheritance")) parser.add_option("--show-blocked", action="store_true", help=_("Show blocked packages")) parser.add_option("--show-dups", action="store_true", help=_("Show superseded owners")) @@ -3520,7 +3526,8 @@ def anon_handle_list_targets(goptions, session, args): usage += _("\n(Specify the --help global option for a list of other help options)") parser = OptionParser(usage=usage) parser.add_option("--name", help=_("Specify the build target name")) - parser.add_option("--quiet", action="store_true", help=_("Do not print the header information"), default=options.quiet) + 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")) @@ -4906,7 +4913,7 @@ def anon_handle_list_external_repos(goptions, session, args): parser.add_option("--ts", type='int', metavar="TIMESTAMP", help=_("Query at timestamp")) parser.add_option("--repo", type='int', metavar="REPO#", help=_("Query at event corresponding to (nonexternal) repo")) - parser.add_option("--quiet", action="store_true", default=options.quiet, + parser.add_option("--quiet", action="store_true", default=goptions.quiet, help=_("Do not display the column headers")) (options, args) = parser.parse_args(args) if len(args) > 0: @@ -5886,7 +5893,8 @@ def handle_list_tasks(goptions, session, args): parser.add_option("--method", help=_("Only tasks of this method")) parser.add_option("--channel", help=_("Only tasks in this channel")) parser.add_option("--host", help=_("Only tasks for this host")) - parser.add_option("--quiet", action="store_true", help=_("Do not display the column headers"), default=options.quiet) + parser.add_option("--quiet", action="store_true", default=goptions.quiet, + help=_("Do not display the column headers")) (options, args) = parser.parse_args(args) if len(args) != 0: parser.error(_("This command takes no arguments")) @@ -6007,8 +6015,8 @@ def anon_handle_watch_task(goptions, session, args): usage = _("usage: %prog watch-task [options] [...]") usage += _("\n(Specify the --help global option for a list of other help options)") parser = OptionParser(usage=usage) - parser.add_option("--quiet", action="store_true", - help=_("Do not print the task information"), default=options.quiet) + parser.add_option("--quiet", action="store_true", default=goptions.quiet, + help=_("Do not print the task information")) parser.add_option("--mine", action="store_true", help=_("Just watch your tasks")) parser.add_option("--user", help=_("Only tasks for this user")) parser.add_option("--arch", help=_("Only tasks for this architecture")) From 94f116e898c6a4961e305fa6c6ce06443d6cb91d Mon Sep 17 00:00:00 2001 From: Mike McLean Date: Jun 16 2017 08:16:07 +0000 Subject: [PATCH 18/19] watch-logs: fix py3 encoding issues --- diff --git a/cli/koji_cli/lib.py b/cli/koji_cli/lib.py index a11d1fa..7bd61f8 100644 --- a/cli/koji_cli/lib.py +++ b/cli/koji_cli/lib.py @@ -374,7 +374,7 @@ def watch_logs(session, tasklist, opts, poll_interval): sys.stdout.write("\n") sys.stdout.write("==> %s <==\n" % currlog) lastlog = currlog - sys.stdout.write(contents) + sys.stdout.write(contents.decode('utf8')) if not tasklist: break diff --git a/koji/__init__.py b/koji/__init__.py index 9e1e5ce..2b9fe24 100644 --- a/koji/__init__.py +++ b/koji/__init__.py @@ -2680,7 +2680,8 @@ class ClientSession(object): if volume and volume != 'DEFAULT': dlopts['volume'] = volume result = self.callMethod('downloadTaskOutput', taskID, fileName, **dlopts) - return base64.decodestring(result) + return base64.decodestring(result.encode('ascii')) + class DBHandler(logging.Handler): """ From 519f792ce2f576a3541bc6c3db44e50dee373efc Mon Sep 17 00:00:00 2001 From: Tomas Kopecek Date: Jun 16 2017 08:55:08 +0000 Subject: [PATCH 19/19] strip .py from module name --- diff --git a/cli/koji b/cli/koji index 0ed9645..fdb2a1b 100755 --- a/cli/koji +++ b/cli/koji @@ -77,6 +77,7 @@ def load_plugins(options): 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))