5235 lines
218 KiB
Python
Executable file
5235 lines
218 KiB
Python
Executable file
#!/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 <dgregor@redhat.com>
|
|
# Mike McLean <mikem@redhat.com>
|
|
# Cristian Balint <cbalint@redhat.com>
|
|
import sys
|
|
try:
|
|
import krbV
|
|
except ImportError:
|
|
pass
|
|
try:
|
|
import ast
|
|
except ImportError:
|
|
ast = None
|
|
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
|
|
|
|
ARGMAP = {'None': None,
|
|
'True': True,
|
|
'False': False}
|
|
|
|
def arg_filter(arg):
|
|
try:
|
|
return int(arg)
|
|
except ValueError:
|
|
pass
|
|
try:
|
|
return float(arg)
|
|
except ValueError:
|
|
pass
|
|
if arg in ARGMAP:
|
|
return ARGMAP[arg]
|
|
#handle lists/dicts?
|
|
return arg
|
|
|
|
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',
|
|
'max_retries' : None,
|
|
'retry_interval': None,
|
|
'anon_retry' : None,
|
|
'offline_retry' : None,
|
|
'offline_retry_interval' : None,
|
|
'poll_interval': 5,
|
|
'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):
|
|
if name in ('anon_retry', 'offline_retry'):
|
|
defaults[name] = config.getboolean(progname, name)
|
|
elif name in ('max_retries', 'retry_interval',
|
|
'offline_retry_interval', 'poll_interval'):
|
|
try:
|
|
defaults[name] = int(value)
|
|
except ValueError:
|
|
parser.error("value for %s config option must be a valid integer" % name)
|
|
assert False
|
|
else:
|
|
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,quiet=False):
|
|
self.id = task_id
|
|
self.session = session
|
|
self.info = None
|
|
self.level = level
|
|
self.quiet = quiet
|
|
|
|
#XXX - a bunch of this stuff needs to adapt to different tasks
|
|
|
|
def str(self):
|
|
if self.info:
|
|
label = koji.taskLabel(self.info)
|
|
return "%s%d %s" % (' ' * self.level, self.id, label)
|
|
else:
|
|
return "%s%d" % (' ' * self.level, self.id)
|
|
|
|
def __str__(self):
|
|
return self.str()
|
|
|
|
def get_failure(self):
|
|
"""Print infomation about task completion"""
|
|
if self.info['state'] != koji.TASK_STATES['FAILED']:
|
|
return ''
|
|
error = None
|
|
try:
|
|
result = self.session.getTaskResult(self.id)
|
|
except (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:
|
|
if not self.quiet:
|
|
print "No such task id: %i" % self.id
|
|
sys.exit(1)
|
|
state = self.info['state']
|
|
if last:
|
|
#compare and note status changes
|
|
laststate = last['state']
|
|
if laststate != state:
|
|
if not self.quiet:
|
|
print "%s: %s -> %s" % (self.str(), self.display_state(last), self.display_state(self.info))
|
|
return True
|
|
return False
|
|
else:
|
|
# First time we're seeing this task, so just show the current state
|
|
if not self.quiet:
|
|
print "%s: %s" % (self.str(), self.display_state(self.info))
|
|
return False
|
|
|
|
def is_done(self):
|
|
if self.info is None:
|
|
return False
|
|
state = koji.TASK_STATES[self.info['state']]
|
|
return (state in ['CLOSED','CANCELED','FAILED'])
|
|
|
|
def is_success(self):
|
|
if self.info is None:
|
|
return False
|
|
state = koji.TASK_STATES[self.info['state']]
|
|
return (state == 'CLOSED')
|
|
|
|
def display_state(self, info):
|
|
if info['state'] == koji.TASK_STATES['OPEN']:
|
|
if info['host_id']:
|
|
host = self.session.getHost(info['host_id'])
|
|
return 'open (%s)' % host['name']
|
|
else:
|
|
return 'open'
|
|
elif info['state'] == koji.TASK_STATES['FAILED']:
|
|
return 'FAILED: %s' % self.get_failure()
|
|
else:
|
|
return koji.TASK_STATES[info['state']].lower()
|
|
|
|
def display_tasklist_status(tasks):
|
|
free = 0
|
|
open = 0
|
|
failed = 0
|
|
done = 0
|
|
for task_id in tasks.keys():
|
|
status = tasks[task_id].info['state']
|
|
if status == koji.TASK_STATES['FAILED']:
|
|
failed += 1
|
|
elif status == koji.TASK_STATES['CLOSED'] or status == koji.TASK_STATES['CANCELED']:
|
|
done += 1
|
|
elif status == koji.TASK_STATES['OPEN'] or status == koji.TASK_STATES['ASSIGNED']:
|
|
open += 1
|
|
elif status == koji.TASK_STATES['FREE']:
|
|
free += 1
|
|
print " %d free %d open %d done %d failed" % (free, open, done, failed)
|
|
|
|
def display_task_results(tasks):
|
|
for task in [task for task in tasks.values() if task.level == 0]:
|
|
state = task.info['state']
|
|
task_label = task.str()
|
|
|
|
if state == koji.TASK_STATES['CLOSED']:
|
|
print '%s completed successfully' % task_label
|
|
elif state == koji.TASK_STATES['FAILED']:
|
|
print '%s failed' % task_label
|
|
elif state == koji.TASK_STATES['CANCELED']:
|
|
print '%s was canceled' % task_label
|
|
else:
|
|
# shouldn't happen
|
|
print '%s has not completed' % task_label
|
|
|
|
def watch_tasks(session,tasklist,quiet=False):
|
|
global options
|
|
if not tasklist:
|
|
return
|
|
if not quiet:
|
|
print "Watching tasks (this may be safely interrupted)..."
|
|
rv = 0
|
|
try:
|
|
tasks = {}
|
|
for task_id in tasklist:
|
|
tasks[task_id] = TaskWatcher(task_id,session,quiet=quiet)
|
|
while True:
|
|
all_done = True
|
|
for task_id,task in tasks.items():
|
|
changed = task.update()
|
|
if not task.is_done():
|
|
all_done = False
|
|
else:
|
|
if changed:
|
|
# task is done and state just changed
|
|
if not quiet:
|
|
display_tasklist_status(tasks)
|
|
if not task.is_success():
|
|
rv = 1
|
|
for child in session.getTaskChildren(task_id):
|
|
child_id = child['id']
|
|
if not child_id in tasks.keys():
|
|
tasks[child_id] = TaskWatcher(child_id, session, task.level + 1, quiet=quiet)
|
|
tasks[child_id].update()
|
|
# If we found new children, go through the list again,
|
|
# in case they have children also
|
|
all_done = False
|
|
if all_done:
|
|
if not quiet:
|
|
print
|
|
display_task_results(tasks)
|
|
break
|
|
|
|
time.sleep(options.poll_interval)
|
|
except (KeyboardInterrupt):
|
|
if tasks and not quiet:
|
|
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, opts):
|
|
global options
|
|
print "Watching logs (this may be safely interrupted)..."
|
|
def _isDone(session, taskId):
|
|
info = session.getTaskInfo(taskId)
|
|
if info is None:
|
|
print "No such task id: %i" % taskId
|
|
sys.exit(1)
|
|
state = koji.TASK_STATES[info['state']]
|
|
return (state in ['CLOSED','CANCELED','FAILED'])
|
|
|
|
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 opts.log:
|
|
logs = [filename for filename in output if filename == opts.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(options.poll_interval)
|
|
except (KeyboardInterrupt):
|
|
pass
|
|
|
|
def handle_add_group(options, session, args):
|
|
"[admin] Add a group to a tag"
|
|
usage = _("usage: %prog add-group <tag> <group>")
|
|
usage += _("\n(Specify the --help global option for a list of other help options)")
|
|
parser = OptionParser(usage=usage)
|
|
(options, args) = parser.parse_args(args)
|
|
if len(args) != 2:
|
|
parser.error(_("Please specify a tag name and a group name"))
|
|
assert False
|
|
tag = args[0]
|
|
group = args[1]
|
|
|
|
activate_session(session)
|
|
if not session.hasPerm('admin'):
|
|
print "This action requires admin privileges"
|
|
return 1
|
|
|
|
dsttag = session.getTag(tag)
|
|
if not dsttag:
|
|
print "Unknown tag: %s" % tag
|
|
return 1
|
|
|
|
groups = dict([(p['name'], p['group_id']) for p in session.getTagGroups(tag, inherit=False)])
|
|
group_id = groups.get(group, None)
|
|
if group_id is not None:
|
|
print "Group %s already exists for tag %s" % (group, tag)
|
|
return 1
|
|
|
|
session.groupListAdd(tag, group)
|
|
|
|
def handle_add_host(options, session, args):
|
|
"[admin] Add a host"
|
|
usage = _("usage: %prog add-host [options] hostname arch [arch2 ...]")
|
|
usage += _("\n(Specify the --help global option for a list of other help options)")
|
|
parser = OptionParser(usage=usage)
|
|
parser.add_option("--krb-principal", help=_("set a non-default kerberos principal for the host"))
|
|
(options, args) = parser.parse_args(args)
|
|
if len(args) < 2:
|
|
parser.error(_("Please specify a hostname and at least one arch"))
|
|
assert False
|
|
host = args[0]
|
|
activate_session(session)
|
|
id = session.getHost(host)
|
|
if id:
|
|
print "%s is already in the database" % host
|
|
return 1
|
|
else:
|
|
kwargs = {}
|
|
if options.krb_principal is not None:
|
|
kwargs['krb_principal'] = options.krb_principal
|
|
id = session.addHost(host, args[1:], **kwargs)
|
|
if id:
|
|
print "%s added: id %d" % (host, id)
|
|
|
|
def handle_edit_host(options, session, args):
|
|
"[admin] Edit a host"
|
|
usage = _("usage: %prog edit-host hostname ... [options]")
|
|
usage += _("\n(Specify the --help global option for a list of other help options)")
|
|
parser = OptionParser(usage=usage)
|
|
parser.add_option("--arches", help=_("Space-separated list of supported architectures"))
|
|
parser.add_option("--capacity", type="float", help=_("Capacity of this host"))
|
|
parser.add_option("--description", metavar="DESC", help=_("Description of this host"))
|
|
parser.add_option("--comment", help=_("A brief comment about this host"))
|
|
(subopts, args) = parser.parse_args(args)
|
|
if not args:
|
|
parser.error(_("Please specify a hostname"))
|
|
|
|
activate_session(session)
|
|
|
|
vals = {}
|
|
for key, val in subopts.__dict__.items():
|
|
if val is not None:
|
|
vals[key] = val
|
|
|
|
session.multicall = True
|
|
for host in args:
|
|
session.getHost(host)
|
|
error = False
|
|
for host, [info] in zip(args, session.multiCall(strict=True)):
|
|
if not info:
|
|
print _("Host %s does not exist") % host
|
|
error = True
|
|
|
|
if error:
|
|
print _("No changes made, please correct the command line")
|
|
return 1
|
|
|
|
session.multicall = True
|
|
for host in args:
|
|
session.editHost(host, **vals)
|
|
for host, [result] in zip(args, session.multiCall(strict=True)):
|
|
if result:
|
|
print _("Edited %s") % host
|
|
else:
|
|
print _("No changes made to %s") % host
|
|
|
|
def handle_add_host_to_channel(options, session, args):
|
|
"[admin] Add a host to a channel"
|
|
usage = _("usage: %prog add-host-to-channel [options] hostname channel")
|
|
usage += _("\n(Specify the --help global option for a list of other help options)")
|
|
parser = OptionParser(usage=usage)
|
|
parser.add_option("--list", action="store_true", help=_("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)
|
|
if options.list:
|
|
for channel in session.listChannels():
|
|
print channel['name']
|
|
return
|
|
channel = args[1]
|
|
channelinfo = session.getChannel(channel)
|
|
if not channelinfo:
|
|
print "No such channel: %s" % channel
|
|
return 1
|
|
host = args[0]
|
|
hostinfo = session.getHost(host)
|
|
if not hostinfo:
|
|
print "No such host: %s" % host
|
|
return 1
|
|
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 = [c['name'] for c in session.listChannels(hostinfo['id'])]
|
|
|
|
channel = args[1]
|
|
if channel not in hostchannels:
|
|
print "Host %s is not a member of channel %s" % (host, channel)
|
|
return 1
|
|
|
|
session.removeHostFromChannel(host, channel)
|
|
|
|
def handle_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'])])
|
|
ret = 0
|
|
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)
|
|
ret = 1
|
|
if ret:
|
|
return ret
|
|
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)])
|
|
ret = 0
|
|
for package in args[1:]:
|
|
package_id = pkglist.get(package, None)
|
|
if package_id is None:
|
|
print "Package %s doesn't exist in tag %s" % (package, tag)
|
|
ret = 1
|
|
if ret:
|
|
return ret
|
|
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("--wait", action="store_true",
|
|
help=_("Wait on the build, even if running in the background"))
|
|
parser.add_option("--nowait", action="store_false", dest="wait",
|
|
help=_("Don't wait on build"))
|
|
parser.add_option("--quiet", action="store_true",
|
|
help=_("Do not print the task information"), default=options.quiet)
|
|
parser.add_option("--arch-override", help=_("Override build arches"))
|
|
parser.add_option("--repo-id", type="int", help=_("Use a specific repo"))
|
|
parser.add_option("--noprogress", action="store_true",
|
|
help=_("Do not display progress of the upload"))
|
|
parser.add_option("--background", action="store_true",
|
|
help=_("Run the build at a lower priority"))
|
|
(build_opts, args) = parser.parse_args(args)
|
|
if len(args) != 2:
|
|
parser.error(_("Exactly two arguments (a build target and a SCM URL or srpm file) are required"))
|
|
assert False
|
|
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
|
|
if not build_opts.quiet:
|
|
print "Uploading srpm: %s" % source
|
|
serverdir = _unique_path('cli-build')
|
|
if _running_in_bg() or build_opts.noprogress or build_opts.quiet:
|
|
callback = None
|
|
else:
|
|
callback = _progress_callback
|
|
session.uploadWrapper(source, serverdir, callback=callback)
|
|
print
|
|
source = "%s/%s" % (serverdir, os.path.basename(source))
|
|
task_id = session.build(source, target, opts, priority=priority)
|
|
if not build_opts.quiet:
|
|
print "Created task:", task_id
|
|
print "Task info: %s/taskinfo?taskID=%s" % (options.weburl, task_id)
|
|
if build_opts.wait or (build_opts.wait is None and not _running_in_bg()):
|
|
session.logout()
|
|
return watch_tasks(session, [task_id], quiet=build_opts.quiet)
|
|
else:
|
|
return
|
|
|
|
def handle_chain_build(options, session, args):
|
|
# XXX - replace handle_build with this, once chain-building has gotten testing
|
|
"Build 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'], strict=True)
|
|
if dest_tag['locked']:
|
|
parser.error(_("Destination tag %s is locked" % dest_tag['name']))
|
|
|
|
# check that the destination tag is in the inheritance tree of the build tag
|
|
# otherwise there is no way that a chain-build can work
|
|
ancestors = session.getFullInheritance(build_target['build_tag'])
|
|
if dest_tag['id'] not in [build_target['build_tag']] + [ancestor['parent_id'] for ancestor in ancestors]:
|
|
print _("Packages in destination tag %(dest_tag_name)s are not inherited by build tag %(build_tag_name)s" % build_target)
|
|
print _("Target %s is not usable for a chain-build" % build_target['name'])
|
|
return 1
|
|
|
|
sources = args[1:]
|
|
|
|
src_list = []
|
|
build_level = []
|
|
#src_lists is a list of lists of sources to build.
|
|
# each list is block of builds ("build level") which must all be completed
|
|
# before the next block begins. Blocks are separated on the command line with ':'
|
|
for src in sources:
|
|
if src == ':':
|
|
if build_level:
|
|
src_list.append(build_level)
|
|
build_level = []
|
|
elif '://' in src:
|
|
# quick check that src might be a url
|
|
build_level.append(src)
|
|
elif '/' not in src and not src.endswith('.rpm') and len(src.split('-')) >= 3:
|
|
# quick check that it looks like a N-V-R
|
|
build_level.append(src)
|
|
else:
|
|
print _('"%s" is not a SCM URL or package N-V-R' % src)
|
|
return 1
|
|
if build_level:
|
|
src_list.append(build_level)
|
|
|
|
if len(src_list) < 2:
|
|
parser.error(_('You must specify at least one dependency between builds with : (colon)\nIf there are no dependencies, use the build command instead'))
|
|
|
|
priority = None
|
|
if build_opts.background:
|
|
#relative to koji.PRIO_DEFAULT
|
|
priority = 5
|
|
|
|
task_id = session.chainBuild(src_list, target, priority=priority)
|
|
|
|
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],quiet=options.quiet)
|
|
|
|
def handle_maven_build(options, session, args):
|
|
"Build a Maven package from source"
|
|
usage = _("usage: %prog maven-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("--patches", action="store", metavar="URL",
|
|
help=_("SCM URL of a directory containing patches to apply to the sources before building"))
|
|
parser.add_option("-P", "--profile", action="append",
|
|
dest="profiles", metavar="PROFILE", default=[],
|
|
help=_("Enable a profile for the Maven build"))
|
|
parser.add_option("-D", "--property", action="append",
|
|
dest="properties", metavar="NAME=VALUE", default=[],
|
|
help=_("Pass a system property to the Maven build"))
|
|
parser.add_option("-J", "--jvm-option", action="append",
|
|
dest="jvm_options", metavar="OPTION", default=[],
|
|
help=_("Pass a command-line option to the JVM"))
|
|
parser.add_option("--specfile", action="store", metavar="URL",
|
|
help=_("SCM URL of a spec file fragment to use to generate wrapper RPMs"))
|
|
parser.add_option("--skip-tag", action="store_true",
|
|
help=_("Do not attempt to tag package"))
|
|
parser.add_option("--scratch", action="store_true",
|
|
help=_("Perform a scratch build"))
|
|
parser.add_option("--nowait", action="store_true",
|
|
help=_("Don't wait on build"))
|
|
parser.add_option("--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) 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'] and not build_opts.scratch:
|
|
parser.error(_("Destination tag %s is locked" % dest_tag['name']))
|
|
source = args[1]
|
|
if '://' not in source:
|
|
parser.error(_("Invalid SCM URL: %s" % source))
|
|
assert False
|
|
opts = {}
|
|
for key in ('skip_tag', 'scratch', 'specfile', 'patches', 'profiles', 'jvm_options'):
|
|
val = getattr(build_opts, key)
|
|
if val:
|
|
opts[key] = val
|
|
props = {}
|
|
for prop in build_opts.properties:
|
|
fields = prop.split('=', 1)
|
|
if len(fields) != 2:
|
|
parser.error(_("Properties must be in name=value format"))
|
|
assert False
|
|
props[fields[0]] = fields[1]
|
|
if props:
|
|
opts['properties'] = props
|
|
priority = None
|
|
if build_opts.background:
|
|
#relative to koji.PRIO_DEFAULT
|
|
priority = 5
|
|
task_id = session.mavenBuild(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],quiet=options.quiet)
|
|
|
|
def handle_wrapper_rpm(options, session, args):
|
|
"""[admin] Build wrapper rpms for any jars associated with the build. Any existing rpms will be deleted from the database and the filesystem."""
|
|
usage = _("usage: %prog wrapper-rpm [options] target build-id|n-v-r URL")
|
|
usage += _("\n(Specify the --help global option for a list of other help options)")
|
|
parser = OptionParser(usage=usage)
|
|
parser.add_option("--scratch", action="store_true", help=_("Perform a scratch build"))
|
|
parser.add_option("--nowait", action="store_true", help=_("Don't wait on build"))
|
|
parser.add_option("--background", action="store_true", help=_("Run the build at a lower priority"))
|
|
|
|
(build_opts, args) = parser.parse_args(args)
|
|
if len(args) < 3:
|
|
parser.error(_("You must provide a build target, a build ID or NVR, and a SCM URL to a specfile fragment"))
|
|
assert False
|
|
activate_session(session)
|
|
if not session.hasPerm('admin'):
|
|
print "This action requires admin privileges"
|
|
return 1
|
|
|
|
target = args[0]
|
|
build_id = args[1]
|
|
if build_id.isdigit():
|
|
build_id = int(build_id)
|
|
url = args[2]
|
|
priority = None
|
|
if build_opts.background:
|
|
priority = 5
|
|
opts = {}
|
|
if build_opts.scratch:
|
|
opts['scratch'] = True
|
|
task_id = session.wrapperRPM(build_id, url, target, priority, opts=opts)
|
|
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],quiet=options.quiet)
|
|
|
|
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 single task ID"))
|
|
assert False
|
|
activate_session(session)
|
|
taskID = int(args[0])
|
|
if not options.quiet:
|
|
print "Resubmitting the following task:"
|
|
_printTaskInfo(session, taskID, 0, False, True)
|
|
newID = session.resubmitTask(taskID)
|
|
if not options.quiet:
|
|
print "Resubmitted task %s as new task %s" % (taskID, newID)
|
|
if _running_in_bg() or options.nowait:
|
|
return
|
|
else:
|
|
session.logout()
|
|
return watch_tasks(session, [newID], quiet=options.quiet)
|
|
|
|
def handle_call(options, session, args):
|
|
"[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)
|
|
parser.add_option("--python", action="store_true", help=_("Use python syntax for values"))
|
|
parser.add_option("--kwargs", help=_("Specify keyword arguments as a dictionary (implies --python)"))
|
|
(options, args) = parser.parse_args(args)
|
|
if len(args) < 1:
|
|
parser.error(_("Please specify the name of the XML-RPC method"))
|
|
assert False
|
|
if options.kwargs:
|
|
options.python = True
|
|
if options.python and ast is None:
|
|
parser.error(_("The ast module is required to read python syntax"))
|
|
activate_session(session)
|
|
name = args[0]
|
|
non_kw = []
|
|
kw = {}
|
|
if options.python:
|
|
non_kw = [ast.literal_eval(a) for a in args[1:]]
|
|
if options.kwargs:
|
|
kw = ast.literal_eval(options.kwargs)
|
|
else:
|
|
for arg in args[1:]:
|
|
if arg.find('=') != -1:
|
|
key, value = arg.split('=', 1)
|
|
kw[key] = arg_filter(value)
|
|
else:
|
|
non_kw.append(arg_filter(arg))
|
|
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 a 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
|
|
return 1
|
|
if len(broots) > 1:
|
|
print _("Multiple buildroots found: %s" % [br['id'] for br in broots])
|
|
brootinfo = broots[-1]
|
|
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")
|
|
return 1
|
|
tag = session.getTag(options.tag)
|
|
if not tag:
|
|
parser.error(_("Invalid tag: %s" % options.tag))
|
|
arch = options.arch
|
|
config = session.getBuildConfig(tag['id'])
|
|
if not config:
|
|
print _("Could not get config info for tag: %(name)s") % tag
|
|
return 1
|
|
opts['tag_name'] = tag['name']
|
|
repo = session.getRepo(config['id'])
|
|
if not repo:
|
|
print _("Could not get a repo for tag: %(name)s") % tag
|
|
return 1
|
|
opts['repoid'] = repo['id']
|
|
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 one or more hosts as disabled"
|
|
usage = _("usage: %prog disable-host [options] hostname ...")
|
|
usage += _("\n(Specify the --help global option for a list of other help options)")
|
|
parser = OptionParser(usage=usage)
|
|
parser.add_option("--comment", help=_("Comment indicating why the host(s) are being disabled"))
|
|
(options, args) = parser.parse_args(args)
|
|
|
|
activate_session(session)
|
|
session.multicall = True
|
|
for host in args:
|
|
session.getHost(host)
|
|
error = False
|
|
for host, [id] in zip(args, session.multiCall(strict=True)):
|
|
if not id:
|
|
print "Host %s does not exist" % host
|
|
error = True
|
|
if error:
|
|
print "No changes made. Please correct the command line."
|
|
return 1
|
|
session.multicall = True
|
|
for host in args:
|
|
session.disableHost(host)
|
|
if options.comment is not None:
|
|
session.editHost(host, comment=options.comment)
|
|
session.multiCall(strict=True)
|
|
|
|
def handle_enable_host(options, session, args):
|
|
"[admin] Mark one or more hosts as enabled"
|
|
usage = _("usage: %prog enable-host [options] hostname ...")
|
|
usage += _("\n(Specify the --help global option for a list of other help options)")
|
|
parser = OptionParser(usage=usage)
|
|
parser.add_option("--comment", help=_("Comment indicating why the host(s) are being enabled"))
|
|
(options, args) = parser.parse_args(args)
|
|
|
|
activate_session(session)
|
|
session.multicall = True
|
|
for host in args:
|
|
session.getHost(host)
|
|
error = False
|
|
for host, [id] in zip(args, session.multiCall(strict=True)):
|
|
if not id:
|
|
print "Host %s does not exist" % host
|
|
error = True
|
|
if error:
|
|
print "No changes made. Please correct the command line."
|
|
return 1
|
|
session.multicall = True
|
|
for host in args:
|
|
session.enableHost(host)
|
|
if options.comment is not None:
|
|
session.editHost(host, comment=options.comment)
|
|
session.multiCall(strict=True)
|
|
|
|
def handle_import(options, session, args):
|
|
"[admin] Import externally built RPMs into the database"
|
|
usage = _("usage: %prog import [options] package [package...]")
|
|
usage += _("\n(Specify the --help global option for a list of other help options)")
|
|
parser = OptionParser(usage=usage)
|
|
parser.add_option("--link", action="store_true", help=_("Attempt to hardlink 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] <file> <tag>")
|
|
# 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','sigpgp','sourcepackage'))
|
|
if data['sourcepackage']:
|
|
data['arch'] = 'src'
|
|
sigkey = data['siggpg']
|
|
if not sigkey:
|
|
sigkey = data['sigpgp']
|
|
if not sigkey:
|
|
sigkey = ""
|
|
if not options.with_unsigned:
|
|
print _("Skipping unsigned package: %s" % path)
|
|
continue
|
|
else:
|
|
sigkey = koji.get_sigpacket_key_id(sigkey)
|
|
del data['siggpg']
|
|
del data['sigpgp']
|
|
rinfo = session.getRPM(data)
|
|
if not rinfo:
|
|
print "No such rpm in system: %(name)s-%(version)s-%(release)s.%(arch)s" % data
|
|
continue
|
|
if rinfo.get('external_repo_id'):
|
|
print "Skipping external rpm: %(name)s-%(version)s-%(release)s.%(arch)s@%(external_repo_name)s" %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] <signature-key> 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:
|
|
print "User %s does not exist" % options.user
|
|
return 1
|
|
perms = session.getUserPerms(user['id'])
|
|
else:
|
|
perms = [p['name'] for p in session.getAllPerms()]
|
|
for perm in perms:
|
|
print perm
|
|
|
|
def handle_add_user(options, session, args):
|
|
"[admin] Add a user"
|
|
usage = _("usage: %prog add-user username [options]")
|
|
usage += _("\n(Specify the --help global option for a list of other help options)")
|
|
parser = OptionParser(usage=usage)
|
|
parser.add_option("--principal", help=_("The Kerberos principal for this user"))
|
|
parser.add_option("--disable", help=_("Prohibit logins by this user"), action="store_true")
|
|
(options, args) = parser.parse_args(args)
|
|
if len(args) < 1:
|
|
parser.error(_("You must specify the username of the user to add"))
|
|
elif len(args) > 1:
|
|
parser.error(_("This command only accepts one argument (username)"))
|
|
username = args[0]
|
|
if options.disable:
|
|
status = koji.USER_STATUS['BLOCKED']
|
|
else:
|
|
status = koji.USER_STATUS['NORMAL']
|
|
activate_session(session)
|
|
user_id = session.createUser(username, status=status, krb_principal=options.principal)
|
|
print "Added user %s (%i)" % (username, user_id)
|
|
|
|
def handle_enable_user(options, session, args):
|
|
"[admin] Enable logins by a user"
|
|
usage = _("usage: %prog enable-user username")
|
|
usage += _("\n(Specify the --help global option for a list of other help options)")
|
|
parser = OptionParser(usage=usage)
|
|
(options, args) = parser.parse_args(args)
|
|
if len(args) < 1:
|
|
parser.error(_("You must specify the username of the user to enable"))
|
|
elif len(args) > 1:
|
|
parser.error(_("This command only accepts one argument (username)"))
|
|
username = args[0]
|
|
activate_session(session)
|
|
session.enableUser(username)
|
|
|
|
def handle_disable_user(options, session, args):
|
|
"[admin] Disable logins by a user"
|
|
usage = _("usage: %prog disable-user username")
|
|
usage += _("\n(Specify the --help global option for a list of other help options)")
|
|
parser = OptionParser(usage=usage)
|
|
(options, args) = parser.parse_args(args)
|
|
if len(args) < 1:
|
|
parser.error(_("You must specify the username of the user to disable"))
|
|
elif len(args) > 1:
|
|
parser.error(_("This command only accepts one argument (username)"))
|
|
username = args[0]
|
|
activate_session(session)
|
|
session.disableUser(username)
|
|
|
|
def handle_list_signed(options, session, args):
|
|
"[admin] List signed copies of rpms"
|
|
usage = _("usage: %prog list-signed [options]")
|
|
usage += _("\n(Specify the --help global option for a list of other help options)")
|
|
parser = OptionParser(usage=usage)
|
|
parser.add_option("--debug", action="store_true")
|
|
parser.add_option("--key", help=_("Only list RPMs signed with this key"))
|
|
parser.add_option("--build", help=_("Only list RPMs from this build"))
|
|
parser.add_option("--rpm", help=_("Only list signed copies for this RPM"))
|
|
parser.add_option("--tag", help=_("Only list RPMs within this tag"))
|
|
(options, args) = parser.parse_args(args)
|
|
activate_session(session)
|
|
qopts = {}
|
|
build_idx = {}
|
|
rpm_idx = {}
|
|
if options.key:
|
|
qopts['sigkey'] = options.key
|
|
if options.rpm:
|
|
rinfo = session.getRPM(options.rpm)
|
|
rpm_idx[rinfo['id']] = rinfo
|
|
if rinfo is None:
|
|
parser.error(_("No such RPM: %s") % options.rpm)
|
|
if rinfo.get('external_repo_id'):
|
|
print "External rpm: %(name)s-%(version)s-%(release)s.%(arch)s@%(external_repo_name)s" %rpminfo
|
|
return 1
|
|
qopts['rpm_id'] = rinfo['id']
|
|
if options.build:
|
|
binfo = session.getBuild(options.build)
|
|
build_idx[binfo['id']] = binfo
|
|
if binfo is None:
|
|
parser.error(_("No such build: %s") % options.rpm)
|
|
sigs = []
|
|
rpms = session.listRPMs(buildID=binfo['id'])
|
|
for rinfo in rpms:
|
|
rpm_idx[rinfo['id']] = rinfo
|
|
sigs += session.queryRPMSigs(rpm_id=rinfo['id'], **qopts)
|
|
else:
|
|
sigs = session.queryRPMSigs(**qopts)
|
|
if options.tag:
|
|
print "getting tag listing"
|
|
rpms, builds = session.listTaggedRPMS(options.tag, inherit=False, latest=False)
|
|
print "got tag listing"
|
|
tagged = {}
|
|
for binfo in builds:
|
|
build_idx.setdefault(binfo['id'], binfo)
|
|
for rinfo in rpms:
|
|
rpm_idx.setdefault(rinfo['id'], rinfo)
|
|
tagged[rinfo['id']] = 1
|
|
#Now figure out which sig entries actually have live copies
|
|
for sig in sigs:
|
|
rpm_id = sig['rpm_id']
|
|
sigkey = sig['sigkey']
|
|
if options.tag:
|
|
if tagged.get(rpm_id) is None:
|
|
continue
|
|
rinfo = rpm_idx.get(rpm_id)
|
|
if not rinfo:
|
|
rinfo = session.getRPM(rpm_id)
|
|
rpm_idx[rinfo['id']] = rinfo
|
|
binfo = build_idx.get(rinfo['build_id'])
|
|
if not binfo:
|
|
binfo = session.getBuild(rinfo['build_id'])
|
|
build_idx[binfo['id']] = binfo
|
|
binfo['name'] = binfo['package_name']
|
|
builddir = koji.pathinfo.build(binfo)
|
|
signedpath = "%s/%s" % (builddir, koji.pathinfo.signed(rinfo, sigkey))
|
|
if not os.path.exists(signedpath):
|
|
if options.debug:
|
|
print "No copy: %s" % signedpath
|
|
continue
|
|
print signedpath
|
|
|
|
def handle_import_in_place(options, session, args):
|
|
"[admin] Import RPMs that are already in place"
|
|
usage = _("usage: %prog import-in-place [options] package [package...]")
|
|
usage += _("\n(Specify the --help global option for a list of other help options)")
|
|
parser = OptionParser(usage=usage)
|
|
(options, args) = parser.parse_args(args)
|
|
if len(args) < 1:
|
|
parser.error(_("At least one package must be specified"))
|
|
assert False
|
|
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_import_archive(options, session, args):
|
|
"[admin] Import an archive file and associate it with a build"
|
|
usage = _("usage: %prog import-archive build-id|n-v-r /path/to/archive...")
|
|
usage += _("\n(Specify the --help global option for a list of other help options)")
|
|
parser = OptionParser(usage=usage)
|
|
parser.add_option("--noprogress", action="store_true",
|
|
help=_("Do not display progress of the upload"))
|
|
parser.add_option("--create-build", action="store_true", help=_("Auto-create builds as needed"))
|
|
parser.add_option("--type", help=_("The type of archive we're importing. Currently supported types: maven"))
|
|
parser.add_option("--type-info", help=_("Type-specific information to associate with the archives. "
|
|
"For Maven archives this should be a local path to a .pom file."))
|
|
(suboptions, args) = parser.parse_args(args)
|
|
|
|
if not len(args) > 1:
|
|
parser.error(_("You must specify a build ID or N-V-R and an archive to import"))
|
|
assert False
|
|
|
|
activate_session(session)
|
|
|
|
if not suboptions.type:
|
|
parser.error(_("You must specify an archive type"))
|
|
assert False
|
|
if suboptions.type == 'maven':
|
|
if not (session.hasPerm('maven-import') or session.hasPerm('admin')):
|
|
parser.error(_("This action requires the maven-import privilege"))
|
|
assert False
|
|
if not suboptions.type_info:
|
|
parser.error(_("--type-info must point to a .pom file when importing Maven archives"))
|
|
assert False
|
|
pom_info = koji.parse_pom(suboptions.type_info)
|
|
maven_info = koji.pom_to_maven_info(pom_info)
|
|
suboptions.type_info = maven_info
|
|
else:
|
|
parser.error(_("Unsupported archive type: %s" % suboptions.type))
|
|
assert False
|
|
|
|
needs_create = False
|
|
|
|
buildinfo = session.getBuild(arg_filter(args[0]))
|
|
if not buildinfo:
|
|
buildinfo = koji.parse_NVR(args[0])
|
|
if buildinfo['epoch'] == '':
|
|
buildinfo['epoch'] = None
|
|
else:
|
|
buildinfo['epoch'] = int(buildinfo['epoch'])
|
|
if suboptions.type == 'maven':
|
|
# --type-info should point to a local .pom file
|
|
session.createMavenBuild(buildinfo, suboptions.type_info)
|
|
else:
|
|
# should get caught above
|
|
assert False
|
|
|
|
for filepath in args[1:]:
|
|
filename = os.path.basename(filepath)
|
|
print "Uploading archive: %s" % filename
|
|
serverdir = _unique_path('cli-import')
|
|
if _running_in_bg() or suboptions.noprogress:
|
|
callback = None
|
|
else:
|
|
callback = _progress_callback
|
|
session.uploadWrapper(filepath, serverdir, callback=callback, blocksize=65536)
|
|
print
|
|
serverpath = "%s/%s" % (serverdir, filename)
|
|
session.importArchive(serverpath, buildinfo, suboptions.type, suboptions.type_info)
|
|
print "Imported: %s" % filename
|
|
|
|
def handle_grant_permission(options, session, args):
|
|
"[admin] Grant a permission to a user"
|
|
usage = _("usage: %prog grant-permission <permission> <user> [<user> ...]")
|
|
usage += _("\n(Specify the --help global option for a list of other help options)")
|
|
parser = OptionParser(usage=usage)
|
|
(options, args) = parser.parse_args(args)
|
|
if len(args) < 2:
|
|
parser.error(_("Please specify a permission and at least one user"))
|
|
assert False
|
|
activate_session(session)
|
|
perm = args[0]
|
|
names = args[1:]
|
|
users = []
|
|
for n in names:
|
|
user = session.getUser(n)
|
|
if user is None:
|
|
parser.error(_("No such user: %s" % n))
|
|
assert False
|
|
users.append(user)
|
|
for user in users:
|
|
session.grantPermission(user['name'], perm)
|
|
|
|
def handle_revoke_permission(options, session, args):
|
|
"[admin] Revoke a permission from a user"
|
|
usage = _("usage: %prog revoke-permission <permission> <user> [<user> ...]")
|
|
usage += _("\n(Specify the --help global option for a list of other help options)")
|
|
parser = OptionParser(usage=usage)
|
|
(options, args) = parser.parse_args(args)
|
|
if len(args) < 2:
|
|
parser.error(_("Please specify a permission and at least one user"))
|
|
assert False
|
|
activate_session(session)
|
|
perm = args[0]
|
|
names = args[1:]
|
|
users = []
|
|
for n in names:
|
|
user = session.getUser(n)
|
|
if user is None:
|
|
parser.error(_("No such user: %s" % n))
|
|
assert False
|
|
users.append(user)
|
|
for user in users:
|
|
session.revokePermission(user['name'], perm)
|
|
|
|
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"))
|
|
parser.add_option("--type", help=_("Show builds of the given type only. Currently supported types: maven"))
|
|
(options, args) = parser.parse_args(args)
|
|
if len(args) == 0:
|
|
parser.error(_("A tag name must be specified"))
|
|
assert False
|
|
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:
|
|
kwargs = {'package': pkg}
|
|
if options.type:
|
|
kwargs['type'] = options.type
|
|
data = session.getLatestBuilds(args[0], **kwargs)
|
|
if options.paths:
|
|
if options.type == 'maven':
|
|
for x in data:
|
|
x['path'] = pathinfo.mavenbuild(x, {'group_id': x['maven_group_id'],
|
|
'artifact_id': x['maven_artifact_id'],
|
|
'version': x['maven_version']})
|
|
fmt = "%(path)-40s %(tag_name)-20s %(maven_group_id)-20s %(maven_artifact_id)-20s %(owner_name)s"
|
|
else:
|
|
for x in data:
|
|
x['path'] = pathinfo.build(x)
|
|
fmt = "%(path)-40s %(tag_name)-20s %(owner_name)s"
|
|
else:
|
|
if options.type == 'maven':
|
|
fmt = "%(nvr)-40s %(tag_name)-20s %(maven_group_id)-20s %(maven_artifact_id)-20s %(owner_name)s"
|
|
else:
|
|
fmt = "%(nvr)-40s %(tag_name)-20s %(owner_name)s"
|
|
if not options.quiet:
|
|
if options.type == 'maven':
|
|
print "%-40s %-20s %-20s %-20s %s" % ("Build", "Tag", "Group Id", "Artifact Id", "Built by")
|
|
print "%s %s %s %s %s" % ("-"*40, "-"*20, "-"*20, "-"*20, "-"*16)
|
|
else:
|
|
print "%-40s %-20s %s" % ("Build","Tag","Built by")
|
|
print "%s %s %s" % ("-"*40, "-"*20, "-"*16)
|
|
options.quiet = True
|
|
|
|
output = [ fmt % x for x in data]
|
|
output.sort()
|
|
for line in output:
|
|
print line
|
|
|
|
def anon_handle_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("--type", help=_("Show builds of the given type only. Currently supported types: maven"))
|
|
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
|
|
if options.type:
|
|
opts['type'] = options.type
|
|
event = koji.util.eventFromOpts(session, options)
|
|
if event:
|
|
opts['event'] = event['id']
|
|
event['timestr'] = time.asctime(time.localtime(event['ts']))
|
|
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:
|
|
if options.type == 'maven':
|
|
for x in data:
|
|
x['path'] = pathinfo.mavenbuild(x, {'group_id': x['maven_group_id'],
|
|
'artifact_id': x['maven_artifact_id'],
|
|
'version': x['maven_version']})
|
|
fmt = "%(path)-40s %(tag_name)-20s %(maven_group_id)-20s %(maven_artifact_id)-20s %(owner_name)s"
|
|
else:
|
|
for x in data:
|
|
x['path'] = pathinfo.build(x)
|
|
fmt = "%(path)-40s %(tag_name)-20s %(owner_name)s"
|
|
else:
|
|
if options.type == 'maven':
|
|
fmt = "%(nvr)-40s %(tag_name)-20s %(maven_group_id)-20s %(maven_artifact_id)-20s %(owner_name)s"
|
|
else:
|
|
fmt = "%(nvr)-40s %(tag_name)-20s %(owner_name)s"
|
|
if not options.quiet:
|
|
if options.type == 'maven':
|
|
print "%-40s %-20s %-20s %-20s %s" % ("Build", "Tag", "Group Id", "Artifact Id", "Built by")
|
|
print "%s %s %s %s %s" % ("-"*40, "-"*20, "-"*20, "-"*20, "-"*16)
|
|
else:
|
|
print "%-40s %-20s %s" % ("Build","Tag","Built by")
|
|
print "%s %s %s" % ("-"*40, "-"*20, "-"*16)
|
|
|
|
output = [ fmt % x for x in data]
|
|
output.sort()
|
|
for line in output:
|
|
print line
|
|
|
|
def anon_handle_list_buildroot(options, session, args):
|
|
"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)
|
|
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] <tag> [group]")
|
|
usage += _("\n(Specify the --help global option for a list of other help options)")
|
|
parser = OptionParser(usage=usage)
|
|
(options, args) = parser.parse_args(args)
|
|
if len(args) < 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] <tag> <group> <pkg> [<pkg>...]")
|
|
usage += _("\n(Specify the --help global option for a list of other help options)")
|
|
parser = OptionParser(usage=usage)
|
|
(options, args) = parser.parse_args(args)
|
|
if len(args) < 3:
|
|
parser.error(_("You must specify a tag name, group name, and one or more package names"))
|
|
assert False
|
|
tag = args[0]
|
|
group = args[1]
|
|
activate_session(session)
|
|
for pkg in args[2:]:
|
|
session.groupPackageListAdd(tag, group, pkg)
|
|
|
|
def handle_block_group_pkg(options, session, args):
|
|
"[admin] Block a package from a group's package listing"
|
|
usage = _("usage: %prog block-group-pkg [options] <tag> <group> <pkg> [<pkg>...]")
|
|
usage += _("\n(Specify the --help global option for a list of other help options)")
|
|
parser = OptionParser(usage=usage)
|
|
(options, args) = parser.parse_args(args)
|
|
if len(args) < 3:
|
|
parser.error(_("You must specify a tag name, group name, and one or more package names"))
|
|
assert False
|
|
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] <tag> <group> <pkg> [<pkg>...]")
|
|
usage += _("\n(Specify the --help global option for a list of other help options)")
|
|
parser = OptionParser(usage=usage)
|
|
(options, args) = parser.parse_args(args)
|
|
if len(args) < 3:
|
|
parser.error(_("You must specify a tag name, group name, and one or more package names"))
|
|
assert False
|
|
tag = args[0]
|
|
group = args[1]
|
|
activate_session(session)
|
|
for pkg in args[2:]:
|
|
session.groupPackageListUnblock(tag, group, pkg)
|
|
|
|
def handle_add_group_req(options, session, args):
|
|
"[admin] Add a group to a group's required list"
|
|
usage = _("usage: %prog add-group-req [options] <tag> <target group> <required 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) != 3:
|
|
parser.error(_("You must specify a tag name and two group names"))
|
|
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] <tag> <group> <blocked req>")
|
|
usage += _("\n(Specify the --help global option for a list of other help options)")
|
|
parser = OptionParser(usage=usage)
|
|
(options, args) = parser.parse_args(args)
|
|
if len(args) != 3:
|
|
parser.error(_("You must specify a tag name and two group names"))
|
|
assert False
|
|
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] <tag> <group> <requirement>")
|
|
usage += _("\n(Specify the --help global option for a list of other help options)")
|
|
parser = OptionParser(usage=usage)
|
|
(options, args) = parser.parse_args(args)
|
|
if len(args) != 3:
|
|
parser.error(_("You must specify a tag name and two group names"))
|
|
assert False
|
|
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"))
|
|
parser.add_option("--event", type='int', metavar="EVENT#", help=_("query at event"))
|
|
parser.add_option("--ts", type='int', metavar="TIMESTAMP", help=_("query at timestamp"))
|
|
parser.add_option("--repo", type='int', metavar="REPO#", help=_("query at event for a repo"))
|
|
(options, args) = parser.parse_args(args)
|
|
if len(args) != 0:
|
|
parser.error(_("This command takes no arguments"))
|
|
assert False
|
|
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
|
|
event = koji.util.eventFromOpts(session, options)
|
|
if event:
|
|
opts['event'] = event['id']
|
|
event['timestr'] = time.asctime(time.localtime(event['ts']))
|
|
print "Querying at event %(id)i (%(timestr)s)" % event
|
|
data = session.listPackages(**opts)
|
|
if not data:
|
|
print "(no matching packages)"
|
|
return 1
|
|
if not options.quiet:
|
|
if allpkgs:
|
|
print "Package"
|
|
print '-'*23
|
|
else:
|
|
print "%-23s %-23s %-16s %-15s" % ('Package','Tag','Extra Arches','Owner')
|
|
print "%s %s %s %s" % ('-'*23,'-'*23,'-'*16,'-'*15)
|
|
for pkg in data:
|
|
if allpkgs:
|
|
print pkg['package_name']
|
|
else:
|
|
if not options.show_blocked and pkg.get('blocked',False):
|
|
continue
|
|
if 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] <n-v-r.a> [<n-v-r.a> ...]")
|
|
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] <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("--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
|
|
task = None
|
|
if info['task_id']:
|
|
task = session.getTaskInfo(info['task_id'], request=True)
|
|
taglist = []
|
|
for tag in session.listTags(build):
|
|
taglist.append(tag['name'])
|
|
info['arch'] = 'src'
|
|
info['state'] = koji.BUILD_STATES[info['state']]
|
|
print "BUILD: %(name)s-%(version)s-%(release)s [%(id)d]" % info
|
|
print "State: %(state)s" % info
|
|
print "Built by: %(owner_name)s" % info
|
|
if task:
|
|
print "Task: %s %s" % (task['id'], koji.taskLabel(task))
|
|
else:
|
|
print "Task: none"
|
|
print "Finished: %s" % koji.formatTimeLong(info['completion_time'])
|
|
maven_info = session.getMavenBuild(info['id'])
|
|
if maven_info:
|
|
print "Maven groupId: %s" % maven_info['group_id']
|
|
print "Maven artifactId: %s" % maven_info['artifact_id']
|
|
print "Maven version: %s" % maven_info['version']
|
|
win_info = session.getWinBuild(info['id'])
|
|
if win_info:
|
|
print "Windows build platform: %s" % win_info['platform']
|
|
print "Tags: %s" % ' '.join(taglist)
|
|
maven_archives = session.listArchives(buildID=info['id'], type='maven')
|
|
if maven_archives:
|
|
print "Maven archives:"
|
|
for archive in maven_archives:
|
|
print os.path.join(koji.pathinfo.mavenbuild(info, maven_info), archive['filename'])
|
|
win_archives = session.listArchives(buildID=info['id'], type='win')
|
|
if win_archives:
|
|
print "Windows archives:"
|
|
for archive in win_archives:
|
|
print os.path.join(koji.pathinfo.winbuild(info), koji.pathinfo.winfile(archive))
|
|
rpms = session.listRPMs(buildID=info['id'])
|
|
if rpms:
|
|
print "RPMs:"
|
|
for rpm in rpms:
|
|
print os.path.join(koji.pathinfo.build(info), koji.pathinfo.rpm(rpm))
|
|
if options.changelog:
|
|
changelog = session.getChangelogEntries(info['id'])
|
|
if changelog:
|
|
print "Changelog:"
|
|
print koji.util.formatChangelog(changelog)
|
|
|
|
def handle_clone_tag(options, session, args):
|
|
"[admin] Duplicate the contents of one tag onto another tag"
|
|
usage = _("usage: %prog clone-tag [options] <src-tag> <dst-tag>")
|
|
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: <src-tag> <dst-tag>"))
|
|
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 <dest-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] <tag>")
|
|
usage += _("\n(Specify the --help global option for a list of other help options)")
|
|
parser = OptionParser(usage=usage)
|
|
parser.add_option("--reverse", action="store_true", help=_("Process tag's children instead of its parents"))
|
|
parser.add_option("--stop", help=_("Stop processing inheritance at this tag"))
|
|
parser.add_option("--jump", help=_("Jump from one tag to another when processing inheritance"))
|
|
parser.add_option("--event", type='int', metavar="EVENT#", help=_("query at event"))
|
|
parser.add_option("--ts", type='int', metavar="TIMESTAMP", help=_("query at timestamp"))
|
|
parser.add_option("--repo", type='int', metavar="REPO#", help=_("query at event for a repo"))
|
|
(options, args) = parser.parse_args(args)
|
|
if len(args) != 1:
|
|
parser.error(_("This command takes exctly one argument: a tag name or ID"))
|
|
assert False
|
|
activate_session(session)
|
|
event = koji.util.eventFromOpts(session, options)
|
|
if event:
|
|
event['timestr'] = time.asctime(time.localtime(event['ts']))
|
|
print "Querying at event %(id)i (%(timestr)s)" % event
|
|
if event:
|
|
tag = session.getTag(args[0], event=event['id'])
|
|
else:
|
|
tag = session.getTag(args[0])
|
|
if not tag:
|
|
parser.error(_("Unknown tag: %s" % args[0]))
|
|
|
|
opts = {}
|
|
opts['reverse'] = options.reverse or False
|
|
opts['stops'] = {}
|
|
opts['jumps'] = {}
|
|
if event:
|
|
opts['event'] = event['id']
|
|
|
|
if options.jump:
|
|
match = re.match(r'^(.*)/(.*)$', options.jump)
|
|
if match:
|
|
tag1 = session.getTagID(match.group(1))
|
|
if not tag1:
|
|
parser.error(_("Unknown tag: %s" % match.group(1)))
|
|
tag2 = session.getTagID(match.group(2))
|
|
if not tag2:
|
|
parser.error(_("Unknown tag: %s" % match.group(2)))
|
|
opts['jumps'][str(tag1)] = tag2
|
|
|
|
if options.stop:
|
|
tag1 = session.getTagID(options.stop)
|
|
if not tag1:
|
|
parser.error(_("Unknown tag: %s" % options.stop))
|
|
opts['stops'] = {str(tag1): 1}
|
|
|
|
sys.stdout.write('%s (%i)\n' % (tag['name'], tag['id']))
|
|
data = session.getFullInheritance(tag['id'], **opts)
|
|
_printInheritance(data, None, opts['reverse'])
|
|
|
|
def anon_handle_list_tags(options, session, args):
|
|
"Print the list of tags"
|
|
usage = _("usage: %prog list-tags [options] [pattern]")
|
|
usage += _("\n(Specify the --help global option for a list of other help options)")
|
|
parser = OptionParser(usage=usage)
|
|
parser.add_option("--show-id", action="store_true", help=_("Show tag ids"))
|
|
parser.add_option("--verbose", action="store_true", help=_("Show more information"))
|
|
parser.add_option("--unlocked", action="store_true", help=_("Only show unlocked tags"))
|
|
parser.add_option("--build", help=_("Show tags associated with a build"))
|
|
parser.add_option("--package", help=_("Show tags associated with a package"))
|
|
(options, args) = parser.parse_args(args)
|
|
activate_session(session)
|
|
|
|
pkginfo = {}
|
|
buildinfo = {}
|
|
|
|
if options.package:
|
|
pkginfo = session.getPackage(options.package)
|
|
if not pkginfo:
|
|
parser.error(_("Invalid package %s" % options.package))
|
|
assert False
|
|
|
|
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]")
|
|
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 = "%(name)s-%(version)s-%(release)s untagged from %(tag_name)s"
|
|
if x.has_key('revoker_name'):
|
|
fmt += " by %(revoker_name)s"
|
|
elif event_id == x['create_event']:
|
|
ts = x['create_ts']
|
|
fmt = "%(name)s-%(version)s-%(release)s tagged into %(tag_name)s"
|
|
if x.has_key('creator_name'):
|
|
fmt += " by %(creator_name)s"
|
|
if x['active']:
|
|
fmt += " [still active]"
|
|
else:
|
|
raise koji.GenericError, "unknown event: (%r, %r)" % (event_id, x)
|
|
time_str = time.asctime(time.localtime(ts))
|
|
return "%s: %s" % (time_str, fmt % x)
|
|
for event_id, x in timeline:
|
|
if options.debug:
|
|
print "%r" % x
|
|
print _histline(event_id, x)
|
|
|
|
def _handleMap(lines, data, prefix=''):
|
|
for key, val in data.items():
|
|
if key != '__starstar':
|
|
lines.append(' %s%s: %s' % (prefix, key, val))
|
|
|
|
def _handleOpts(lines, opts, prefix=''):
|
|
if opts:
|
|
lines.append("%sOptions:" % prefix)
|
|
_handleMap(lines, opts, prefix)
|
|
|
|
def _parseTaskParams(session, method, task_id):
|
|
"""Parse the return of getTaskRequest()"""
|
|
params = session.getTaskRequest(task_id)
|
|
|
|
lines = []
|
|
|
|
if method == 'buildSRPMFromCVS':
|
|
lines.append("CVS URL: %s" % params[0])
|
|
elif method == 'buildSRPMFromSCM':
|
|
lines.append("SCM URL: %s" % params[0])
|
|
elif method == 'buildArch':
|
|
lines.append("SRPM: %s/work/%s" % (options.topdir, params[0]))
|
|
lines.append("Build Tag: %s" % session.getTag(params[1])['name'])
|
|
lines.append("Build Arch: %s" % params[2])
|
|
lines.append("SRPM Kept: %r" % params[3])
|
|
if len(params) > 4:
|
|
_handleOpts(lines, params[4])
|
|
elif method == 'tagBuild':
|
|
build = session.getBuild(params[1])
|
|
lines.append("Destination Tag: %s" % session.getTag(params[0])['name'])
|
|
lines.append("Build: %s" % koji.buildLabel(build))
|
|
elif method == 'buildNotification':
|
|
build = params[1]
|
|
buildTarget = params[2]
|
|
lines.append("Recipients: %s" % (", ".join(params[0])))
|
|
lines.append("Build: %s" % koji.buildLabel(build))
|
|
lines.append("Build Target: %s" % buildTarget['name'])
|
|
lines.append("Web URL: %s" % params[3])
|
|
elif method == 'build':
|
|
lines.append("Source: %s" % params[0])
|
|
lines.append("Build Target: %s" % params[1])
|
|
if len(params) > 2:
|
|
_handleOpts(lines, params[2])
|
|
elif method == 'maven':
|
|
lines.append("SCM URL: %s" % params[0])
|
|
lines.append("Build Target: %s" % params[1])
|
|
if len(params) > 2:
|
|
_handleOpts(lines, params[2])
|
|
elif method == 'buildMaven':
|
|
lines.append("SCM URL: %s" % params[0])
|
|
lines.append("Build Tag: %s" % params[1]['name'])
|
|
if len(params) > 2:
|
|
_handleOpts(lines, params[2])
|
|
elif method == 'wrapperRPM':
|
|
lines.append("Spec File URL: %s" % params[0])
|
|
lines.append("Build Tag: %s" % params[1]['name'])
|
|
if params[2]:
|
|
lines.append("Build: %s" % koji.buildLabel(params[2]))
|
|
if params[3]:
|
|
lines.append("Task: %s %s" % (params[3]['id'], koji.taskLabel(params[3])))
|
|
if len(params) > 4:
|
|
_handleOpts(lines, params[4])
|
|
elif method == 'winbuild':
|
|
lines.append("VM: %s" % params[0])
|
|
lines.append("SCM URL: %s" % params[1])
|
|
lines.append("Build Target: %s" % params[2])
|
|
if len(params) > 3:
|
|
_handleOpts(lines, params[3])
|
|
elif method == 'vmExec':
|
|
lines.append("VM: %s" % params[0])
|
|
lines.append("Exec Params:")
|
|
for info in params[1]:
|
|
if isinstance(info, dict):
|
|
_handleMap(lines, info, prefix=' ')
|
|
else:
|
|
lines.append(" %s" % info)
|
|
if len(params) > 2:
|
|
_handleOpts(lines, params[2])
|
|
elif method in ('createLiveCD', 'createAppliance'):
|
|
lines.append("Arch: %s" % params[0])
|
|
lines.append("Build Target: %s" % params[1])
|
|
lines.append("Kickstart File: %s" % params[2])
|
|
if len(params) > 3:
|
|
_handleOpts(lines, params[3])
|
|
elif method == 'newRepo':
|
|
tag = session.getTag(params[0])
|
|
lines.append("Tag: %s" % tag['name'])
|
|
elif method == 'prepRepo':
|
|
lines.append("Tag: %s" % params[0]['name'])
|
|
elif method == 'createrepo':
|
|
lines.append("Repo ID: %i" % params[0])
|
|
lines.append("Arch: %s" % params[1])
|
|
oldrepo = params[2]
|
|
if oldrepo:
|
|
lines.append("Old Repo ID: %i" % oldrepo['id'])
|
|
lines.append("Old Repo Creation: %s" % koji.formatTimeLong(oldrepo['creation_time']))
|
|
if len(params) > 3:
|
|
lines.append("External Repos: %s" % ', '.join([ext['external_repo_name'] for ext in params[3]]))
|
|
elif method == 'tagNotification':
|
|
destTag = session.getTag(params[2])
|
|
srcTag = None
|
|
if params[3]:
|
|
srcTag = session.getTag(params[3])
|
|
build = session.getBuild(params[4])
|
|
user = session.getUser(params[5])
|
|
|
|
lines.append("Recipients: %s" % ", ".join(params[0]))
|
|
lines.append("Successful?: %s" % (params[1] and 'yes' or 'no'))
|
|
lines.append("Tagged Into: %s" % destTag['name'])
|
|
if srcTag:
|
|
lines.append("Moved From: %s" % srcTag['name'])
|
|
lines.append("Build: %s" % koji.buildLabel(build))
|
|
lines.append("Tagged By: %s" % user['name'])
|
|
lines.append("Ignore Success?: %s" % (params[6] and 'yes' or 'no'))
|
|
if params[7]:
|
|
lines.append("Failure Message: %s" % params[7])
|
|
elif method == 'dependantTask':
|
|
lines.append("Dependant Tasks: %s" % ", ".join([str(depID) for depID in params[0]]))
|
|
lines.append("Subtasks:")
|
|
for subtask in params[1]:
|
|
lines.append(" Method: %s" % subtask[0])
|
|
lines.append(" Parameters: %s" % ", ".join([str(subparam) for subparam in subtask[1]]))
|
|
if len(subtask) > 2 and subtask[2]:
|
|
subopts = subtask[2]
|
|
_handleOpts(lines, subopts, prefix=' ')
|
|
lines.append("")
|
|
elif method == 'chainbuild':
|
|
lines.append("Build Groups:")
|
|
group_num = 0
|
|
for group_list in params[0]:
|
|
group_num += 1
|
|
lines.append(" %i: %s" % (group_num, ', '.join(group_list)))
|
|
lines.append("Build Target: %s" % params[1])
|
|
if len(params) > 2:
|
|
_handleOpts(lines, params[2])
|
|
elif method == 'waitrepo':
|
|
lines.append("Build Target: %s" % params[0])
|
|
if params[1]:
|
|
lines.append("Newer Than: %s" % params[1])
|
|
if params[2]:
|
|
lines.append("NVRs: %s" % ', '.join(params[2]))
|
|
|
|
return lines
|
|
|
|
def _printTaskInfo(session, task_id, level=0, recurse=True, verbose=True):
|
|
"""Recursive function to print information about a task
|
|
and its children."""
|
|
|
|
BUILDDIR = '/var/lib/mock'
|
|
indent = " "*2*level
|
|
|
|
info = session.getTaskInfo(task_id)
|
|
if info['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())
|
|
print "%sCreated: %s" % (indent, time.asctime(time.localtime(info['create_ts'])))
|
|
if info.get('start_ts'):
|
|
print "%sStarted: %s" % (indent, time.asctime(time.localtime(info['start_ts'])))
|
|
if info.get('completion_ts'):
|
|
print "%sFinished: %s" % (indent, time.asctime(time.localtime(info['completion_ts'])))
|
|
if host_info:
|
|
print "%sHost: %s" % (indent, host_info['name'])
|
|
if build_info:
|
|
print "%sBuild: %s (%d)" % (indent, build_info[0]['nvr'], build_info[0]['build_id'])
|
|
if buildroot_infos:
|
|
print "%sBuildroots:" % indent
|
|
for root in buildroot_infos:
|
|
print "%s %s/%s-%d-%d/" % (indent, BUILDDIR, root['tag_name'], root['id'], root['repo_id'])
|
|
if logs:
|
|
print "%sLog Files:" % indent
|
|
for log 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
|
|
print
|
|
|
|
if recurse:
|
|
level += 1
|
|
children = session.getTaskChildren(task_id, request=True)
|
|
children.sort(cmp=lambda a, b: cmp(a['id'], b['id']))
|
|
for child in children:
|
|
_printTaskInfo(session, child['id'], level, verbose=verbose)
|
|
|
|
def anon_handle_taskinfo(options, session, args):
|
|
"""Show information about a task"""
|
|
usage = _("usage: %prog taskinfo [options] taskID [taskID...]")
|
|
usage += _("\n(Specify the --help global option for a list of other help options)")
|
|
parser = OptionParser(usage=usage)
|
|
parser.add_option("-r", "--recurse", action="store_true", help=_("Show children of this task as well"))
|
|
parser.add_option("-v", "--verbose", action="store_true", help=_("Be verbose"))
|
|
(options, args) = parser.parse_args(args)
|
|
if len(args) < 1:
|
|
parser.error(_("You must specify at least one task ID"))
|
|
assert False
|
|
|
|
activate_session(session)
|
|
|
|
for arg in args:
|
|
task_id = int(arg)
|
|
_printTaskInfo(session, task_id, 0, options.recurse, options.verbose)
|
|
|
|
def anon_handle_taginfo(options, session, args):
|
|
"Print basic information about a tag"
|
|
usage = _("usage: %prog taginfo [options] <tag> [<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(_("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'] is not None:
|
|
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"))
|
|
parser.add_option("--maven-support", action="store_true", help=_("Enable creation of Maven repos for this tag"))
|
|
parser.add_option("--include-all", action="store_true", help=_("Include all packages in this tag when generating Maven repos"))
|
|
(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())
|
|
if options.maven_support:
|
|
opts['maven_support'] = True
|
|
if options.include_all:
|
|
opts['maven_include_all'] = True
|
|
session.createTag(args[0],**opts)
|
|
|
|
def handle_edit_tag(options, session, args):
|
|
"[admin] Alter tag information"
|
|
usage = _("usage: %prog edit-tag [options] name")
|
|
usage += _("\n(Specify the --help global option for a list of other help options)")
|
|
parser = OptionParser(usage=usage)
|
|
parser.add_option("--arches", help=_("Specify arches"))
|
|
parser.add_option("--perm", help=_("Specify permission requirement"))
|
|
parser.add_option("--no-perm", action="store_true", help=_("Remove permission requirement"))
|
|
parser.add_option("--lock", action="store_true", help=_("Lock the tag"))
|
|
parser.add_option("--unlock", action="store_true", help=_("Unlock the tag"))
|
|
parser.add_option("--rename", help=_("Rename the tag"))
|
|
parser.add_option("--maven-support", action="store_true", help=_("Enable creation of Maven repos for this tag"))
|
|
parser.add_option("--no-maven-support", action="store_true", help=_("Disable creation of Maven repos for this tag"))
|
|
parser.add_option("--include-all", action="store_true", help=_("Include all packages in this tag when generating Maven repos"))
|
|
parser.add_option("--no-include-all", action="store_true", help=_("Do not include all packages in this tag when generating Maven repos"))
|
|
(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
|
|
if options.maven_support:
|
|
opts['maven_support'] = True
|
|
if options.no_maven_support:
|
|
opts['maven_support'] = False
|
|
if options.include_all:
|
|
opts['maven_include_all'] = True
|
|
if options.no_include_all:
|
|
opts['maven_include_all'] = False
|
|
#XXX change callname
|
|
session.editTag2(tag,**opts)
|
|
|
|
def handle_lock_tag(options, session, args):
|
|
"[admin] Lock a tag"
|
|
usage = _("usage: %prog lock-tag [options] <tag> [<tag> ...] ")
|
|
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] <tag> [<tag> ...]")
|
|
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 <parent> <priority>")
|
|
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 <parent> <priority>")
|
|
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] <tag>")
|
|
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
|
|
return 0
|
|
tags = current_tags
|
|
if delete:
|
|
#removing entirely
|
|
if current_tags and not options.force:
|
|
print _("Error: external repo %s used by tag(s): %s") % (repo, ', '.join(current_tags))
|
|
print _("Use --force to remove anyway")
|
|
return 1
|
|
session.deleteExternalRepo(args[0])
|
|
else:
|
|
for tag in tags:
|
|
if not tag in current_tags:
|
|
print _("External repo %s not associated with tag %s") % (repo, tag)
|
|
continue
|
|
session.removeExternalRepoFromTag(tag, repo)
|
|
|
|
# This handler is for spinning livecd images
|
|
#
|
|
def handle_spin_livecd(options, session, args):
|
|
"""[admin] Create a live CD image given a kickstart file"""
|
|
|
|
# Usage & option parsing.
|
|
usage = _("usage: %prog spin-livecd [options] <target> <arch> " +
|
|
"<kickstart-file>")
|
|
usage += _("\n(Specify the --help global option for a list of other " +
|
|
"help options)")
|
|
parser = OptionParser(usage=usage)
|
|
parser.add_option("--wait", action="store_true",
|
|
help=_("Wait on the livecd creation, even if running in the background"))
|
|
parser.add_option("--nowait", action="store_false", dest="wait",
|
|
help=_("Don't wait on livecd creation"))
|
|
parser.add_option("--noprogress", action="store_true",
|
|
help=_("Do not display progress of the upload"))
|
|
parser.add_option("--background", action="store_true",
|
|
help=_("Run the livecd creation task at a lower priority"))
|
|
parser.add_option("--isoname",
|
|
help=_("Use a custom name for the iso file"))
|
|
parser.add_option("--ksurl", metavar="SCMURL",
|
|
help=_("The URL to the SCM containing the kickstart file"))
|
|
parser.add_option("--ksversion", metavar="VERSION",
|
|
help=_("The syntax version used in the kickstart file"))
|
|
parser.add_option("--scratch", action="store_true",
|
|
help=_("Create a scratch LiveCD image"))
|
|
parser.add_option("--repo",
|
|
help=_("Specify a comma-separated list of repos that will override " +
|
|
"the repo used to install RPMs in the LiveCD image. The " +
|
|
"build tag repo associated with the target is the default."))
|
|
(task_options, args) = parser.parse_args(args)
|
|
|
|
# Make sure the target and kickstart is specified.
|
|
if len(args) != 3:
|
|
parser.error(_("Three arguments are required: an architecture, " +
|
|
"a build target, and a relative path to a " +
|
|
"kickstart file."))
|
|
assert False
|
|
_build_image(options, task_options, session, args, 'livecd')
|
|
|
|
# This handler is for spinning appliance images
|
|
#
|
|
def handle_spin_appliance(options, session, args):
|
|
"""[admin] Create an appliance given a kickstart file"""
|
|
|
|
# Usage & option parsing
|
|
usage = _("usage: %prog spin-appliance [options] <target> <arch> " +
|
|
"<kickstart-file>")
|
|
usage += _("\n(Specify the --help global option for a list of other " +
|
|
"help options)")
|
|
parser = OptionParser(usage=usage)
|
|
parser.add_option("--wait", action="store_true",
|
|
help=_("Wait on the appliance creation, even if running in the background"))
|
|
parser.add_option("--nowait", action="store_false", dest="wait",
|
|
help=_("Don't wait on appliance creation"))
|
|
parser.add_option("--noprogress", action="store_true",
|
|
help=_("Do not display progress of the upload"))
|
|
parser.add_option("--background", action="store_true",
|
|
help=_("Run the appliance creation task at a lower priority"))
|
|
parser.add_option("--ksurl", metavar="SCMURL",
|
|
help=_("The URL to the SCM containing the kickstart file"))
|
|
parser.add_option("--ksversion", metavar="VERSION",
|
|
help=_("The syntax version used in the kickstart file"))
|
|
parser.add_option("--scratch", action="store_true",
|
|
help=_("Create a scratch appliance"))
|
|
parser.add_option("--repo",
|
|
help=_("Specify a comma-separated list of repos that will override " +
|
|
"the repo used to install RPMs in the appliance. The " +
|
|
"build tag repo associated with the target is the default."))
|
|
parser.add_option("--name", metavar="NAME", default=None,
|
|
help=_("Specify the appliance file name"))
|
|
parser.add_option("--version", metavar="VERSION", default=None,
|
|
help=_("Set an appliance version, used in .xml metadata"))
|
|
parser.add_option("--release", metavar="RELEASE", default=None,
|
|
help=_("Set an appliance release number, used in .xml metadata"))
|
|
parser.add_option("--vmem", metavar="VMEM", default=None,
|
|
help=_("Set the amount of virtual memory in the appliance in MB, " +
|
|
"default is 512"))
|
|
parser.add_option("--vcpu", metavar="VCPU", default=None,
|
|
help=_("Set the number of virtual cpus in the appliance, " +
|
|
"default is 1"))
|
|
parser.add_option("--format", metavar="DISK_FORMAT", default='raw',
|
|
help=_("Disk format, default is raw. Other options are qcow, " +
|
|
"qcow2, and vmx."))
|
|
|
|
(task_options, args) = parser.parse_args(args)
|
|
|
|
# Make sure the target and kickstart is specified.
|
|
if len(args) != 3:
|
|
parser.error(_("Three arguments are required: an architecture, " +
|
|
"a build target, and a relative path to a " +
|
|
"kickstart file."))
|
|
assert False
|
|
_build_image(options, task_options, session, args, 'appliance')
|
|
|
|
def _build_image(options, task_opts, session, args, img_type):
|
|
"""
|
|
A private helper function that houses common CLI code for building
|
|
images.
|
|
"""
|
|
|
|
if img_type not in ('livecd', 'appliance'):
|
|
raise koji.GenericError, 'Unrecognized image type: %s' % img_type
|
|
activate_session(session)
|
|
|
|
# Set the task's priority. Users can only lower it with --background.
|
|
priority = None
|
|
if task_opts.background:
|
|
# relative to koji.PRIO_DEFAULT; higher means a "lower" priority.
|
|
priority = 5
|
|
if _running_in_bg() or task_opts.noprogress:
|
|
callback = None
|
|
else:
|
|
callback = _progress_callback
|
|
|
|
# We do some early sanity checking of the given target.
|
|
# Kojid gets these values again later on, but we check now as a convenience
|
|
# for the user.
|
|
target = args[0]
|
|
tmp_target = session.getBuildTarget(target)
|
|
if not tmp_target:
|
|
raise koji.GenericError, _("Unknown build target: %s" % target)
|
|
dest_tag = session.getTag(tmp_target['dest_tag'])
|
|
if not dest_tag:
|
|
raise koji.GenericError, _("Unknown destination tag: %s" %
|
|
tmp_target['dest_tag_name'])
|
|
|
|
# Set the architecture
|
|
arch = koji.canonArch(args[1])
|
|
|
|
# Upload the KS file to the staging area.
|
|
# If it's a URL, it's kojid's job to go get it when it does the checkout.
|
|
ksfile = args[2]
|
|
|
|
if not task_opts.ksurl:
|
|
serverdir = _unique_path('cli-' + img_type)
|
|
session.uploadWrapper(ksfile, serverdir, callback=callback)
|
|
ksfile = os.path.join(serverdir, os.path.basename(ksfile))
|
|
print
|
|
|
|
hub_opts = {}
|
|
for opt in ('isoname', 'ksurl', 'ksversion', 'scratch', 'repo',
|
|
'name', 'version', 'release', 'vmem', 'vcpu', 'format'):
|
|
val = getattr(task_opts, opt, None)
|
|
if val is not None:
|
|
hub_opts[opt] = val
|
|
|
|
# finally, create the task.
|
|
task_id = session.buildImage(arch, target, ksfile, img_type,
|
|
opts=hub_opts, priority=priority)
|
|
|
|
if not options.quiet:
|
|
print "Created task:", task_id
|
|
print "Task info: %s/taskinfo?taskID=%s" % (options.weburl, task_id)
|
|
if task_opts.wait or (task_opts.wait is None and not _running_in_bg()):
|
|
session.logout()
|
|
return watch_tasks(session, [task_id], quiet=options.quiet)
|
|
else:
|
|
return
|
|
|
|
def handle_win_build(options, session, args):
|
|
"""Build a Windows package from source"""
|
|
# Usage & option parsing
|
|
usage = _("usage: %prog win-build [options] target URL VM")
|
|
usage += _("\n(Specify the --help global option for a list of other " +
|
|
"help options)")
|
|
parser = OptionParser(usage=usage)
|
|
parser.add_option("--winspec", metavar="URL",
|
|
help=_("SCM URL to retrieve the build descriptor from. " + \
|
|
"If not specified, the winspec must be in the root directory " + \
|
|
"of the source repository."))
|
|
parser.add_option("--patches", metavar="URL",
|
|
help=_("SCM URL of a directory containing patches to apply " + \
|
|
"to the sources before building"))
|
|
parser.add_option("--cpus", type="int",
|
|
help=_("Number of cpus to allocate to the build VM " + \
|
|
"(requires admin access)"))
|
|
parser.add_option("--mem", type="int",
|
|
help=_("Amount of memory (in megabytes) to allocate to the build VM " + \
|
|
"(requires admin access)"))
|
|
parser.add_option("--specfile", metavar="URL",
|
|
help=_("SCM URL of a spec file fragment to use to generate wrapper RPMs"))
|
|
parser.add_option("--scratch", action="store_true",
|
|
help=_("Perform a scratch build"))
|
|
parser.add_option("--repo-id", type="int", help=_("Use a specific repo"))
|
|
parser.add_option("--skip-tag", action="store_true",
|
|
help=_("Do not attempt to tag package"))
|
|
parser.add_option("--background", action="store_true",
|
|
help=_("Run the build at a lower priority"))
|
|
parser.add_option("--wait", action="store_true",
|
|
help=_("Wait on the build, even if running in the background"))
|
|
parser.add_option("--nowait", action="store_false", dest="wait",
|
|
help=_("Don't wait on build"))
|
|
parser.add_option("--quiet", action="store_true",
|
|
help=_("Do not print the task information"), default=options.quiet)
|
|
(build_opts, args) = parser.parse_args(args)
|
|
if len(args) != 3:
|
|
parser.error(_("Exactly three arguments (a build target, a SCM URL, and a VM name) are required"))
|
|
assert False
|
|
activate_session(session)
|
|
target = args[0]
|
|
if target.lower() == "none" and build_opts.repo_id:
|
|
target = None
|
|
build_opts.skip_tag = True
|
|
else:
|
|
build_target = session.getBuildTarget(target)
|
|
if not build_target:
|
|
parser.error(_("Unknown build target: %s" % target))
|
|
dest_tag = session.getTag(build_target['dest_tag'])
|
|
if not dest_tag:
|
|
parser.error(_("Unknown destination tag: %s" % build_target['dest_tag_name']))
|
|
if dest_tag['locked'] and not build_opts.scratch:
|
|
parser.error(_("Destination tag %s is locked" % dest_tag['name']))
|
|
scmurl = args[1]
|
|
vm_name = args[2]
|
|
opts = {}
|
|
for key in ('winspec', 'patches', 'cpus', 'mem',
|
|
'specfile', 'scratch', 'repo_id', 'skip_tag'):
|
|
val = getattr(build_opts, key)
|
|
if val is not None:
|
|
opts[key] = val
|
|
priority = None
|
|
if build_opts.background:
|
|
#relative to koji.PRIO_DEFAULT
|
|
priority = 5
|
|
task_id = session.winBuild(vm_name, scmurl, target, opts, priority=priority)
|
|
if not build_opts.quiet:
|
|
print "Created task:", task_id
|
|
print "Task info: %s/taskinfo?taskID=%s" % (options.weburl, task_id)
|
|
if build_opts.wait or (build_opts.wait is None and not _running_in_bg()):
|
|
session.logout()
|
|
return watch_tasks(session, [task_id], quiet=build_opts.quiet)
|
|
else:
|
|
return
|
|
|
|
def handle_free_task(options, session, args):
|
|
"[admin] Free a task"
|
|
usage = _("usage: %prog free-task [options] <task-id> [<task-id> ...]")
|
|
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] <task-id|build> [<task-id|build> ...]")
|
|
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_set_task_priority(options, session, args):
|
|
"[admin] Set task priority"
|
|
usage = _("usage: %prog set-task-priority [options] --priority=<priority> <task-id> [task-id]...")
|
|
usage += _("\n(Specify the --help global option for a list of other help options)")
|
|
parser = OptionParser(usage=usage)
|
|
parser.add_option("--priority", type="int", help=_("New priority"))
|
|
parser.add_option("--recurse", action="store_true", default=False, help=_("Change priority of child tasks as well"))
|
|
(options, args) = parser.parse_args(args)
|
|
if len(args) == 0:
|
|
parser.error(_("You must specify at least one task id"))
|
|
assert False
|
|
|
|
if options.priority is None:
|
|
parser.error(_("You must specify --priority"))
|
|
assert False
|
|
try:
|
|
tasks = [int(a) for a in args]
|
|
except ValueError:
|
|
parser.error(_("Task numbers must be integers"))
|
|
|
|
activate_session(session)
|
|
|
|
for task_id in tasks:
|
|
session.setTaskPriority(task_id, options.priority, options.recurse)
|
|
|
|
def 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
|
|
return 1
|
|
opts = {'with_dups' : True}
|
|
old_user = None
|
|
if options.old_user:
|
|
old_user = session.getUser(options.old_user)
|
|
if not old_user:
|
|
print "No such user: %s" % options.old_user
|
|
return 1
|
|
opts['userID'] = old_user['id']
|
|
to_change = []
|
|
for package in packages:
|
|
entries = session.listPackages(pkgID=package, **opts)
|
|
if not entries:
|
|
print "No data for package %s" % package
|
|
continue
|
|
to_change.extend(entries)
|
|
if not packages and options.old_user:
|
|
entries = session.listPackages(**opts)
|
|
if not entries:
|
|
print "No data for user %s" % old_user['name']
|
|
return 1
|
|
to_change.extend(entries)
|
|
for entry in to_change:
|
|
if user['id'] == entry['owner_id']:
|
|
if options.verbose:
|
|
print "Preserving owner=%s for package %s in tag %s" \
|
|
% (user['name'], package, entry['tag_name'] )
|
|
else:
|
|
if options.test:
|
|
print "Would have changed owner for %s in tag %s: %s -> %s" \
|
|
% (entry['package_name'], entry['tag_name'], entry['owner_name'], user['name'])
|
|
continue
|
|
if options.verbose:
|
|
print "Changing owner for %s in tag %s: %s -> %s" \
|
|
% (entry['package_name'], entry['tag_name'], entry['owner_name'], user['name'])
|
|
session.packageListSetOwner(entry['tag_id'], entry['package_name'], user['id'])
|
|
|
|
def anon_handle_watch_task(options, session, args):
|
|
"Track progress of particular tasks"
|
|
usage = _("usage: %prog watch-task [options] <task id> [<task id>...]")
|
|
usage += _("\n(Specify the --help global option for a list of other help options)")
|
|
parser = OptionParser(usage=usage)
|
|
parser.add_option("--quiet", action="store_true",
|
|
help=_("Do not print the task information"), default=options.quiet)
|
|
(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"))
|
|
|
|
return watch_tasks(session, tasks, quiet=options.quiet)
|
|
|
|
def anon_handle_watch_logs(options, session, args):
|
|
"Watch logs in realtime"
|
|
usage = _("usage: %prog watch-logs [options] <task id> [<task id>...]")
|
|
usage += _("\n(Specify the --help global option for a list of other help options)")
|
|
parser = OptionParser(usage=usage)
|
|
parser.add_option("--log", help=_("Watch only a specific log"))
|
|
(options, args) = parser.parse_args(args)
|
|
activate_session(session)
|
|
|
|
tasks = []
|
|
for task in args:
|
|
try:
|
|
tasks.append(int(task))
|
|
except ValueError:
|
|
parser.error(_("task id must be an integer"))
|
|
if not tasks:
|
|
parser.error(_("at least one task id must be specified"))
|
|
|
|
watch_logs(session, tasks, options)
|
|
|
|
def handle_make_task(opts, session, args):
|
|
"[admin] Create an arbitrary task"
|
|
usage = _("usage: %prog make-task [options] <arg1> [<arg2>...]")
|
|
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:]),
|
|
**taskopts)
|
|
print "Created task id %d" % task_id
|
|
if _running_in_bg() or not options.watch:
|
|
return
|
|
else:
|
|
session.logout()
|
|
return watch_tasks(session, [task_id], quiet=opts.quiet)
|
|
|
|
def handle_tag_pkg(opts, session, args):
|
|
"Apply a tag to one or more packages"
|
|
usage = _("usage: %prog tag-pkg [options] <tag> <pkg> [<pkg>...]")
|
|
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:
|
|
session.logout()
|
|
return watch_tasks(session,tasks,quiet=opts.quiet)
|
|
|
|
def handle_move_pkg(opts, session, args):
|
|
"'Move' one or more packages between tags"
|
|
usage = _("usage: %prog move-pkg [options] <tag1> <tag2> <pkg> [<pkg>...]")
|
|
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, <pkg>'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:
|
|
session.logout()
|
|
return watch_tasks(session, tasks, quiet=opts.quiet)
|
|
|
|
def handle_untag_pkg(options, session, args):
|
|
"Remove a tag from one or more packages"
|
|
usage = _("usage: %prog untag-pkg [options] <tag> <pkg> [<pkg>...]")
|
|
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:
|
|
return 1
|
|
builds.reverse()
|
|
for binfo in builds:
|
|
if options.test:
|
|
print _("would have untagged %(nvr)s") % binfo
|
|
else:
|
|
if options.verbose:
|
|
print _("untagging %(nvr)s") % binfo
|
|
session.untagBuild(tag['name'], binfo['nvr'], force=options.force)
|
|
|
|
def handle_unblock_pkg(options, session, args):
|
|
"[admin] Unblock a package in the listing for tag"
|
|
usage = _("usage: %prog unblock-pkg [options] tag package [package2 ...]")
|
|
usage += _("\n(Specify the --help global option for a list of other help options)")
|
|
parser = OptionParser(usage=usage)
|
|
(options, args) = parser.parse_args(args)
|
|
if len(args) < 2:
|
|
parser.error(_("Please specify a tag and at least one package"))
|
|
assert False
|
|
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] <n-v-r | build_id | package>")
|
|
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 not suboptions.debuginfo and koji.is_debuginfo(rpm['name']):
|
|
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] <tag>")
|
|
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"), default=options.quiet)
|
|
(suboptions, args) = parser.parse_args(args)
|
|
|
|
start = time.time()
|
|
|
|
builds = [koji.parse_NVR(build) for build in suboptions.builds]
|
|
if len(args) < 1:
|
|
parser.error(_("Please specify a tag name"))
|
|
elif len(args) > 1:
|
|
parser.error(_("Only one tag may be specified"))
|
|
|
|
tag = args[0]
|
|
|
|
if suboptions.target:
|
|
target_info = session.getBuildTarget(tag)
|
|
if not target_info:
|
|
parser.error("Invalid build target: %s" % tag)
|
|
tag = target_info['build_tag_name']
|
|
tag_id = target_info['build_tag']
|
|
else:
|
|
tag_info = session.getTag(tag)
|
|
if not tag_info:
|
|
parser.error("Invalid tag: %s" % tag)
|
|
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')
|
|
|
|
def handle_regen_repo(options, session, args):
|
|
"[admin] Force a repo to be regenerated"
|
|
usage = _("usage: %prog regen-repo [options] <tag>")
|
|
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], quiet=options.quiet)
|
|
|
|
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 <command> --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__":
|
|
global options
|
|
options, command, args = get_options()
|
|
|
|
session_opts = {}
|
|
for k in ('user', 'password', 'debug_xmlrpc', 'debug', 'max_retries',
|
|
'retry_interval', 'offline_retry', 'offline_retry_interval',
|
|
'anon_retry'):
|
|
value = getattr(options,k)
|
|
if value is not None:
|
|
session_opts[k] = value
|
|
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)
|