#!/usr/bin/python # command line interface for the Koji build system # Copyright (c) 2005-2008 Red Hat # # Koji is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; # version 2.1 of the License. # # This software is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this software; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA # # Authors: # Dennis Gregorovic # Mike McLean # Cristian Balint import sys try: import krbV except ImportError: pass import ConfigParser import base64 import koji import koji.util import fnmatch from koji.util import md5_constructor import os import re import pprint import random import socket import stat import string import time import urllib import urlgrabber.grabber as grabber import urlgrabber.progress as progress import xmlrpclib import optparse #for import-comps handler (currently disabled) #from rhpl.comps import Comps # 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+": 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 def _(args): """Stub function for translation""" return args def arg_filter(str): try: return int(str) except ValueError: pass try: return float(str) except ValueError: pass #handle lists? return str def get_options(): """process options from command line and config file""" usage = _("%prog [global-options] command [command-options-and-arguments]") parser = OptionParser(usage=usage) parser.disable_interspersed_args() parser.add_option("-c", "--config", dest="configFile", help=_("use alternate configuration file"), metavar="FILE", default="~/.koji/config") parser.add_option("--keytab", help=_("specify a Kerberos keytab to use"), metavar="FILE") parser.add_option("--principal", help=_("specify a Kerberos principal to use")) parser.add_option("--runas", help=_("run as the specified user (requires special privileges)")) parser.add_option("--user", help=_("specify user")) parser.add_option("--password", help=_("specify password")) parser.add_option("--noauth", action="store_true", default=False, help=_("do not authenticate")) parser.add_option("--force-auth", action="store_true", default=False, help=_("authenticate even for read-only operations")) parser.add_option("--authtype", help=_("force use of a type of authentication, options: noauth, ssl, password, or kerberos")) parser.add_option("-d", "--debug", action="store_true", default=False, help=_("show debug output")) parser.add_option("--debug-xmlrpc", action="store_true", default=False, help=_("show xmlrpc debug output")) parser.add_option("-q", "--quiet", action="store_true", default=False, help=_("run quietly")) parser.add_option("--skip-main", action="store_true", default=False, help=_("don't actually run main")) parser.add_option("-s", "--server", help=_("url of XMLRPC server")) parser.add_option("--topdir", help=_("specify topdir")) parser.add_option("--weburl", help=_("url of the Koji web interface")) parser.add_option("--pkgurl", help=_("url of the Koji package tree")) parser.add_option("--help-commands", action="store_true", default=False, help=_("list commands")) (options, args) = parser.parse_args() if options.help_commands: list_commands() sys.exit(0) if not args: list_commands() sys.exit(0) aliases = { 'cancel-task' : 'cancel', 'cxl' : 'cancel', 'list-commands' : 'help', } cmd = args[0] cmd = aliases.get(cmd, cmd) cmd = cmd.replace('-', '_') if globals().has_key('anon_handle_' + cmd): if not options.force_auth: options.noauth = True cmd = 'anon_handle_' + cmd elif globals().has_key('handle_' + cmd): cmd = 'handle_' + cmd else: list_commands() parser.error('Unknown command: %s' % args[0]) assert False # load local config defaults = { 'server' : 'http://localhost/kojihub', 'weburl' : 'http://localhost/koji', 'pkgurl' : 'http://localhost/packages', 'topdir' : '/mnt/koji', 'cert': '~/.koji/client.crt', 'ca': '~/.koji/clientca.crt', 'serverca': '~/.koji/serverca.crt', 'authtype': None } # grab settings from /etc/koji.conf first, and allow them to be # overridden by user config progname = os.path.basename(sys.argv[0]) or 'koji' for configFile in ('/etc/koji.conf', os.path.expanduser(options.configFile)): if os.access(configFile, os.F_OK): f = open(configFile) config = ConfigParser.ConfigParser() config.readfp(f) f.close() if config.has_section(progname): for name, value in config.items(progname): #note the defaults dictionary also serves to indicate which #options *can* be set via the config file. Such options should #not have a default value set in the option parser. if defaults.has_key(name): defaults[name] = value for name, value in defaults.iteritems(): if getattr(options, name, None) is None: setattr(options, name, value) dir_opts = ('topdir', 'cert', 'ca', '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 return options, cmd, args[1:] def ensure_connection(session): try: ret = session.getAPIVersion() except xmlrpclib.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') fmt1 = "%(id)-5s %(priority)-4s %(owner)-12s %(state)-8s %(arch)-10s " fmt2 = "%(method)s" if depth: indent = " "*(depth-1) + " +" else: indent = '' if task.get('host'): fmt3 = ' [%(host)s]' else: fmt3 = '' if task.get('build_id'): fmt4 = ' %(build_name)s-%(build_version)s-%(build_release)s' else: fmt4 = '' print ''.join([fmt1 % task, indent, fmt2 % task, fmt3 % task, fmt4 % task]) 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) class TaskWatcher(object): def __init__(self,task_id,session,level=0): self.id = task_id self.session = session self.info = None self.level = level #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 (xmlrpclib.Fault,koji.GenericError),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: 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: 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 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): 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): if not tasklist: return print "Watching tasks (this may be safely interrupted)..." rv = 0 try: tasks = {} for task_id in tasklist: tasks[task_id] = TaskWatcher(task_id,session) while True: all_done = True for task_id,task in tasks.items(): changed = task.update() if not task.is_done(): all_done = False elif changed: # task is done and state just changed 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 tasks.keys(): tasks[child_id] = TaskWatcher(child_id, session, task.level + 1) 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: print display_task_results(tasks) break time.sleep(1) except (KeyboardInterrupt): if tasks: print \ """Tasks still running. You can continue to watch with the 'koji watch-task' command. Running Tasks: %s""" % '\n'.join(['%s: %s' % (t.str(), t.display_state(t.info)) for t in tasks.values() if not t.is_done()]) rv = 1 return rv def watch_logs(session, tasklist, 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']) try: 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 = session.listTaskOutput(task_id) if options.log: logs = [filename for filename in output if filename == options.log] else: logs = [filename for filename in output if filename.endswith('.log')] taskoffsets = offsets[task_id] for log in logs: contents = 'placeholder' while contents: if not taskoffsets.has_key(log): taskoffsets[log] = 0 contents = session.downloadTaskOutput(task_id, log, taskoffsets[log], 16384) taskoffsets[log] += len(contents) if contents: currlog = "%d:%s:" % (task_id, 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(1) except (KeyboardInterrupt): pass 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 tag = args[0] group = args[1] activate_session(session) if not session.hasPerm('admin'): print "This action requires admin privileges" return 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_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) (options, args) = parser.parse_args(args) if len(args) < 2: parser.error(_("Please specify a hostname and at least one arch")) assert False host = args[0] activate_session(session) id = session.getHost(host) if id: print "%s is already in the database, skipping" % host else: id = session.addHost(host, args[1:]) if id: print "%s added: id %d" % (host, id) 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=_("List possible channels")) (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 activate_session(session) channels = dict([(p['name'], p['id']) for p in session.getAllChannels()]) if options.list: for p in channels.keys(): print p return channel = args[1] channel_id = channels.get(channel, None) if not channel_id: 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 session.addHostToChannel(host, channel) 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 host = args[0] activate_session(session) hostinfo = session.getHost(host) if not hostinfo: print "No such host: %s" % host return 1 hostchannels = dict([(p['name'], p['id']) for p in session.listChannels(hostinfo['id'])]) channel = args[1] channel_id = hostchannels.get(channel, None) if not channel_id: print "Host %s is not a member of channel %s" % (host, channel) return 1 session.removeHostFromChannel(host, channel) 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 if not options.owner: parser.error(_("Please specify an owner for the package(s)")) assert False 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) pkglist = dict([(p['package_name'], p['package_id']) for p in session.listPackages(tagID=dsttag['id'])]) 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) return 1 if options.extra_arches: opts['extra_arches'] = ' '.join(options.extra_arches.replace(',',' ').split()) for package in args[1:]: #really should implement multicall... session.packageListAdd(tag,package,options.owner,**opts) 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 activate_session(session) tag = args[0] # check if list of packages exists for that tag already dsttag=session.getTag(tag) pkglist = dict([(p['package_name'], p['package_id']) for p in session.listPackages(tagID=dsttag['id'], inherited=True)]) 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) return 1 for package in args[1:]: #really should implement multicall... session.packageListBlock(tag,package) 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 = t % 3600 m = t / 60 s = t % 60 return "%02d:%02d:%02d" % (h, m, s) def _progress_callback(uploaded, total, piece, time, total_time): 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: if (not os.isatty(0)) or (os.getpgrp() != os.tcgetpgrp(0)): return True except OSError, e: return True return False def handle_build(options, session, args): "Build a package from source" usage = _("usage: %prog build [options] target URL") usage += _("\n(Specify the --help global option for a list of other help options)") parser = OptionParser(usage=usage) parser.add_option("--skip-tag", action="store_true", help=_("Do not attempt to tag package")) parser.add_option("--scratch", action="store_true", help=_("Perform a scratch build")) parser.add_option("--nowait", action="store_true", help=_("Don't wait on build")) 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 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'] = ' '.join(build_opts.arch_override.replace(',',' ').split()) 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 print "Uploading srpm: %s" % source serverdir = _unique_path('cli-build') if _running_in_bg() or build_opts.noprogress: 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) print "Created task:", 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]) def handle_chain_build(options, session, args): # XXX - replace handle_build with this, once chain-building has gotten testing "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("--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(_("At least two arguments (a build target and a SCM URL) are required")) assert False 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']: 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) print "Created task:", 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]) def handle_resubmit(options, session, args): """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 task ID")) assert False activate_session(session) taskID = args[0] if not options.quiet: print "Resubmitting the following task:" _printTaskInfo(session, int(taskID), 0, False, True) newID = session.resubmitTask(int(taskID)) print "Resubmitted task %s as new task %s" % (taskID, newID) if _running_in_bg() or options.nowait: return else: watch_tasks(session,[newID]) def handle_call(options, session, args): "[admin] 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) (options, args) = parser.parse_args(args) if len(args) < 1: parser.error(_("Please specify the name of the XML-RPC method")) assert False activate_session(session) name = args[0] non_kw = [] kw = {} def _convarg(val): valmap = {'None': None, 'True': True, 'False': False} if val.isdigit(): return int(val) elif valmap.has_key(val): return valmap[val] else: return val for arg in args[1:]: if arg.find('=') != -1: key, value = arg.split('=', 1) kw[key] = _convarg(value) else: non_kw.append(_convarg(arg)) pprint.pprint(getattr(session, name).__call__(*non_kw, **kw)) def anon_handle_mock_config(options, session, args): "Create a mock config" usage = _("usage: %prog mock-config [options] name") usage += _("\n(Specify the --help global option for a list of other help options)") parser = OptionParser(usage=usage) parser.add_option("--arch", help=_("Specify the arch")) parser.add_option("--tag", help=_("Create a mock config for a tag")) parser.add_option("--task", help=_("Duplicate the mock config of a previous task")) 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", help=_("url under which Koji files are accessible")) parser.add_option("--distribution", default="Koji Testing", help=_("Change the distribution macro")) parser.add_option("-o", metavar="FILE", dest="ofile", help=_("Output to a file")) (options, args) = parser.parse_args(args) if len(args) != 1: parser.error(_("Please specify and name for your buildroot")) assert False activate_session(session) name = args[0] arch = None opts = {} for k in ('topdir', 'topurl', 'distribution', 'mockdir'): 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) 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 sys.exit(1) if len(broots) > 1: print _("Multiple buildroots found: %s" % [br['id'] for br in broots]) brootinfo = broots[0] opts['repoid'] = brootinfo['repo_id'] opts['tag_name'] = brootinfo['tag_name'] arch = brootinfo['arch'] elif options.tag: if not options.arch: print _("Please specify an arch") sys.exit(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 sys.exit(1) opts['tag_name'] = tag['name'] repo = session.getRepo(config['id']) if not repo: print _("Could not get a repo for tag: %(name)s") % tag sys.exit(1) opts['repoid'] = repo['id'] else: parser.error(_("Please specify one of: --tag, --task, --buildroot")) assert False output = koji.genMockConfig(name, arch, **opts) if options.ofile: fo = file(options.ofile, 'w') fo.write(output) fo.close() else: print output def handle_disable_host(options, session, args): "[admin] Mark a host 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) (options, args) = parser.parse_args(args) if len(args) != 1: parser.error(_("Exactly one argument (a hostname) is required")) assert False host = args[0] activate_session(session) id = session.getHost(host) if not id: print "Host %s does not exist" % host return 1 session.disableHost(host) def handle_enable_host(options, session, args): "[admin] Mark a host 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) (options, args) = parser.parse_args(args) if len(args) != 1: parser.error(_("Exactly one argument (a hostname) is required")) assert False host = args[0] activate_session(session) id = session.getHost(host) if not id: print "Host %s does not exist" % host return 1 session.enableHost(host) def handle_import(options, session, args): "[admin] Import local RPMs to 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 the rpm")) 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 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 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 = 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 _("Skipping import") return if options.test: print _("Test mode -- skipping import for %s") % path return serverdir = _unique_path('cli-import') if options.link: old_umask = os.umask(002) dst = "%s/%s/%s" % (koji.pathinfo.work(), serverdir, os.path.basename(path)) koji.ensuredir(os.path.dirname(dst)) os.chown(os.path.dirname(dst), 48, 48) #XXX - hack print "Linking rpm to: %s" % dst os.link(path, dst) os.umask(old_umask) else: print _("uploading %s...") % path, sys.stdout.flush() session.uploadWrapper(path, serverdir) print _("done") sys.stdout.flush() print _("importing %s...") % path, sys.stdout.flush() try: session.importRPM(serverdir, os.path.basename(path)) except koji.GenericError, e: print _("\nError importing: %s" % str(e).splitlines()[-1]) sys.stdout.flush() else: print _("done") sys.stdout.flush() for nvr in nvrs: got_build = False #srpms first, if any for path, data in to_import[nvr]: if data['sourcepackage']: do_import(path, data) got_build = True for path, data in to_import[nvr]: if data['sourcepackage']: continue if not got_build: binfo = session.getBuild(nvr) if binfo: got_build = True elif options.create_build: binfo = koji.parse_NVR(nvr) if options.src_epoch: binfo['epoch'] = options.src_epoch else: binfo['epoch'] = data['epoch'] if options.test: print _("Test mode -- would have created empty build: %s") % nvr got_build = True #avoid duplicate notices else: print _("Creating empty build: %s") % nvr session.createEmptyBuild(**binfo) else: #shouldn't happen print _("Build missing: %s") % nvr break do_import(path, data) # Currently disabled, needs porting to yum.comps #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 # comps = Comps(args[0]) # tag = args[1] # force = local_options.force # #add all the groups first (so that group reqs do not break) # for name,group in comps.groups.items(): # print "Group: %s (%s)" % (group.id,name) # session.groupListAdd(tag,group.id,force=force,display_name=name, # is_default=bool(group.default), # uservisible=bool(group.user_visible), # description=group.description, # langonly=group.langonly, # biarchonly=bool(group.biarchonly)) # #for k in ('id','biarchonly','langonly','user_visible','default','description'): # # print " %s: %s" %(k,getattr(group,k)) # for name,group in comps.groups.items(): # print "Group: %s (%s)" % (group.id,name) # for pkg in group.pkgs.values(): # pkg = pkg.copy() # pkg_name = pkg['package'] # if group.pkgConditionals.has_key(pkg_name): # pkg['requires'] = group.pkgConditionals[pkg_name] # pkg['basearchonly'] = bool(pkg['baseonly']) # del pkg['package'] # del pkg['baseonly'] # print " Package: %s: %r" % (pkg_name, pkg) # session.groupPackageListAdd(tag,group.id,pkg_name,force=force, **pkg) # for type,req in group.groups.values(): # print " Req: %s (%s)" % (req,type) # session.groupReqListAdd(tag,group.id,req,force=force,type=type) # for type,req in group.metapkgs.values(): # print " Metapkg: %s (%s)" %(req,type) # session.groupReqListAdd(tag,group.id,req,force=force,type=type,is_metapkg=True) 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("--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 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','sourcepackage')) if data['sourcepackage']: data['arch'] = 'src' sigkey = data['siggpg'] if not sigkey: sigkey = "" if not options.with_unsigned: print _("Skipping unsigned package: %s" % path) continue else: sigkey = koji.hex_string(sigkey[13:17]) del data['siggpg'] 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" %rpminfo 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 continue print _("Importing signature [key %s] from %s...") % (sigkey, path) if not options.test: session.addRPMSig(rinfo['id'], base64.encodestring(sighdr)) 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 if len(args) < 2 and not (options.all or options.buildid): parser.error(_("At least one RPM must be specified")) assert False key = args.pop(0) activate_session(session) if options.all: rpms = session.queryRPMSigs(sigkey=key) count = 1 for rpm in rpms: print "%d/%d" % (count, len(rpms)) count += 1 session.writeSignedRPM(rpm['rpm_id'], key) elif options.buildid: rpms = session.listRPMs(int(options.buildid)) for rpm in rpms: session.writeSignedRPM(rpm['id'], key) else: for nvr in args: build = session.getBuild(nvr) rpms = session.listRPMs(buildID=build['id']) for rpm in rpms: session.writeSignedRPM(rpm['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 = file(options.ignore_tag_file) options.ignore_tag.extend([line.strip() for line in fo.readlines()]) fo.close() if options.protect_tag_file: fo = file(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 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 not binfo.has_key('id'): binfo['id'] = binfo['build_id'] if not binfo.has_key('name'): 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" % 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 xrange(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 others.has_key(entry['build_id']): 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 by_sig.iteritems(): 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 options.test: print "Would have unlinked: %s" % signedpath else: if options.verbose: print "Unlinking: %s" % signedpath try: os.unlink(signedpath) except OSError, 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, e: print "Error removing %s: %s" % (signedpath, e) if len(sigdirs) == 1: dir = 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, 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_list_permissions(options, session, args): "[admin] 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=_("Only list permissions for this user")) (options, args) = parser.parse_args(args) if len(args) > 0: parser.error(_("This command takes no arguments")) assert False activate_session(session) if options.user: user = session.getUser(options.user) if not user: raise koji.GenericError("%s can not be found" % options.user) perms = session.getUserPerms(user['id']) else: perms = [p['name'] for p in session.getAllPerms()] print perms 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 enable")) 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" %rpminfo sys.exit(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 activate_session(session) for nvr in args: data = koji.parse_NVR(nvr) print _("importing %s...") % nvr, try: session.importBuildInPlace(data) except koji.GenericError, e: print _("\nError importing: %s" % str(e).splitlines()[-1]) sys.stdout.flush() else: print _("done") sys.stdout.flush() 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("--list", action="store_true", help=_("List possible permissions")) (options, args) = parser.parse_args(args) if not options.list and len(args) < 2: parser.error(_("Please specify a permission and at least one user")) assert False activate_session(session) perms = dict([(p['name'], p['id']) for p in session.getAllPerms()]) if options.list: for p in perms.keys(): print p return perm_id = perms.get(args[0], None) if perm_id is None: print "No such permission: %s" % args[0] sys.exit(1) names = args[1:] users = [] for n in names: user = session.getUser(n) if user is None: print "No such user: %s" % n sys.exit(1) users.append(user) for user in users: session.grantPermission(user['id'], perm_id) def anon_handle_latest_pkg(options, session, args): "Print the latest packages for a tag" usage = _("usage: %prog latest-pkg [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")) (options, args) = parser.parse_args(args) if len(args) == 0: parser.error(_("A tag name must be specified")) assert False activate_session(session) if options.all: if len(args) > 1: parser.error(_("A package name may not be combined with --all")) assert False # 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 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: data = session.getLatestBuilds(args[0], package=pkg) if options.paths: for x in data: x['name'] = x['package_name'] x['path'] = pathinfo.build(x) fmt = "%(path)-40s %(tag_name)-20s %(owner_name)s" else: fmt = "%(nvr)-40s %(tag_name)-20s %(owner_name)s" if not options.quiet: 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_latest_by_tag(options, session, args): "Print the latest packages for each tag" usage = _("usage: %prog latest-by-tag [options] package [package...]") 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 header information"), default=options.quiet) parser.add_option("--paths", action="store_true", help=_("Show the file paths")) (options, args) = parser.parse_args(args) if len(args) == 0: parser.error(_("A package name must be specified")) assert False activate_session(session) pathinfo = koji.PathInfo() for pkg in args: tags = [x['name'] for x in session.listTags(package=pkg)] tags.sort() for tag in tags: data = session.getLatestBuilds(tag, package=pkg) if options.paths: for x in data: x['path'] = pathinfo.build(x) x['tag_name'] = tag fmt = "%(path)-40s %(tag_name)-30s %(owner_name)s" else: for x in data: x['tag_name'] = tag fmt = "%(nvr)-40s %(tag_name)-30s %(owner_name)s" if not options.quiet: print "%-40s %-30s %s" % ("Build", "Tag", "Built by") print "%s %s %s" % ("-"*40, "-"*30, "-"*16) options.quiet = True for datum in data: print fmt % datum def anon_handle_list_api(options, session, args): "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 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 x['args']: expanded = [] for arg in x['args']: if type(arg) is str: expanded.append(arg) else: expanded.append('%s=%s' % (arg[0], arg[1])) args = ", ".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): "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("--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("--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 elif len(args) > 2: parser.error(_("Only one package name may be specified")) assert False 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 package: opts['package'] = package if options.arch: options.rpms = True opts['arch'] = options.arch if options.sigs: opts['rpmsigs'] = True options.rpms = 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 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 x.has_key('path')] 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: for x in data: x['name'] = x['package_name'] x['path'] = pathinfo.build(x) fmt = "%(path)-40s %(tag_name)-20s %(owner_name)s" else: fmt = "%(nvr)-40s %(tag_name)-20s %(owner_name)s" if not options.quiet: 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): "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")) (options, args) = parser.parse_args(args) if len(args) != 1: parser.error(_("Incorrect number of arguments")) assert False activate_session(session) package = None buildrootID = int(args[0]) opts = {} if options.built: opts['buildrootID'] = buildrootID else: opts['componentBuildrootID'] = buildrootID data = session.listRPMs(**opts) fmt = "%(nvr)s.%(arch)s" output = [ fmt % x for x in data] output.sort() for line in output: print line def anon_handle_list_untagged(options, session, args): "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 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) has_ref = {} #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 not refs.has_key(x['id'])] 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): "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) (options, args) = parser.parse_args(args) if len(args) < 1 or len(args) > 2: parser.error(_("Incorrect number of arguments")) assert False opts = {} activate_session(session) 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(_("This command takes three arguments")) assert False tag = args[0] group = args[1] pkg = args[2] activate_session(session) 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(_("This command takes at least three arguments")) assert False 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(_("This command takes three arguments")) assert False tag = args[0] group = args[1] pkg = args[2] activate_session(session) 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(_("This command takes three arguments")) assert False 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(_("This command takes three arguments")) assert False 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(_("This command takes three arguments")) assert False tag = args[0] group = args[1] req = args[2] activate_session(session) session.groupReqListUnblock(tag, group, req) def anon_handle_list_hosts(options, session, args): "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 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): "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")) (options, args) = parser.parse_args(args) if len(args) != 0: parser.error(_("This command takes no arguments")) assert False activate_session(session) opts = {} if options.owner: user = session.getUser(options.owner) if user is None: parser.error(_("Invalid user")) assert False opts['userID'] = user['id'] if options.tag: tag = session.getTag(options.tag) if tag is None: parser.error(_("Invalid tag")) assert False 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 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 pkg.has_key('tag_id'): 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): "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) (options, args) = parser.parse_args(args) if len(args) < 1: parser.error(_("Please specify an RPM")) assert False 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 "Payload: %(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']) 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 def anon_handle_buildinfo(options, session, args): "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 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 taglist = [] for tag in session.listTags(build): taglist.append(tag['name']) if info['epoch'] is None: info['epoch'] = "" else: info['epoch'] = str(info['epoch']) + ":" info['name'] = info['package_name'] info['arch'] = 'src' info['state'] = koji.BUILD_STATES[info['state']] rpms = session.listRPMs(buildID=info['id']) print "BUILD: %(name)s-%(version)s-%(release)s [%(id)d]" % info print "State: %(state)s" % info print "Built by: %(owner_name)s" % info print "Task: %(task_id)s" % info print "Finished: %s" % koji.formatTimeLong(info['completion_time']) print "Tags: %s" % ' '.join(taglist) print "RPMs:" for rpm in rpms: print os.path.join(koji.pathinfo.build(info), koji.pathinfo.rpm(rpm)) if options.changelog: print "Changelog:" for entry in session.getChangelogEntries(info['id']): print ("* %s %s" % (time.strftime('%a %b %d %Y', time.strptime(entry['date'], '%Y-%m-%d %H:%M:%S')), entry['author'])).encode('utf-8') print entry['text'].encode('utf-8') 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("-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 argument: ")) assert False 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 # 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: # 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']) newtag = session.getTag(args[1]) # store the new tag, need its asigned id. # get pkglist of src-tag, including inherited packages. srcpkgs = session.listPackages(tagID=srctag['id'],inherited=True) srcpkgs.sort(lambda x, y: cmp(x['package_name'],y['package_name'])) 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']) # get --all latest builds from src tag builds=session.getLatestBuilds(srctag['name']) 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) # Copy the group data srcgroups = session.getTagGroups(srctag['name']) 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'])) # case of existing dst-tag. if dsttag: # get fresh list of packages & builds into maps. srcpkgs = {} for pkg in session.listPackages(tagID=srctag['id'],inherited=True): srcpkgs[pkg['package_name']] = pkg dstpkgs = {} for pkg in session.listPackages(tagID=dsttag['id'],inherited=True): dstpkgs[pkg['package_name']] = pkg srclblds = {} for build in session.getLatestBuilds(srctag['name']): srclblds[build['nvr']] = build dstlblds = {} for build in session.getLatestBuilds(dsttag['name']): dstlblds[build['nvr']] = build srcgroups = {} for group in session.getTagGroups(srctag['name']): srcgroups[group['name']] = group dstgroups = {} 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 srcpkgs.iteritems(): if not dstpkgs.has_key(package_name): paddlist.append(pkg) paddlist.sort(lambda x, y: cmp(x['package_name'],y['package_name'])) pdellist=[] # list containing packages no more present in dst tag for (package_name,pkg) in dstpkgs.iteritems(): if not srcpkgs.has_key(package_name): pdellist.append(pkg) pdellist.sort(lambda x, y: cmp(x['package_name'],y['package_name'])) baddlist=[] # list containing new builds to be added from src tag for (nvr,lbld) in srclblds.iteritems(): if not dstlblds.has_key(nvr): baddlist.append(lbld) baddlist.sort(lambda x, y: cmp(x['package_name'],y['package_name'])) bdellist=[] # list containing new builds to be removed from src tag for (nvr,lbld) in dstlblds.iteritems(): if not srclblds.has_key(nvr): bdellist.append(lbld) bdellist.sort(lambda x, y: cmp(x['package_name'],y['package_name'])) gaddlist=[] # list containing new groups to be added from src tag for (grpname, group) in srcgroups.iteritems(): if not dstgroups.has_key(grpname): gaddlist.append(group) gdellist=[] # list containing groups to be removed from src tag for (grpname, group) in dstgroups.iteritems(): if not srcgroups.has_key(grpname): gdellist.append(group) grpchanges={} # dict of changes to make in shared groups for (grpname, group) in srcgroups.iteritems(): if dstgroups.has_key(grpname): 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. 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']) # ADD builds. 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) # ADD groups. 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'])) # ADD group pkgs. 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) # DEL builds. 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) # DEL packages. 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']) # DEL groups. 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'])) # DEL group pkgs. 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) # 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 elif len(args) > 3: parser.error(_("Incorrect number of arguments")) assert False 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 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 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 if options.dest_tag: 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 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 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): "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 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(u'\u2502'.encode('UTF-8')) outdepth = depth sys.stdout.write(' ' * ((currtag['currdepth'] - outdepth) * 3 - 1)) if siblings: sys.stdout.write(u'\u251c'.encode('UTF-8')) else: sys.stdout.write(u'\u2514'.encode('UTF-8')) sys.stdout.write(u'\u2500'.encode('UTF-8')) 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): "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")) (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 activate_session(session) tag = session.getTag(args[0]) if not tag: parser.error(_("Unknown tag: %s" % args[0])) opts = {} opts['reverse'] = options.reverse or False opts['stop'] = {} opts['jump'] = {} 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['jump'][str(tag1)] = tag2 if options.stop: tag1 = session.getTagID(options.stop) if not tag1: parser.error(_("Unknown tag: %s" % options.stop)) opts['stop'] = {str(tag1): 1} sys.stdout.write('%s (%i)\n' % (tag['name'], tag['id'])) _printInheritance(session.getFullInheritance(tag['id'], None, opts['reverse'], opts['stop'], opts['jump']), None, opts['reverse']) def anon_handle_list_tags(options, session, args): "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) #if len(args) != 0: # parser.error(_("This command takes no arguments")) # assert False 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 if options.build: buildinfo = session.getBuild(options.build) if not buildinfo: parser.error(_("Invalid build %s" % options.build)) assert False tags = session.listTags(buildinfo.get('id',None), pkginfo.get('id',None)) tags.sort(lambda a,b: cmp(a['name'],b['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: print fmt % tag, if tag['locked']: print ' [LOCKED]', if tag['perm']: print ' [%(perm)s perm required]' % tag, print '' def anon_handle_list_tag_history(options, session, args): "Print a history of tag operations" usage = _("usage: %prog list-tag-history [options] [pattern]") 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 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 = "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 event_id, x in timeline: if options.debug: print "%r" % x print _histline(event_id, x) def _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: for key in params[4].keys(): if not key == '__starstar': lines.append("%s: %s" % (key, params[4][key])) 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]) for key in params[2].keys(): if not key == '__starstar': lines.append("%s: %s" % (key, params[2][key])) 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'])) 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]: lines.append(" Options:") subopts = subtask[2] for key in subopts: if not key == '__starstar': lines.append(" %s: %s" % (key, subopts[key])) lines.append("") 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['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 = session.listTaskOutput(task_id) logs = [filename for filename in files if filename.endswith('.log')] output = [filename for filename in files if not filename.endswith('.log')] files_dir = '%s/%s' % (koji.pathinfo.work(), koji.pathinfo.taskrelpath(task_id)) 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()) 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 in logs: print "%s %s/%s" % (indent, files_dir, log) if output: print "%sOutput:" % indent for filename in output: print "%s %s/%s" % (indent, files_dir, filename) # white space sys.stdout.write("\n") if recurse: level += 1 children = session.getTaskChildren(task_id) for child in children: _printTaskInfo(session, child['id'], level, verbose=verbose) def anon_handle_taskinfo(options, session, args): """Show information about a task""" usage = _("usage: %prog taskinfo [options] task_id") usage += _("\n(Specify the --help global option for a list of other help options)") parser = OptionParser(usage=usage) parser.add_option("--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(_("This command takes exctly one argument: a task ID")) assert False activate_session(session) task_id = int(args[0]) _printTaskInfo(session, task_id, 0, options.recurse, options.verbose) def anon_handle_taginfo(options, session, args): "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) (options, args) = parser.parse_args(args) if len(args) < 1: parser.error(_("Please specify a tag")) assert False activate_session(session) perms = dict([(p['id'], p['name']) for p in session.getAllPerms()]) for tag in args: info = session.getTag(tag) if info is None: print "No such tag: %s\n" % tag continue print "Tag: %(name)s [%(id)d]" %info print "Arches: %(arches)s" %info 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) dest_targets = session.getBuildTargets(destTagID=info['id']) build_targets = session.getBuildTargets(buildTagID=info['id']) repos = {} for target in dest_targets + build_targets: if not repos.has_key(target['build_tag']): 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: 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" 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']) 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): 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']: print " maxdepth: %(maxdepth)s" % parent if parent['pkg_filter']: print " package filter: %(pkg_filter)s" % parent print 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")) (options, args) = parser.parse_args(args) if len(args) != 1: parser.error(_("Please specify a name for the tag")) assert False 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'] = ' '.join(options.arches.replace(',',' ').split()) 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")) (options, args) = parser.parse_args(args) if len(args) != 1: parser.error(_("Please specify a name for the tag")) assert False activate_session(session) tag = args[0] opts = {} if options.arches: opts['arches'] = ' '.join(options.arches.replace(',',' ').split()) 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 #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 activate_session(session) pdata = session.getAllPerms() perms = dict([(p['id'], p['name']) for p in pdata]) 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 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 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 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 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 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']) 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 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 and options.maxdepth.isdigit(): new_data['maxdepth'] = int(options.maxdepth) 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 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 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): "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 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): "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 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 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 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 to tag %s (priority %i)" \ % (rinfo['name'], options.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 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 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 if not current_tags: print _("External repo %s not associated with any tags") % repo sys.exit(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") sys.exit(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) 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 for task_id in tlist: session.freeTask(task_id) def handle_cancel(options, session, args): "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 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 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_list_tasks(options, session, args): "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("--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 activate_session(session) if options.mine: id = session.getLoggedInUser()['id'] else: id = None tasklist = session.taskReport(owner=id) #tasks are pre-sorted tasks = dict([(x['id'], x) for x in tasklist]) #thread the tasks if not tasklist: print "(no tasks)" return 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 seen = {} 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 activate_session(session) arches = ' '.join(args[0].replace(',',' ').split()) 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 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 elif len(args) < 2: parser.error(_("Please specify an owner and at least one package")) assert False activate_session(session) owner = args[0] packages = args[1:] user = session.getUser(owner) if not user: print "No such user: %s" % owner sys.exit(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 sys.exit(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'] sys.exit(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): "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) (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_tasks(session,tasks) def anon_handle_watch_logs(options, session, args): "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(options, 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 = {} method = args[0] taskargs = map(arg_filter,args[1:]) 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=map(arg_filter,args[1:]), opts=taskopts) print "Created task id %d" % task_id if options.watch: watch_tasks(session,[task_id]) def handle_tag_pkg(options, session, args): "Apply a tag to one or more packages" usage = _("usage: %prog tag-pkg [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 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 %s" % task_id if _running_in_bg() or options.nowait: return else: watch_tasks(session,tasks) def handle_move_pkg(options, session, args): "'Move' one or more packages between tags" usage = _("usage: %prog move-pkg [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 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 %s, moving %s" % (task_id, koji.buildLabel(build)) if _running_in_bg() or options.nowait: return else: return watch_tasks(session, tasks) def handle_untag_pkg(options, session, args): "Remove a tag from one or more packages" usage = _("usage: %prog untag-pkg [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 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 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 not seen_pkg.has_key(binfo['name']): #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: sys.exit(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 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 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", dest="arches", metavar="ARCH", action="append", default=[], help=_("Only download packages for this arch (may be used multiple times)")) 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("--key", help=_("Download rpms signed with the given key")) 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 elif len(args) > 1: parser.error(_("Only a single package N-V-R or build ID may be specified")) assert False 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.latestfrom: # We want the latest build, not a specific build try: builds = session.listTagged(suboptions.latestfrom, latest=True, package=build) except koji.GenericError, 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] else: info = session.getBuild(build) if info is None: print "No such build: %s" % build return 1 arches = suboptions.arches if len(arches) == 0: arches = None 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 if suboptions.quiet: pg = None else: pg = progress.TextMeter() for rpm in rpms: if rpm['name'].endswith('-debuginfo') and not suboptions.debuginfo: continue if suboptions.key: fname = koji.pathinfo.signed(rpm, suboptions.key) else: fname = koji.pathinfo.rpm(rpm) url = "%s/%s/%s/%s/%s" % (options.pkgurl, info['package_name'], info['version'], info['release'], fname) file = grabber.urlopen(url, progress_obj = pg, text = "%s.%s" % (rpm['name'], rpm['arch'])) out = os.open(os.path.basename(fname), os.O_WRONLY|os.O_CREAT|os.O_TRUNC, 0666) try: while 1: buf = file.read(4096) if not buf: break os.write(out, buf) finally: os.close(out) file.close() def anon_handle_wait_repo(options, session, args): "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 (or a later build of the same package) 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")) (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) tag_id = tag_info['id'] 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']): 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 time.sleep(60) 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 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 _search_types = ('package', 'build', 'tag', 'target', 'user', 'host', 'rpm', 'file') 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")) (suboptions, args) = parser.parse_args(args) if len(args) == 0: parser.error(_("A tag name must be specified")) assert False 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 activate_session(session) tag = args[0] if suboptions.target: info = session.getBuildTarget(tag) if not info: parser.error(_("No matching build target: " + tag)) assert False tag = info['build_tag_name'] else: info = session.getTag(tag) if not info: parser.error(_("No matching tag: " + tag)) assert False tag = info['name'] task_id = session.newRepo(tag) print "Regenerating repo for tag " + tag if _running_in_bg() or suboptions.nowait: return else: session.logout() return watch_tasks(session, [task_id]) def anon_handle_search(options, session, args): "Search koji" usage = _("usage: %prog sync [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 = optparse.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")) main_options = options (options, args) = parser.parse_args(args) 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_help(options, session, args): "List available commands" usage = _("usage: %prog help [options]") usage += _("\n(Specify the --help global option for a list of other help options)") parser = OptionParser(usage=usage) parser.add_option("--admin", action="store_true", help=_("show admin commands")) (options, args) = parser.parse_args(args) list_commands(show_admin=options.admin) def list_commands(show_admin=False): 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 alias,handler in handlers: desc = handler.__doc__ if desc.startswith('[admin] '): if not show_admin: continue desc = desc[8:] print " %-25s %s" % (alias, desc) print _('(Type "koji --help" for help about global options') print _(' or "koji --help" for help about a particular command\'s options') print _(' or "koji help --admin" for help about privileged administrative commands.)') 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 not sys.modules.has_key('krbV'): 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, options.ca, 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 krbV.Krb5Error, e: error(_("Kerberos authentication failed: %s (%s)") % (e.args[1], e.args[0])) except socket.error, e: warn(_("Could not connect to Kerberos authentication service: %s") % e.args[1]) 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__": options, command, args = get_options() session_opts = {} for k in ('user', 'password', 'debug_xmlrpc', 'debug'): session_opts[k] = getattr(options,k) session = koji.ClientSession(options.server,session_opts) rv = 0 try: rv = locals()[command].__call__(options, session, args) if not rv: rv = 0 except KeyboardInterrupt: pass except SystemExit: rv = 1 except: if options.debug: raise else: exctype, value = sys.exc_info()[:2] rv = 1 print "%s: %s" % (exctype.__name__, value) try: session.logout() except: pass sys.exit(rv)