debian-koji/www/kojiweb/index.py
2021-02-16 14:43:49 +01:00

2570 lines
87 KiB
Python

# core web interface handlers for koji
#
# Copyright (c) 2005-2014 Red Hat, Inc.
#
# 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:
# Mike Bonnet <mikeb@redhat.com>
# Mike McLean <mikem@redhat.com>
import datetime
import hashlib
import http.cookies
import logging
import mimetypes
import os
import os.path
import re
import sys
import time
import koji
import kojiweb.util
from koji.server import ServerRedirect
from kojiweb.util import _genHTML, _getValidTokens, _initValues
# Convenience definition of a commonly-used sort function
def _sortbyname(x):
return x['name']
# regexps for input checking
_VALID_SEARCH_CHARS = r"""a-zA-Z0-9"""
_VALID_SEARCH_SYMS = r""" @.,_/\()%+-~*?|[]^$"""
_VALID_SEARCH_RE = re.compile('^[' + _VALID_SEARCH_CHARS + re.escape(_VALID_SEARCH_SYMS) + ']+$')
_VALID_ARCH_RE = re.compile(r'^[\w-]+$', re.ASCII)
def _validate_arch(arch):
# archs (ASCII alnum + _ + -)
if not arch:
return None
elif _VALID_ARCH_RE.match(arch):
return arch
else:
raise koji.GenericError("Invalid arch: %r" % arch)
def _validate_name_or_id(value):
# integer ID or label, it is unicode alnum + search symbols (reasonable expectation?)
if value.isdigit():
return int(value)
elif _VALID_SEARCH_RE.match(value):
return value
else:
raise koji.GenericError("Invalid int/label value: %r" % value)
# loggers
authlogger = logging.getLogger('koji.auth')
def _setUserCookie(environ, user):
options = environ['koji.options']
# include the current time in the cookie so we can verify that
# someone is not using an expired cookie
value = user + ':' + str(int(time.time()))
if not options['Secret'].value:
raise koji.AuthError('Unable to authenticate, server secret not configured')
digest_string = value + options['Secret'].value
digest_string = digest_string.encode('utf-8')
shasum = hashlib.sha256(digest_string)
value = "%s:%s" % (shasum.hexdigest(), value)
cookies = http.cookies.SimpleCookie()
cookies['user'] = value
c = cookies['user'] # morsel instance
c['secure'] = True
c['path'] = os.path.dirname(environ['SCRIPT_NAME'])
# the Cookie module treats integer expire times as relative seconds
c['expires'] = int(options['LoginTimeout']) * 60 * 60
out = c.OutputString()
out += '; HttpOnly'
environ['koji.headers'].append(['Set-Cookie', out])
environ['koji.headers'].append(['Cache-Control', 'no-cache="set-cookie"'])
def _clearUserCookie(environ):
cookies = http.cookies.SimpleCookie()
cookies['user'] = ''
c = cookies['user'] # morsel instance
c['path'] = os.path.dirname(environ['SCRIPT_NAME'])
c['expires'] = 0
out = c.OutputString()
environ['koji.headers'].append(['Set-Cookie', out])
def _getUserCookie(environ):
options = environ['koji.options']
cookies = http.cookies.SimpleCookie(environ.get('HTTP_COOKIE', ''))
if 'user' not in cookies:
return None
value = cookies['user'].value
parts = value.split(":", 1)
if len(parts) != 2:
authlogger.warning('malformed user cookie: %s' % value)
return None
sig, value = parts
if not options['Secret'].value:
raise koji.AuthError('Unable to authenticate, server secret not configured')
digest_string = value + options['Secret'].value
digest_string = digest_string.encode('utf-8')
shasum = hashlib.sha256(digest_string)
if shasum.hexdigest() != sig:
authlogger.warning('invalid user cookie: %s:%s', sig, value)
return None
parts = value.split(":", 1)
if len(parts) != 2:
authlogger.warning('invalid signed user cookie: %s:%s', sig, value)
# no embedded timestamp
return None
user, timestamp = parts
try:
timestamp = float(timestamp)
except ValueError:
authlogger.warning('invalid time in signed user cookie: %s:%s', sig, value)
return None
if (time.time() - timestamp) > (int(options['LoginTimeout']) * 60 * 60):
authlogger.info('expired user cookie: %s', value)
return None
# Otherwise, cookie is valid and current
return user
def _gssapiLogin(environ, session, principal):
options = environ['koji.options']
wprinc = options['WebPrincipal']
keytab = options['WebKeytab']
ccache = options['WebCCache']
return session.gssapi_login(principal=wprinc, keytab=keytab,
ccache=ccache, proxyuser=principal)
def _sslLogin(environ, session, username):
options = environ['koji.options']
client_cert = options['WebCert']
server_ca = options['KojiHubCA']
return session.ssl_login(client_cert, None, server_ca,
proxyuser=username)
def _assertLogin(environ):
session = environ['koji.session']
options = environ['koji.options']
if 'koji.currentLogin' not in environ or 'koji.currentUser' not in environ:
raise Exception('_getServer() must be called before _assertLogin()')
elif environ['koji.currentLogin'] and environ['koji.currentUser']:
if options['WebCert']:
if not _sslLogin(environ, session, environ['koji.currentLogin']):
raise koji.AuthError('could not login %s via SSL' % environ['koji.currentLogin'])
elif options['WebPrincipal']:
if not _gssapiLogin(environ, environ['koji.session'], environ['koji.currentLogin']):
raise koji.AuthError(
'could not login using principal: %s' % environ['koji.currentLogin'])
else:
raise koji.AuthError(
'KojiWeb is incorrectly configured for authentication, '
'contact the system administrator')
# verify a valid authToken was passed in to avoid CSRF
authToken = environ['koji.form'].getfirst('a', '')
validTokens = _getValidTokens(environ)
if authToken and authToken in validTokens:
# we have a token and it's valid
pass
else:
# their authToken is likely expired
# send them back to the page that brought them here so they
# can re-click the link with a valid authToken
_redirectBack(environ, page=None,
forceSSL=(_getBaseURL(environ).startswith('https://')))
assert False # pragma: no cover
else:
_redirect(environ, 'login')
assert False # pragma: no cover
def _getServer(environ):
koji_opts = environ['koji.options']
opts = {
'serverca': koji_opts.get('KojiHubCA', '')
}
session = koji.ClientSession(koji_opts['KojiHubURL'], opts=opts)
environ['koji.currentLogin'] = _getUserCookie(environ)
if environ['koji.currentLogin']:
environ['koji.currentUser'] = session.getUser(environ['koji.currentLogin'])
if not environ['koji.currentUser']:
raise koji.AuthError(
'could not get user for principal: %s' % environ['koji.currentLogin'])
_setUserCookie(environ, environ['koji.currentLogin'])
else:
environ['koji.currentUser'] = None
environ['koji.session'] = session
return session
def _construct_url(environ, page):
port = environ['SERVER_PORT']
host = environ['SERVER_NAME']
url_scheme = environ['wsgi.url_scheme']
if (url_scheme == 'https' and port == '443') or \
(url_scheme == 'http' and port == '80'):
return "%s://%s%s" % (url_scheme, host, page)
return "%s://%s:%s%s" % (url_scheme, host, port, page)
def _getBaseURL(environ):
base = environ['SCRIPT_NAME']
return _construct_url(environ, base)
def _redirect(environ, location):
environ['koji.redirect'] = location
raise ServerRedirect
def _redirectBack(environ, page, forceSSL):
localurl = '%s://%s' % (environ['REQUEST_SCHEME'], environ['SERVER_NAME'])
if page:
# We'll work with the page we were given
pass
elif environ.get('HTTP_REFERER', '').startswith(localurl):
page = environ['HTTP_REFERER']
else:
page = 'index'
# Modify the scheme if necessary
if page.startswith('http'):
pass
elif page.startswith('/'):
page = _construct_url(environ, page)
else:
page = _getBaseURL(environ) + '/' + page
if forceSSL:
page = page.replace('http:', 'https:')
else:
page = page.replace('https:', 'http:')
# and redirect to the page
_redirect(environ, page)
def login(environ, page=None):
session = _getServer(environ)
options = environ['koji.options']
# try SSL first, fall back to Kerberos
if options['WebCert']:
if environ['wsgi.url_scheme'] != 'https':
dest = 'login'
if page:
dest = dest + '?page=' + page
_redirectBack(environ, dest, forceSSL=True)
return
if environ.get('SSL_CLIENT_VERIFY') != 'SUCCESS':
raise koji.AuthError('could not verify client: %s' % environ.get('SSL_CLIENT_VERIFY'))
# use the subject's common name as their username
username = environ.get('SSL_CLIENT_S_DN_CN')
if not username:
raise koji.AuthError('unable to get user information from client certificate')
if not _sslLogin(environ, session, username):
raise koji.AuthError('could not login %s using SSL certificates' % username)
authlogger.info('Successful SSL authentication by %s', username)
elif options['WebPrincipal']:
principal = environ.get('REMOTE_USER')
if not principal:
raise koji.AuthError(
'configuration error: mod_auth_gssapi should have performed authentication before '
'presenting this page')
if not _gssapiLogin(environ, session, principal):
raise koji.AuthError('could not login using principal: %s' % principal)
username = principal
authlogger.info('Successful Kerberos authentication by %s', username)
else:
raise koji.AuthError(
'KojiWeb is incorrectly configured for authentication, contact the system '
'administrator')
_setUserCookie(environ, username)
# To protect the session cookie, we must forceSSL
_redirectBack(environ, page, forceSSL=True)
def logout(environ, page=None):
user = _getUserCookie(environ)
_clearUserCookie(environ)
if user:
authlogger.info('Logout by %s', user)
_redirectBack(environ, page, forceSSL=False)
def index(environ, packageOrder='package_name', packageStart=None):
values = _initValues(environ)
server = _getServer(environ)
opts = environ['koji.options']
user = environ['koji.currentUser']
values['builds'] = server.listBuilds(
userID=(user and user['id'] or None),
queryOpts={'order': '-build_id', 'limit': 10}
)
taskOpts = {'parent': None, 'decode': True}
if user:
taskOpts['owner'] = user['id']
if opts.get('HiddenUsers'):
taskOpts['not_owner'] = [
int(userid) for userid in opts['HiddenUsers'].split()
]
values['tasks'] = server.listTasks(
opts=taskOpts,
queryOpts={'order': '-id', 'limit': 10}
)
values['order'] = '-id'
if user:
kojiweb.util.paginateResults(server, values, 'listPackages',
kw={'userID': user['id'], 'with_dups': True},
start=packageStart, dataName='packages', prefix='package',
order=packageOrder, pageSize=10)
notifs = server.getBuildNotifications(user['id'])
notifs.sort(key=lambda x: x['id'])
# XXX Make this a multicall
for notif in notifs:
notif['package'] = None
if notif['package_id']:
notif['package'] = server.getPackage(notif['package_id'])
notif['tag'] = None
if notif['tag_id']:
notif['tag'] = server.getTag(notif['tag_id'])
values['notifs'] = notifs
values['user'] = user
values['welcomeMessage'] = environ['koji.options']['KojiGreeting']
return _genHTML(environ, 'index.chtml')
def notificationedit(environ, notificationID):
server = _getServer(environ)
_assertLogin(environ)
notificationID = int(notificationID)
notification = server.getBuildNotification(notificationID)
if notification is None:
raise koji.GenericError('no notification with ID: %i' % notificationID)
form = environ['koji.form']
if 'save' in form:
package_id = form.getfirst('package')
if package_id == 'all':
package_id = None
else:
package_id = int(package_id)
tag_id = form.getfirst('tag')
if tag_id == 'all':
tag_id = None
else:
tag_id = int(tag_id)
if 'success_only' in form:
success_only = True
else:
success_only = False
server.updateNotification(notification['id'], package_id, tag_id, success_only)
_redirect(environ, 'index')
elif 'cancel' in form:
_redirect(environ, 'index')
else:
values = _initValues(environ, 'Edit Notification')
values['notif'] = notification
packages = server.listPackagesSimple(queryOpts={'order': 'package_name'})
values['packages'] = packages
tags = server.listTags(queryOpts={'order': 'name'})
values['tags'] = tags
return _genHTML(environ, 'notificationedit.chtml')
def notificationcreate(environ):
server = _getServer(environ)
_assertLogin(environ)
form = environ['koji.form']
if 'add' in form:
user = environ['koji.currentUser']
if not user:
raise koji.GenericError('not logged-in')
package_id = form.getfirst('package')
if package_id == 'all':
package_id = None
else:
package_id = int(package_id)
tag_id = form.getfirst('tag')
if tag_id == 'all':
tag_id = None
else:
tag_id = int(tag_id)
if 'success_only' in form:
success_only = True
else:
success_only = False
server.createNotification(user['id'], package_id, tag_id, success_only)
_redirect(environ, 'index')
elif 'cancel' in form:
_redirect(environ, 'index')
else:
values = _initValues(environ, 'Edit Notification')
values['notif'] = None
packages = server.listPackagesSimple(queryOpts={'order': 'package_name'})
values['packages'] = packages
tags = server.listTags(queryOpts={'order': 'name'})
values['tags'] = tags
return _genHTML(environ, 'notificationedit.chtml')
def notificationdelete(environ, notificationID):
server = _getServer(environ)
_assertLogin(environ)
notificationID = int(notificationID)
notification = server.getBuildNotification(notificationID)
if not notification:
raise koji.GenericError('no notification with ID: %i' % notificationID)
server.deleteNotification(notification['id'])
_redirect(environ, 'index')
# All Tasks
_TASKS = ['build',
'buildSRPMFromSCM',
'rebuildSRPM',
'buildArch',
'chainbuild',
'maven',
'buildMaven',
'chainmaven',
'wrapperRPM',
'winbuild',
'vmExec',
'waitrepo',
'tagBuild',
'newRepo',
'createrepo',
'distRepo',
'createdistrepo',
'buildNotification',
'tagNotification',
'dependantTask',
'livecd',
'createLiveCD',
'appliance',
'createAppliance',
'image',
'indirectionimage',
'createImage',
'livemedia',
'createLiveMedia']
# Tasks that can exist without a parent
_TOPLEVEL_TASKS = ['build', 'buildNotification', 'chainbuild', 'maven', 'chainmaven', 'wrapperRPM',
'winbuild', 'newRepo', 'distRepo', 'tagBuild', 'tagNotification', 'waitrepo',
'livecd', 'appliance', 'image', 'livemedia']
# Tasks that can have children
_PARENT_TASKS = ['build', 'chainbuild', 'maven', 'chainmaven', 'winbuild', 'newRepo', 'distRepo',
'wrapperRPM', 'livecd', 'appliance', 'image', 'livemedia']
def tasks(environ, owner=None, state='active', view='tree', method='all', hostID=None,
channelID=None, start=None, order='-id'):
values = _initValues(environ, 'Tasks', 'tasks')
server = _getServer(environ)
if view not in ('tree', 'toplevel', 'flat'):
raise koji.GenericError("Invalid value for view: %r" % view)
opts = {'decode': True}
if owner:
owner = _validate_name_or_id(owner)
ownerObj = server.getUser(owner, strict=True)
opts['owner'] = ownerObj['id']
values['owner'] = ownerObj['name']
values['ownerObj'] = ownerObj
else:
values['owner'] = None
values['ownerObj'] = None
values['users'] = server.listUsers(queryOpts={'order': 'name'})
if method in _TASKS + environ['koji.options']['Tasks']:
opts['method'] = method
else:
method = 'all'
values['method'] = method
values['alltasks'] = sorted(_TASKS + environ['koji.options']['Tasks'])
treeEnabled = True
if hostID or (method not in ['all'] + _PARENT_TASKS + environ['koji.options']['ParentTasks']):
# force flat view if we're filtering by a hostID or a task that never has children
if view == 'tree':
view = 'flat'
# don't let them choose tree view either
treeEnabled = False
values['treeEnabled'] = treeEnabled
toplevelEnabled = True
if method not in ['all'] + _TOPLEVEL_TASKS + environ['koji.options']['ToplevelTasks']:
# force flat view if we're viewing a task that is never a top-level task
if view == 'toplevel':
view = 'flat'
toplevelEnabled = False
values['toplevelEnabled'] = toplevelEnabled
values['view'] = view
if view == 'tree':
treeDisplay = True
else:
treeDisplay = False
values['treeDisplay'] = treeDisplay
if view in ('tree', 'toplevel'):
opts['parent'] = None
if state == 'active':
opts['state'] = [koji.TASK_STATES['FREE'],
koji.TASK_STATES['OPEN'],
koji.TASK_STATES['ASSIGNED']]
elif state == 'all':
pass
else:
# Assume they've passed in a state name
opts['state'] = [koji.TASK_STATES[state.upper()]]
values['state'] = state
if hostID:
hostID = int(hostID)
host = server.getHost(hostID, strict=True)
opts['host_id'] = host['id']
values['host'] = host
values['hostID'] = host['id']
else:
values['host'] = None
values['hostID'] = None
if channelID:
channelID = _validate_name_or_id(channelID)
channel = server.getChannel(channelID, strict=True)
opts['channel_id'] = channel['id']
values['channel'] = channel
values['channelID'] = channel['id']
else:
values['channel'] = None
values['channelID'] = None
loggedInUser = environ['koji.currentUser']
values['loggedInUser'] = loggedInUser
values['order'] = order
tasks = kojiweb.util.paginateMethod(server, values, 'listTasks', kw={'opts': opts},
start=start, dataName='tasks', prefix='task', order=order)
if view == 'tree':
server.multicall = True
for task in tasks:
server.getTaskDescendents(task['id'], request=True)
descendentList = server.multiCall()
for task, [descendents] in zip(tasks, descendentList):
task['descendents'] = descendents
return _genHTML(environ, 'tasks.chtml')
def taskinfo(environ, taskID):
server = _getServer(environ)
values = _initValues(environ, 'Task Info', 'tasks')
taskID = int(taskID)
task = server.getTaskInfo(taskID, request=True)
if not task:
raise koji.GenericError('invalid task ID: %s' % taskID)
values['title'] = koji.taskLabel(task) + ' | Task Info'
values['task'] = task
params = task['request']
values['params'] = params
if task['channel_id']:
channel = server.getChannel(task['channel_id'])
values['channelName'] = channel['name']
else:
values['channelName'] = None
if task['host_id']:
host = server.getHost(task['host_id'])
values['hostName'] = host['name']
else:
values['hostName'] = None
if task['owner']:
owner = server.getUser(task['owner'])
values['owner'] = owner
else:
values['owner'] = None
if task['parent']:
parent = server.getTaskInfo(task['parent'], request=True)
values['parent'] = parent
else:
values['parent'] = None
descendents = server.getTaskDescendents(task['id'], request=True)
values['descendents'] = descendents
builds = server.listBuilds(taskID=task['id'])
if builds:
taskBuild = builds[0]
else:
taskBuild = None
values['taskBuild'] = taskBuild
values['estCompletion'] = None
if taskBuild and taskBuild['state'] == koji.BUILD_STATES['BUILDING']:
avgDuration = server.getAverageBuildDuration(taskBuild['package_id'])
if avgDuration is not None:
avgDelta = datetime.timedelta(seconds=avgDuration)
startTime = datetime.datetime.fromtimestamp(taskBuild['creation_ts'])
values['estCompletion'] = startTime + avgDelta
buildroots = server.listBuildroots(taskID=task['id'])
values['buildroots'] = buildroots
if task['method'] in ('buildArch', 'buildMaven', 'buildSRPMFromSCM'):
if len(params) > 1:
try:
values['buildTag'] = server.getTag(params[1], strict=True)
except koji.GenericError:
values['buildTag'] = {'name': "%d (deleted)" % params[1], 'id': None}
elif task['method'] == 'tagBuild':
destTag = server.getTag(params[0])
build = server.getBuild(params[1])
values['destTag'] = destTag
values['build'] = build
elif task['method'] in ('newRepo', 'distRepo', 'createdistrepo'):
tag = server.getTag(params[0])
values['tag'] = tag
elif task['method'] == 'tagNotification':
destTag = None
if params[2]:
destTag = server.getTag(params[2])
srcTag = None
if params[3]:
srcTag = server.getTag(params[3])
build = server.getBuild(params[4])
user = server.getUser(params[5])
values['destTag'] = destTag
values['srcTag'] = srcTag
values['build'] = build
values['user'] = user
elif task['method'] == 'dependantTask':
deps = [server.getTaskInfo(depID, request=True) for depID in params[0]]
values['deps'] = deps
elif task['method'] == 'wrapperRPM':
buildTarget = params[1]
values['buildTarget'] = buildTarget
if params[3]:
wrapTask = server.getTaskInfo(params[3]['id'], request=True)
values['wrapTask'] = wrapTask
elif task['method'] == 'restartVerify':
values['rtask'] = server.getTaskInfo(params[0], request=True)
values['taskBuilds'] = []
if task['state'] in (koji.TASK_STATES['CLOSED'], koji.TASK_STATES['FAILED']):
try:
result = server.getTaskResult(task['id'])
except Exception:
excClass, exc = sys.exc_info()[:2]
values['result'] = exc
values['excClass'] = excClass
if not values.get('result'):
values['result'] = result
values['excClass'] = None
if task['method'] == 'buildContainer' and 'koji_builds' in result:
values['taskBuilds'] = [
server.getBuild(int(buildID)) for buildID in result['koji_builds']]
else:
values['result'] = None
values['excClass'] = None
full_result_text, abbr_result_text = kojiweb.util.task_result_to_html(
values['result'], values['excClass'], abbr_postscript='...')
values['full_result_text'] = full_result_text
values['abbr_result_text'] = abbr_result_text
topurl = environ['koji.options']['KojiFilesURL']
pathinfo = koji.PathInfo(topdir=topurl)
values['pathinfo'] = pathinfo
paths = [] # (volume, relpath) tuples
for relname, volumes in server.listTaskOutput(task['id'], all_volumes=True).items():
paths += [(volume, relname) for volume in volumes]
values['output'] = sorted(paths, key=_sortByExtAndName)
if environ['koji.currentUser']:
values['perms'] = server.getUserPerms(environ['koji.currentUser']['id'])
else:
values['perms'] = []
try:
values['params_parsed'] = _genHTML(environ, 'taskinfo_params.chtml')
except Exception:
values['params_parsed'] = None
return _genHTML(environ, 'taskinfo.chtml')
def taskstatus(environ, taskID):
server = _getServer(environ)
taskID = int(taskID)
task = server.getTaskInfo(taskID)
if not task:
return ''
files = server.listTaskOutput(taskID, stat=True, all_volumes=True)
output = '%i:%s\n' % (task['id'], koji.TASK_STATES[task['state']])
for filename, volumes_data in files.items():
for volume, file_stats in volumes_data.items():
output += '%s:%s:%s\n' % (volume, filename, file_stats['st_size'])
return output
def resubmittask(environ, taskID):
server = _getServer(environ)
_assertLogin(environ)
taskID = int(taskID)
newTaskID = server.resubmitTask(taskID)
_redirect(environ, 'taskinfo?taskID=%i' % newTaskID)
def canceltask(environ, taskID):
server = _getServer(environ)
_assertLogin(environ)
taskID = int(taskID)
server.cancelTask(taskID)
_redirect(environ, 'taskinfo?taskID=%i' % taskID)
def _sortByExtAndName(item):
"""Sort filename tuples key function, first by extension, and then by name."""
kRoot, kExt = os.path.splitext(os.path.basename(item[1]))
return (kExt, kRoot)
def getfile(environ, taskID, name, volume='DEFAULT', offset=None, size=None):
server = _getServer(environ)
taskID = int(taskID)
output = server.listTaskOutput(taskID, stat=True, all_volumes=True)
try:
file_info = output[name][volume]
except KeyError:
raise koji.GenericError('no file "%s" output by task %i' % (name, taskID))
mime_guess = mimetypes.guess_type(name, strict=False)[0]
if mime_guess:
ctype = mime_guess
else:
if name.endswith('.log') or name.endswith('.ks'):
ctype = 'text/plain'
else:
ctype = 'application/octet-stream'
if ctype != 'text/plain':
environ['koji.headers'].append(['Content-Disposition', 'attachment; filename=%s' % name])
environ['koji.headers'].append(['Content-Type', ctype])
file_size = int(file_info['st_size'])
if offset is None:
offset = 0
else:
offset = int(offset)
if size is None:
size = file_size
else:
size = int(size)
if size < 0:
size = file_size
if offset < 0:
# seeking relative to the end of the file
if offset < -file_size:
offset = -file_size
if size > -offset:
size = -offset
else:
if size > (file_size - offset):
size = file_size - offset
# environ['koji.headers'].append(['Content-Length', str(size)])
return _chunk_file(server, environ, taskID, name, offset, size, volume)
def _chunk_file(server, environ, taskID, name, offset, size, volume):
remaining = size
while True:
if remaining <= 0:
break
chunk_size = 1048576
if remaining < chunk_size:
chunk_size = remaining
content = server.downloadTaskOutput(taskID, name,
offset=offset, size=chunk_size, volume=volume)
if not content:
break
yield content
content_length = len(content)
offset += content_length
remaining -= content_length
def tags(environ, start=None, order=None, childID=None):
values = _initValues(environ, 'Tags', 'tags')
server = _getServer(environ)
if order is None:
order = 'name'
values['order'] = order
kojiweb.util.paginateMethod(server, values, 'listTags', kw=None,
start=start, dataName='tags', prefix='tag', order=order)
if environ['koji.currentUser']:
values['perms'] = server.getUserPerms(environ['koji.currentUser']['id'])
else:
values['perms'] = []
if childID is None:
values['childID'] = None
else:
values['childID'] = int(childID)
return _genHTML(environ, 'tags.chtml')
_PREFIX_CHARS = [chr(char) for char in list(range(48, 58)) + list(range(97, 123))]
def packages(environ, tagID=None, userID=None, order='package_name', start=None, prefix=None,
inherited='1'):
values = _initValues(environ, 'Packages', 'packages')
server = _getServer(environ)
tag = None
if tagID is not None:
tagID = _validate_name_or_id(tagID)
tag = server.getTag(tagID, strict=True)
values['tagID'] = tagID
values['tag'] = tag
user = None
if userID is not None:
userID = _validate_name_or_id(userID)
user = server.getUser(userID, strict=True)
values['userID'] = userID
values['user'] = user
values['order'] = order
if prefix:
prefix = prefix.lower()[0]
if prefix not in _PREFIX_CHARS:
prefix = None
values['prefix'] = prefix
inherited = int(inherited)
values['inherited'] = inherited
kojiweb.util.paginateMethod(server, values, 'listPackages',
kw={'tagID': tagID,
'userID': userID,
'prefix': prefix,
'inherited': bool(inherited)},
start=start, dataName='packages', prefix='package', order=order)
values['chars'] = _PREFIX_CHARS
return _genHTML(environ, 'packages.chtml')
def packageinfo(environ, packageID, tagOrder='name', tagStart=None, buildOrder='-completion_time',
buildStart=None):
values = _initValues(environ, 'Package Info', 'packages')
server = _getServer(environ)
packageID = _validate_name_or_id(packageID)
package = server.getPackage(packageID)
if package is None:
raise koji.GenericError('invalid package ID: %s' % packageID)
values['title'] = package['name'] + ' | Package Info'
values['package'] = package
values['packageID'] = package['id']
kojiweb.util.paginateMethod(server, values, 'listTags', kw={'package': package['id']},
start=tagStart, dataName='tags', prefix='tag', order=tagOrder)
kojiweb.util.paginateMethod(server, values, 'listBuilds', kw={'packageID': package['id']},
start=buildStart, dataName='builds', prefix='build',
order=buildOrder)
return _genHTML(environ, 'packageinfo.chtml')
def taginfo(environ, tagID, all='0', packageOrder='package_name', packageStart=None,
buildOrder='-completion_time', buildStart=None, childID=None):
values = _initValues(environ, 'Tag Info', 'tags')
server = _getServer(environ)
tagID = _validate_name_or_id(tagID)
tag = server.getTag(tagID, strict=True)
values['title'] = tag['name'] + ' | Tag Info'
all = int(all)
numPackages = server.count('listPackages', tagID=tag['id'], inherited=True)
numBuilds = server.count('listTagged', tag=tag['id'], inherit=True)
values['numPackages'] = numPackages
values['numBuilds'] = numBuilds
inheritance = server.getFullInheritance(tag['id'])
tagsByChild = {}
for parent in inheritance:
child_id = parent['child_id']
if child_id not in tagsByChild:
tagsByChild[child_id] = []
tagsByChild[child_id].append(child_id)
srcTargets = server.getBuildTargets(buildTagID=tag['id'])
srcTargets.sort(key=_sortbyname)
destTargets = server.getBuildTargets(destTagID=tag['id'])
destTargets.sort(key=_sortbyname)
values['tag'] = tag
values['tagID'] = tag['id']
values['inheritance'] = inheritance
values['tagsByChild'] = tagsByChild
values['srcTargets'] = srcTargets
values['destTargets'] = destTargets
values['all'] = all
values['repo'] = server.getRepo(tag['id'], state=koji.REPO_READY)
values['external_repos'] = server.getExternalRepoList(tag['id'])
child = None
if childID is not None:
child = server.getTag(int(childID), strict=True)
values['child'] = child
if environ['koji.currentUser']:
values['perms'] = server.getUserPerms(environ['koji.currentUser']['id'])
else:
values['perms'] = []
permList = server.getAllPerms()
allPerms = dict([(perm['id'], perm['name']) for perm in permList])
values['allPerms'] = allPerms
return _genHTML(environ, 'taginfo.chtml')
def tagcreate(environ):
server = _getServer(environ)
_assertLogin(environ)
mavenEnabled = server.mavenEnabled()
form = environ['koji.form']
if 'add' in form:
params = {}
name = form['name'].value
params['arches'] = form['arches'].value
params['locked'] = 'locked' in form
permission = form['permission'].value
if permission != 'none':
params['perm'] = int(permission)
if mavenEnabled:
params['maven_support'] = bool('maven_support' in form)
params['maven_include_all'] = bool('maven_include_all' in form)
tagID = server.createTag(name, **params)
_redirect(environ, 'taginfo?tagID=%i' % tagID)
elif 'cancel' in form:
_redirect(environ, 'tags')
else:
values = _initValues(environ, 'Add Tag', 'tags')
values['mavenEnabled'] = mavenEnabled
values['tag'] = None
values['permissions'] = server.getAllPerms()
return _genHTML(environ, 'tagedit.chtml')
def tagedit(environ, tagID):
server = _getServer(environ)
_assertLogin(environ)
mavenEnabled = server.mavenEnabled()
tagID = int(tagID)
tag = server.getTag(tagID)
if tag is None:
raise koji.GenericError('no tag with ID: %i' % tagID)
form = environ['koji.form']
if 'save' in form:
params = {}
params['name'] = form['name'].value
params['arches'] = form['arches'].value
params['locked'] = bool('locked' in form)
permission = form['permission'].value
if permission == 'none':
params['perm'] = None
else:
params['perm'] = int(permission)
if mavenEnabled:
params['maven_support'] = bool('maven_support' in form)
params['maven_include_all'] = bool('maven_include_all' in form)
server.editTag2(tag['id'], **params)
_redirect(environ, 'taginfo?tagID=%i' % tag['id'])
elif 'cancel' in form:
_redirect(environ, 'taginfo?tagID=%i' % tag['id'])
else:
values = _initValues(environ, 'Edit Tag', 'tags')
values['mavenEnabled'] = mavenEnabled
values['tag'] = tag
values['permissions'] = server.getAllPerms()
return _genHTML(environ, 'tagedit.chtml')
def tagdelete(environ, tagID):
server = _getServer(environ)
_assertLogin(environ)
tagID = int(tagID)
tag = server.getTag(tagID)
if tag is None:
raise koji.GenericError('no tag with ID: %i' % tagID)
server.deleteTag(tag['id'])
_redirect(environ, 'tags')
def tagparent(environ, tagID, parentID, action):
server = _getServer(environ)
_assertLogin(environ)
tag = server.getTag(int(tagID), strict=True)
parent = server.getTag(int(parentID), strict=True)
if action in ('add', 'edit'):
form = environ['koji.form']
if 'add' in form or 'save' in form:
newDatum = {}
newDatum['parent_id'] = parent['id']
newDatum['priority'] = int(form.getfirst('priority'))
maxdepth = form.getfirst('maxdepth')
maxdepth = len(maxdepth) > 0 and int(maxdepth) or None
newDatum['maxdepth'] = maxdepth
newDatum['intransitive'] = bool('intransitive' in form)
newDatum['noconfig'] = bool('noconfig' in form)
newDatum['pkg_filter'] = form.getfirst('pkg_filter')
data = server.getInheritanceData(tag['id'])
data.append(newDatum)
server.setInheritanceData(tag['id'], data)
elif 'cancel' in form:
pass
else:
values = _initValues(environ, action.capitalize() + ' Parent Tag', 'tags')
values['tag'] = tag
values['parent'] = parent
inheritanceData = server.getInheritanceData(tag['id'])
maxPriority = 0
for datum in inheritanceData:
if datum['priority'] > maxPriority:
maxPriority = datum['priority']
values['maxPriority'] = maxPriority
inheritanceData = [datum for datum in inheritanceData
if datum['parent_id'] == parent['id']]
if len(inheritanceData) == 0:
values['inheritanceData'] = None
elif len(inheritanceData) == 1:
values['inheritanceData'] = inheritanceData[0]
else:
raise koji.GenericError(
'tag %i has tag %i listed as a parent more than once' %
(tag['id'], parent['id']))
return _genHTML(environ, 'tagparent.chtml')
elif action == 'remove':
data = server.getInheritanceData(tag['id'])
for datum in data:
if datum['parent_id'] == parent['id']:
datum['delete link'] = True
break
else:
raise koji.GenericError('tag %i is not a parent of tag %i' % (parent['id'], tag['id']))
server.setInheritanceData(tag['id'], data)
else:
raise koji.GenericError('unknown action: %s' % action)
_redirect(environ, 'taginfo?tagID=%i' % tag['id'])
def externalrepoinfo(environ, extrepoID):
values = _initValues(environ, 'External Repo Info', 'tags')
server = _getServer(environ)
extrepoID = _validate_name_or_id(extrepoID)
extRepo = server.getExternalRepo(extrepoID, strict=True)
repoTags = server.getTagExternalRepos(repo_info=extRepo['id'])
values['title'] = extRepo['name'] + ' | External Repo Info'
values['extRepo'] = extRepo
values['repoTags'] = repoTags
return _genHTML(environ, 'externalrepoinfo.chtml')
def buildinfo(environ, buildID):
values = _initValues(environ, 'Build Info', 'builds')
server = _getServer(environ)
topurl = environ['koji.options']['KojiFilesURL']
pathinfo = koji.PathInfo(topdir=topurl)
buildID = int(buildID)
try:
build = server.getBuild(buildID, strict=True)
except koji.GenericError:
raise koji.GenericError("Invalid build ID: %i" % buildID)
values['title'] = koji.buildLabel(build) + ' | Build Info'
tags = server.listTags(build['id'])
tags.sort(key=_sortbyname)
rpms = server.listBuildRPMs(build['id'])
rpms.sort(key=_sortbyname)
typeinfo = server.getBuildType(buildID)
archiveIndex = {}
for btype in typeinfo:
archives = server.listArchives(build['id'], type=btype, queryOpts={'order': 'filename'})
idx = archiveIndex.setdefault(btype, {})
for archive in archives:
if btype == 'maven':
archive['display'] = archive['filename']
archive['dl_url'] = '/'.join([pathinfo.mavenbuild(build),
pathinfo.mavenfile(archive)])
elif btype == 'win':
archive['display'] = pathinfo.winfile(archive)
archive['dl_url'] = '/'.join([pathinfo.winbuild(build), pathinfo.winfile(archive)])
elif btype == 'image':
archive['display'] = archive['filename']
archive['dl_url'] = '/'.join([pathinfo.imagebuild(build), archive['filename']])
else:
archive['display'] = archive['filename']
archive['dl_url'] = '/'.join([pathinfo.typedir(build, btype), archive['filename']])
ext = os.path.splitext(archive['filename'])[1][1:]
idx.setdefault(ext, []).append(archive)
rpmsByArch = {}
debuginfos = []
for rpm in rpms:
if koji.is_debuginfo(rpm['name']):
debuginfos.append(rpm)
else:
rpmsByArch.setdefault(rpm['arch'], []).append(rpm)
# add debuginfos at the end
for rpm in debuginfos:
rpmsByArch.setdefault(rpm['arch'], []).append(rpm)
if 'src' in rpmsByArch:
srpm = rpmsByArch['src'][0]
result = server.getRPMHeaders(srpm['id'], headers=RPM_HEADERS)
for header in RPM_HEADERS:
values[header] = koji.fixEncoding(result.get(header))
values['changelog'] = server.getChangelogEntries(build['id'])
if build['task_id']:
task = server.getTaskInfo(build['task_id'], request=True)
# get the summary, description, and changelogs from the built srpm
# if the build is not yet complete
if build['state'] != koji.BUILD_STATES['COMPLETE']:
srpm_tasks = server.listTasks(opts={'parent': task['id'],
'method': 'buildSRPMFromSCM'})
if srpm_tasks:
srpm_task = srpm_tasks[0]
if srpm_task['state'] == koji.TASK_STATES['CLOSED']:
srpm_path = None
for output in server.listTaskOutput(srpm_task['id']):
if output.endswith('.src.rpm'):
srpm_path = output
break
if srpm_path:
srpm_headers = server.getRPMHeaders(taskID=srpm_task['id'],
filepath=srpm_path,
headers=RPM_HEADERS)
if srpm_headers:
for header in RPM_HEADERS:
values[header] = koji.fixEncoding(srpm_headers.get(header))
changelog = server.getChangelogEntries(taskID=srpm_task['id'],
filepath=srpm_path)
if changelog:
values['changelog'] = changelog
else:
task = None
# get logs
logs = server.getBuildLogs(buildID)
logs_by_dir = {}
for loginfo in logs:
loginfo['dl_url'] = "%s/%s" % (topurl, loginfo['path'])
logdir = loginfo['dir']
if logdir == '.':
logdir = ''
logs_by_dir.setdefault(logdir, []).append(loginfo)
values['logs_by_dir'] = logs_by_dir
values['build'] = build
values['tags'] = tags
values['rpmsByArch'] = rpmsByArch
values['task'] = task
values['typeinfo'] = typeinfo
values['archiveIndex'] = archiveIndex
if environ['koji.currentUser']:
values['perms'] = server.getUserPerms(environ['koji.currentUser']['id'])
else:
values['perms'] = []
for header in RPM_HEADERS + ['changelog']:
if header not in values:
values[header] = None
# We added the start_time field in 2015 as part of Koji's content
# generator feature. Builds before that point have a null value for
# start_time. Fall back to creation_ts in those cases.
# Currently new_build() has data.setdefault('start_time', 'NOW'), so all
# recent builds should have a value for the field.
values['start_ts'] = build.get('start_ts') or build['creation_ts']
# the build start time is not accurate for maven and win builds, get it from the
# task start time instead
if 'maven' in typeinfo or 'win' in typeinfo:
if task:
values['start_ts'] = task['start_ts']
if build['state'] == koji.BUILD_STATES['BUILDING']:
avgDuration = server.getAverageBuildDuration(build['package_id'])
if avgDuration is not None:
avgDelta = datetime.timedelta(seconds=avgDuration)
startTime = datetime.datetime.fromtimestamp(build['creation_ts'])
values['estCompletion'] = startTime + avgDelta
else:
values['estCompletion'] = None
values['pathinfo'] = pathinfo
return _genHTML(environ, 'buildinfo.chtml')
def builds(environ, userID=None, tagID=None, packageID=None, state=None, order='-build_id',
start=None, prefix=None, inherited='1', latest='1', type=None):
values = _initValues(environ, 'Builds', 'builds')
server = _getServer(environ)
user = None
if userID:
userID = _validate_name_or_id(userID)
user = server.getUser(userID, strict=True)
values['userID'] = userID
values['user'] = user
loggedInUser = environ['koji.currentUser']
values['loggedInUser'] = loggedInUser
values['users'] = server.listUsers(queryOpts={'order': 'name'})
tag = None
if tagID:
tagID = _validate_name_or_id(tagID)
tag = server.getTag(tagID, strict=True)
values['tagID'] = tagID
values['tag'] = tag
package = None
if packageID:
packageID = _validate_name_or_id(packageID)
package = server.getPackage(packageID, strict=True)
values['packageID'] = packageID
values['package'] = package
if state == 'all':
state = None
elif state is not None:
state = int(state)
values['state'] = state
if prefix:
prefix = prefix.lower()[0]
if prefix not in _PREFIX_CHARS:
prefix = None
values['prefix'] = prefix
values['order'] = order
btypes = sorted([b['name'] for b in server.listBTypes()])
if type in btypes:
pass
elif type == 'all':
type = None
else:
type = None
values['type'] = type
values['btypes'] = btypes
if tag:
inherited = int(inherited)
values['inherited'] = inherited
latest = int(latest)
values['latest'] = latest
else:
values['inherited'] = None
values['latest'] = None
if tag:
# don't need to consider 'state' here, since only completed builds would be tagged
kojiweb.util.paginateResults(server, values, 'listTagged',
kw={'tag': tag['id'],
'package': (package and package['name'] or None),
'owner': (user and user['name'] or None),
'type': type,
'inherit': bool(inherited), 'latest': bool(latest),
'prefix': prefix},
start=start, dataName='builds', prefix='build', order=order)
else:
kojiweb.util.paginateMethod(server, values, 'listBuilds',
kw={'userID': (user and user['id'] or None),
'packageID': (package and package['id'] or None),
'type': type,
'state': state, 'prefix': prefix},
start=start, dataName='builds', prefix='build', order=order)
values['chars'] = _PREFIX_CHARS
return _genHTML(environ, 'builds.chtml')
def users(environ, order='name', start=None, prefix=None):
values = _initValues(environ, 'Users', 'users')
server = _getServer(environ)
if prefix:
prefix = prefix.lower()[0]
if prefix not in _PREFIX_CHARS:
prefix = None
values['prefix'] = prefix
values['order'] = order
kojiweb.util.paginateMethod(server, values, 'listUsers', kw={'prefix': prefix},
start=start, dataName='users', prefix='user', order=order)
values['chars'] = _PREFIX_CHARS
return _genHTML(environ, 'users.chtml')
def userinfo(environ, userID, packageOrder='package_name', packageStart=None,
buildOrder='-completion_time', buildStart=None):
values = _initValues(environ, 'User Info', 'users')
server = _getServer(environ)
userID = _validate_name_or_id(userID)
user = server.getUser(userID, strict=True)
values['title'] = user['name'] + ' | User Info'
values['user'] = user
values['userID'] = userID
values['taskCount'] = server.listTasks(opts={'owner': user['id'], 'parent': None},
queryOpts={'countOnly': True})
kojiweb.util.paginateResults(server, values, 'listPackages',
kw={'userID': user['id'], 'with_dups': True},
start=packageStart, dataName='packages', prefix='package',
order=packageOrder, pageSize=10)
kojiweb.util.paginateMethod(server, values, 'listBuilds', kw={'userID': user['id']},
start=buildStart, dataName='builds', prefix='build',
order=buildOrder, pageSize=10)
return _genHTML(environ, 'userinfo.chtml')
# headers shown in rpminfo and buildinfo pages
RPM_HEADERS = ['summary', 'description', 'license', 'disturl', 'vcs']
def rpminfo(environ, rpmID, fileOrder='name', fileStart=None, buildrootOrder='-id',
buildrootStart=None):
values = _initValues(environ, 'RPM Info', 'builds')
server = _getServer(environ)
rpmID = int(rpmID)
try:
rpm = server.getRPM(rpmID, strict=True)
except koji.GenericError:
raise koji.GenericError('invalid RPM ID: %i' % rpmID)
values['title'] = '%(name)s-%%s%(version)s-%(release)s.%(arch)s.rpm' % rpm + ' | RPM Info'
epochStr = ''
if rpm['epoch'] is not None:
epochStr = '%s:' % rpm['epoch']
values['title'] = values['title'] % epochStr
build = None
if rpm['build_id'] is not None:
build = server.getBuild(rpm['build_id'])
builtInRoot = None
if rpm['buildroot_id'] is not None:
builtInRoot = server.getBuildroot(rpm['buildroot_id'])
if rpm['external_repo_id'] == 0:
dep_names = {
koji.DEP_REQUIRE: 'requires',
koji.DEP_PROVIDE: 'provides',
koji.DEP_OBSOLETE: 'obsoletes',
koji.DEP_CONFLICT: 'conflicts',
koji.DEP_SUGGEST: 'suggests',
koji.DEP_ENHANCE: 'enhances',
koji.DEP_SUPPLEMENT: 'supplements',
koji.DEP_RECOMMEND: 'recommends',
}
deps = server.getRPMDeps(rpm['id'])
for dep_type in dep_names:
values[dep_names[dep_type]] = [d for d in deps if d['type'] == dep_type]
values[dep_names[dep_type]].sort(key=_sortbyname)
result = server.getRPMHeaders(rpm['id'], headers=RPM_HEADERS)
for header in RPM_HEADERS:
values[header] = koji.fixEncoding(result.get(header))
buildroots = kojiweb.util.paginateMethod(server, values, 'listBuildroots',
kw={'rpmID': rpm['id']},
start=buildrootStart,
dataName='buildroots',
prefix='buildroot',
order=buildrootOrder)
values['rpmID'] = rpmID
values['rpm'] = rpm
values['build'] = build
values['builtInRoot'] = builtInRoot
values['buildroots'] = buildroots
kojiweb.util.paginateMethod(server, values, 'listRPMFiles', args=[rpm['id']],
start=fileStart, dataName='files', prefix='file', order=fileOrder)
return _genHTML(environ, 'rpminfo.chtml')
def archiveinfo(environ, archiveID, fileOrder='name', fileStart=None, buildrootOrder='-id',
buildrootStart=None):
values = _initValues(environ, 'Archive Info', 'builds')
server = _getServer(environ)
archiveID = int(archiveID)
archive = server.getArchive(archiveID)
archive_type = server.getArchiveType(type_id=archive['type_id'])
build = server.getBuild(archive['build_id'])
maveninfo = False
if 'group_id' in archive:
maveninfo = True
wininfo = False
if 'relpath' in archive:
wininfo = True
builtInRoot = None
if archive['buildroot_id'] is not None:
builtInRoot = server.getBuildroot(archive['buildroot_id'])
kojiweb.util.paginateMethod(server, values, 'listArchiveFiles', args=[archive['id']],
start=fileStart, dataName='files', prefix='file', order=fileOrder)
buildroots = kojiweb.util.paginateMethod(server, values, 'listBuildroots',
kw={'archiveID': archive['id']},
start=buildrootStart,
dataName='buildroots',
prefix='buildroot',
order=buildrootOrder)
values['title'] = archive['filename'] + ' | Archive Info'
values['archiveID'] = archive['id']
values['archive'] = archive
values['archive_type'] = archive_type
values['build'] = build
values['maveninfo'] = maveninfo
values['wininfo'] = wininfo
values['builtInRoot'] = builtInRoot
values['buildroots'] = buildroots
values['show_rpm_components'] = server.listRPMs(imageID=archive['id'], queryOpts={'limit': 1})
values['show_archive_components'] = server.listArchives(imageID=archive['id'],
queryOpts={'limit': 1})
return _genHTML(environ, 'archiveinfo.chtml')
def fileinfo(environ, filename, rpmID=None, archiveID=None):
values = _initValues(environ, 'File Info', 'builds')
server = _getServer(environ)
values['rpm'] = None
values['archive'] = None
if rpmID:
rpmID = int(rpmID)
rpm = server.getRPM(rpmID)
if not rpm:
raise koji.GenericError('invalid RPM ID: %i' % rpmID)
file = server.getRPMFile(rpm['id'], filename)
if not file:
raise koji.GenericError('no file %s in RPM %i' % (filename, rpmID))
values['rpm'] = rpm
elif archiveID:
archiveID = int(archiveID)
archive = server.getArchive(archiveID)
if not archive:
raise koji.GenericError('invalid archive ID: %i' % archiveID)
file = server.getArchiveFile(archive['id'], filename)
if not file:
raise koji.GenericError('no file %s in archive %i' % (filename, archiveID))
values['archive'] = archive
else:
raise koji.GenericError('either rpmID or archiveID must be specified')
values['title'] = file['name'] + ' | File Info'
values['file'] = file
return _genHTML(environ, 'fileinfo.chtml')
def cancelbuild(environ, buildID):
server = _getServer(environ)
_assertLogin(environ)
buildID = int(buildID)
build = server.getBuild(buildID)
if build is None:
raise koji.GenericError('unknown build ID: %i' % buildID)
result = server.cancelBuild(build['id'])
if not result:
raise koji.GenericError('unable to cancel build')
_redirect(environ, 'buildinfo?buildID=%i' % build['id'])
def hosts(environ, state='enabled', start=None, order='name'):
values = _initValues(environ, 'Hosts', 'hosts')
server = _getServer(environ)
values['order'] = order
args = {}
if state == 'enabled':
args['enabled'] = True
elif state == 'disabled':
args['enabled'] = False
else:
state = 'all'
values['state'] = state
hosts = server.listHosts(**args)
server.multicall = True
for host in hosts:
server.getLastHostUpdate(host['id'])
updates = server.multiCall()
for host, [lastUpdate] in zip(hosts, updates):
host['last_update'] = lastUpdate
# Paginate after retrieving last update info so we can sort on it
kojiweb.util.paginateList(values, hosts, start, 'hosts', 'host', order)
return _genHTML(environ, 'hosts.chtml')
def hostinfo(environ, hostID=None, userID=None):
values = _initValues(environ, 'Host Info', 'hosts')
server = _getServer(environ)
if hostID:
hostID = _validate_name_or_id(hostID)
host = server.getHost(hostID)
if host is None:
raise koji.GenericError('invalid host ID: %s' % hostID)
elif userID:
userID = int(userID)
hosts = server.listHosts(userID=userID)
host = None
if hosts:
host = hosts[0]
if host is None:
raise koji.GenericError('invalid host ID: %s' % userID)
else:
raise koji.GenericError('hostID or userID must be provided')
values['title'] = host['name'] + ' | Host Info'
channels = server.listChannels(host['id'])
channels.sort(key=_sortbyname)
buildroots = server.listBuildroots(hostID=host['id'],
state=[state[1] for state in koji.BR_STATES.items()
if state[0] != 'EXPIRED'])
buildroots.sort(key=lambda x: x['create_event_time'], reverse=True)
values['host'] = host
values['channels'] = channels
values['buildroots'] = buildroots
values['lastUpdate'] = server.getLastHostUpdate(host['id'])
if environ['koji.currentUser']:
values['perms'] = server.getUserPerms(environ['koji.currentUser']['id'])
else:
values['perms'] = []
return _genHTML(environ, 'hostinfo.chtml')
def hostedit(environ, hostID):
server = _getServer(environ)
_assertLogin(environ)
hostID = int(hostID)
host = server.getHost(hostID)
if host is None:
raise koji.GenericError('no host with ID: %i' % hostID)
form = environ['koji.form']
if 'save' in form:
arches = form['arches'].value
capacity = float(form['capacity'].value)
description = form['description'].value
comment = form['comment'].value
enabled = bool('enabled' in form)
channels = form.getlist('channels')
server.editHost(host['id'], arches=arches, capacity=capacity,
description=description, comment=comment)
if enabled != host['enabled']:
if enabled:
server.enableHost(host['name'])
else:
server.disableHost(host['name'])
hostChannels = [c['name'] for c in server.listChannels(hostID=host['id'])]
for channel in hostChannels:
if channel not in channels:
server.removeHostFromChannel(host['name'], channel)
for channel in channels:
if channel not in hostChannels:
server.addHostToChannel(host['name'], channel)
_redirect(environ, 'hostinfo?hostID=%i' % host['id'])
elif 'cancel' in form:
_redirect(environ, 'hostinfo?hostID=%i' % host['id'])
else:
values = _initValues(environ, 'Edit Host', 'hosts')
values['host'] = host
allChannels = server.listChannels()
allChannels.sort(key=_sortbyname)
values['allChannels'] = allChannels
values['hostChannels'] = server.listChannels(hostID=host['id'])
return _genHTML(environ, 'hostedit.chtml')
def disablehost(environ, hostID):
server = _getServer(environ)
_assertLogin(environ)
hostID = int(hostID)
host = server.getHost(hostID, strict=True)
server.disableHost(host['name'])
_redirect(environ, 'hostinfo?hostID=%i' % host['id'])
def enablehost(environ, hostID):
server = _getServer(environ)
_assertLogin(environ)
hostID = int(hostID)
host = server.getHost(hostID, strict=True)
server.enableHost(host['name'])
_redirect(environ, 'hostinfo?hostID=%i' % host['id'])
def channelinfo(environ, channelID):
values = _initValues(environ, 'Channel Info', 'hosts')
server = _getServer(environ)
channelID = int(channelID)
channel = server.getChannel(channelID)
if channel is None:
raise koji.GenericError('invalid channel ID: %i' % channelID)
values['title'] = channel['name'] + ' | Channel Info'
states = [koji.TASK_STATES[s] for s in ('FREE', 'OPEN', 'ASSIGNED')]
values['taskCount'] = \
server.listTasks(opts={'channel_id': channelID, 'state': states},
queryOpts={'countOnly': True})
hosts = server.listHosts(channelID=channelID)
hosts.sort(key=_sortbyname)
values['channel'] = channel
values['hosts'] = hosts
values['enabled_hosts'] = len([h for h in hosts if h['enabled']])
values['ready_hosts'] = len([h for h in hosts if h['ready']])
return _genHTML(environ, 'channelinfo.chtml')
def buildrootinfo(environ, buildrootID):
values = _initValues(environ, 'Buildroot Info', 'hosts')
server = _getServer(environ)
buildrootID = int(buildrootID)
buildroot = server.getBuildroot(buildrootID)
if buildroot is None:
raise koji.GenericError('unknown buildroot ID: %i' % buildrootID)
elif buildroot['br_type'] == koji.BR_TYPES['STANDARD']:
template = 'buildrootinfo.chtml'
values['task'] = server.getTaskInfo(buildroot['task_id'], request=True)
else:
template = 'buildrootinfo_cg.chtml'
# TODO - fetch tools and extras info
values['title'] = '%s | Buildroot Info' % kojiweb.util.brLabel(buildroot)
values['buildroot'] = buildroot
return _genHTML(environ, template)
def rpmlist(environ, type, buildrootID=None, imageID=None, start=None, order='nvr'):
"""
rpmlist requires a buildrootID OR an imageID to be passed in. From one
of these values it will paginate a list of rpms included in the
corresponding object. (buildroot or image)
"""
values = _initValues(environ, 'RPM List', 'hosts')
server = _getServer(environ)
if buildrootID is not None:
buildrootID = int(buildrootID)
buildroot = server.getBuildroot(buildrootID)
values['buildroot'] = buildroot
if buildroot is None:
raise koji.GenericError('unknown buildroot ID: %i' % buildrootID)
if type == 'component':
kojiweb.util.paginateMethod(server, values, 'listRPMs',
kw={'componentBuildrootID': buildroot['id']},
start=start, dataName='rpms',
prefix='rpm', order=order)
elif type == 'built':
kojiweb.util.paginateMethod(server, values, 'listRPMs',
kw={'buildrootID': buildroot['id']},
start=start, dataName='rpms',
prefix='rpm', order=order)
else:
raise koji.GenericError('unrecognized type of rpmlist')
elif imageID is not None:
imageID = int(imageID)
values['image'] = server.getArchive(imageID)
# If/When future image types are supported, add elifs here if needed.
if type == 'image':
kojiweb.util.paginateMethod(server, values, 'listRPMs',
kw={'imageID': imageID},
start=start, dataName='rpms',
prefix='rpm', order=order)
else:
raise koji.GenericError('unrecognized type of image rpmlist')
else:
# It is an error if neither buildrootID and imageID are defined.
raise koji.GenericError('Both buildrootID and imageID are None')
values['type'] = type
values['order'] = order
return _genHTML(environ, 'rpmlist.chtml')
def archivelist(environ, type, buildrootID=None, imageID=None, start=None, order='filename'):
values = _initValues(environ, 'Archive List', 'hosts')
server = _getServer(environ)
if buildrootID is not None:
buildrootID = int(buildrootID)
buildroot = server.getBuildroot(buildrootID)
values['buildroot'] = buildroot
if buildroot is None:
raise koji.GenericError('unknown buildroot ID: %i' % buildrootID)
if type == 'component':
kojiweb.util.paginateMethod(server, values, 'listArchives',
kw={'componentBuildrootID': buildroot['id']},
start=start, dataName='archives', prefix='archive',
order=order)
elif type == 'built':
kojiweb.util.paginateMethod(server, values, 'listArchives',
kw={'buildrootID': buildroot['id']},
start=start, dataName='archives', prefix='archive',
order=order)
else:
raise koji.GenericError('unrecognized type of archivelist')
elif imageID is not None:
imageID = int(imageID)
values['image'] = server.getArchive(imageID)
# If/When future image types are supported, add elifs here if needed.
if type == 'image':
kojiweb.util.paginateMethod(server, values, 'listArchives', kw={'imageID': imageID},
start=start, dataName='archives', prefix='archive',
order=order)
else:
raise koji.GenericError('unrecognized type of archivelist')
else:
# It is an error if neither buildrootID and imageID are defined.
raise koji.GenericError('Both buildrootID and imageID are None')
values['type'] = type
values['order'] = order
return _genHTML(environ, 'archivelist.chtml')
def buildtargets(environ, start=None, order='name'):
values = _initValues(environ, 'Build Targets', 'buildtargets')
server = _getServer(environ)
kojiweb.util.paginateMethod(server, values, 'getBuildTargets',
start=start, dataName='targets', prefix='target', order=order)
values['order'] = order
if environ['koji.currentUser']:
values['perms'] = server.getUserPerms(environ['koji.currentUser']['id'])
else:
values['perms'] = []
return _genHTML(environ, 'buildtargets.chtml')
def buildtargetinfo(environ, targetID=None, name=None):
values = _initValues(environ, 'Build Target Info', 'buildtargets')
server = _getServer(environ)
target = None
if targetID is not None:
targetID = int(targetID)
target = server.getBuildTarget(targetID)
elif name is not None:
target = server.getBuildTarget(name)
if target is None:
raise koji.GenericError('invalid build target: %s' % (targetID or name))
values['title'] = target['name'] + ' | Build Target Info'
buildTag = server.getTag(target['build_tag'])
destTag = server.getTag(target['dest_tag'])
values['target'] = target
values['buildTag'] = buildTag
values['destTag'] = destTag
if environ['koji.currentUser']:
values['perms'] = server.getUserPerms(environ['koji.currentUser']['id'])
else:
values['perms'] = []
return _genHTML(environ, 'buildtargetinfo.chtml')
def buildtargetedit(environ, targetID):
server = _getServer(environ)
_assertLogin(environ)
targetID = int(targetID)
target = server.getBuildTarget(targetID)
if target is None:
raise koji.GenericError('invalid build target: %s' % targetID)
form = environ['koji.form']
if 'save' in form:
name = form.getfirst('name')
buildTagID = int(form.getfirst('buildTag'))
buildTag = server.getTag(buildTagID)
if buildTag is None:
raise koji.GenericError('invalid tag ID: %i' % buildTagID)
destTagID = int(form.getfirst('destTag'))
destTag = server.getTag(destTagID)
if destTag is None:
raise koji.GenericError('invalid tag ID: %i' % destTagID)
server.editBuildTarget(target['id'], name, buildTag['id'], destTag['id'])
_redirect(environ, 'buildtargetinfo?targetID=%i' % target['id'])
elif 'cancel' in form:
_redirect(environ, 'buildtargetinfo?targetID=%i' % target['id'])
else:
values = _initValues(environ, 'Edit Build Target', 'buildtargets')
tags = server.listTags()
tags.sort(key=_sortbyname)
values['target'] = target
values['tags'] = tags
return _genHTML(environ, 'buildtargetedit.chtml')
def buildtargetcreate(environ):
server = _getServer(environ)
_assertLogin(environ)
form = environ['koji.form']
if 'add' in form:
# Use the str .value field of the StringField object,
# since xmlrpclib doesn't know how to marshal the StringFields
# returned by mod_python
name = form.getfirst('name')
buildTagID = int(form.getfirst('buildTag'))
destTagID = int(form.getfirst('destTag'))
server.createBuildTarget(name, buildTagID, destTagID)
target = server.getBuildTarget(name)
if target is None:
raise koji.GenericError('error creating build target "%s"' % name)
_redirect(environ, 'buildtargetinfo?targetID=%i' % target['id'])
elif 'cancel' in form:
_redirect(environ, 'buildtargets')
else:
values = _initValues(environ, 'Add Build Target', 'builtargets')
tags = server.listTags()
tags.sort(key=_sortbyname)
values['target'] = None
values['tags'] = tags
return _genHTML(environ, 'buildtargetedit.chtml')
def buildtargetdelete(environ, targetID):
server = _getServer(environ)
_assertLogin(environ)
targetID = int(targetID)
target = server.getBuildTarget(targetID)
if target is None:
raise koji.GenericError('invalid build target: %i' % targetID)
server.deleteBuildTarget(target['id'])
_redirect(environ, 'buildtargets')
def reports(environ):
_getServer(environ)
_initValues(environ, 'Reports', 'reports')
return _genHTML(environ, 'reports.chtml')
def buildsbyuser(environ, start=None, order='-builds'):
values = _initValues(environ, 'Builds by User', 'reports')
server = _getServer(environ)
maxBuilds = 1
users = server.listUsers()
server.multicall = True
for user in users:
server.listBuilds(userID=user['id'], queryOpts={'countOnly': True})
buildCounts = server.multiCall()
for user, [numBuilds] in zip(users, buildCounts):
user['builds'] = numBuilds
if numBuilds > maxBuilds:
maxBuilds = numBuilds
values['order'] = order
graphWidth = 400.0
values['graphWidth'] = graphWidth
values['maxBuilds'] = maxBuilds
values['increment'] = graphWidth / maxBuilds
kojiweb.util.paginateList(values, users, start, 'userBuilds', 'userBuild', order)
return _genHTML(environ, 'buildsbyuser.chtml')
def rpmsbyhost(environ, start=None, order=None, hostArch=None, rpmArch=None):
values = _initValues(environ, 'RPMs by Host', 'reports')
server = _getServer(environ)
hostArch = _validate_arch(hostArch)
rpmArch = _validate_arch(rpmArch)
maxRPMs = 1
hostArchFilter = hostArch
if hostArchFilter == 'ix86':
hostArchFilter = ['i386', 'i486', 'i586', 'i686']
hosts = server.listHosts(arches=hostArchFilter)
rpmArchFilter = rpmArch
if rpmArchFilter == 'ix86':
rpmArchFilter = ['i386', 'i486', 'i586', 'i686']
server.multicall = True
for host in hosts:
server.listRPMs(hostID=host['id'], arches=rpmArchFilter, queryOpts={'countOnly': True})
rpmCounts = server.multiCall()
for host, [numRPMs] in zip(hosts, rpmCounts):
host['rpms'] = numRPMs
if numRPMs > maxRPMs:
maxRPMs = numRPMs
values['hostArch'] = hostArch
hostArchList = sorted(server.getAllArches())
values['hostArchList'] = hostArchList
values['rpmArch'] = rpmArch
values['rpmArchList'] = hostArchList + ['noarch', 'src']
if order is None:
order = '-rpms'
values['order'] = order
graphWidth = 400.0
values['graphWidth'] = graphWidth
values['maxRPMs'] = maxRPMs
values['increment'] = graphWidth / maxRPMs
kojiweb.util.paginateList(values, hosts, start, 'hosts', 'host', order)
return _genHTML(environ, 'rpmsbyhost.chtml')
def packagesbyuser(environ, start=None, order=None):
values = _initValues(environ, 'Packages by User', 'reports')
server = _getServer(environ)
maxPackages = 1
users = server.listUsers()
server.multicall = True
for user in users:
server.count('listPackages', userID=user['id'], with_dups=True)
packageCounts = server.multiCall()
for user, [numPackages] in zip(users, packageCounts):
user['packages'] = numPackages
if numPackages > maxPackages:
maxPackages = numPackages
if order is None:
order = '-packages'
values['order'] = order
graphWidth = 400.0
values['graphWidth'] = graphWidth
values['maxPackages'] = maxPackages
values['increment'] = graphWidth / maxPackages
kojiweb.util.paginateList(values, users, start, 'users', 'user', order)
return _genHTML(environ, 'packagesbyuser.chtml')
def tasksbyhost(environ, start=None, order='-tasks', hostArch=None):
values = _initValues(environ, 'Tasks by Host', 'reports')
server = _getServer(environ)
maxTasks = 1
hostArch = _validate_arch(hostArch)
hostArchFilter = hostArch
if hostArchFilter == 'ix86':
hostArchFilter = ['i386', 'i486', 'i586', 'i686']
hosts = server.listHosts(arches=hostArchFilter)
server.multicall = True
for host in hosts:
server.listTasks(opts={'host_id': host['id']}, queryOpts={'countOnly': True})
taskCounts = server.multiCall()
for host, [numTasks] in zip(hosts, taskCounts):
host['tasks'] = numTasks
if numTasks > maxTasks:
maxTasks = numTasks
values['hostArch'] = hostArch
hostArchList = sorted(server.getAllArches())
values['hostArchList'] = hostArchList
values['order'] = order
graphWidth = 400.0
values['graphWidth'] = graphWidth
values['maxTasks'] = maxTasks
values['increment'] = graphWidth / maxTasks
kojiweb.util.paginateList(values, hosts, start, 'hosts', 'host', order)
return _genHTML(environ, 'tasksbyhost.chtml')
def tasksbyuser(environ, start=None, order='-tasks'):
values = _initValues(environ, 'Tasks by User', 'reports')
server = _getServer(environ)
maxTasks = 1
users = server.listUsers()
server.multicall = True
for user in users:
server.listTasks(opts={'owner': user['id']}, queryOpts={'countOnly': True})
taskCounts = server.multiCall()
for user, [numTasks] in zip(users, taskCounts):
user['tasks'] = numTasks
if numTasks > maxTasks:
maxTasks = numTasks
values['order'] = order
graphWidth = 400.0
values['graphWidth'] = graphWidth
values['maxTasks'] = maxTasks
values['increment'] = graphWidth / maxTasks
kojiweb.util.paginateList(values, users, start, 'users', 'user', order)
return _genHTML(environ, 'tasksbyuser.chtml')
def buildsbystatus(environ, days='7'):
values = _initValues(environ, 'Builds by Status', 'reports')
server = _getServer(environ)
days = int(days)
if days != -1:
seconds = 60 * 60 * 24 * days
dateAfter = time.time() - seconds
else:
dateAfter = None
values['days'] = days
server.multicall = True
# use taskID=-1 to filter out builds with a null task_id (imported rather than built in koji)
server.listBuilds(completeAfter=dateAfter, state=koji.BUILD_STATES['COMPLETE'], taskID=-1,
queryOpts={'countOnly': True})
server.listBuilds(completeAfter=dateAfter, state=koji.BUILD_STATES['FAILED'], taskID=-1,
queryOpts={'countOnly': True})
server.listBuilds(completeAfter=dateAfter, state=koji.BUILD_STATES['CANCELED'], taskID=-1,
queryOpts={'countOnly': True})
[[numSucceeded], [numFailed], [numCanceled]] = server.multiCall()
values['numSucceeded'] = numSucceeded
values['numFailed'] = numFailed
values['numCanceled'] = numCanceled
maxBuilds = 1
for value in (numSucceeded, numFailed, numCanceled):
if value > maxBuilds:
maxBuilds = value
graphWidth = 400.0
values['graphWidth'] = graphWidth
values['maxBuilds'] = maxBuilds
values['increment'] = graphWidth / maxBuilds
return _genHTML(environ, 'buildsbystatus.chtml')
def buildsbytarget(environ, days='7', start=None, order='-builds'):
values = _initValues(environ, 'Builds by Target', 'reports')
server = _getServer(environ)
days = int(days)
if days != -1:
seconds = 60 * 60 * 24 * days
dateAfter = time.time() - seconds
else:
dateAfter = None
values['days'] = days
targets = {}
maxBuilds = 1
tasks = server.listTasks(opts={'method': 'build', 'completeAfter': dateAfter, 'decode': True})
for task in tasks:
targetName = task['request'][1]
target = targets.get(targetName)
if not target:
target = {'name': targetName}
targets[targetName] = target
builds = target.get('builds', 0) + 1
target['builds'] = builds
if builds > maxBuilds:
maxBuilds = builds
kojiweb.util.paginateList(values, list(targets.values()), start, 'targets', 'target', order)
values['order'] = order
graphWidth = 400.0
values['graphWidth'] = graphWidth
values['maxBuilds'] = maxBuilds
values['increment'] = graphWidth / maxBuilds
return _genHTML(environ, 'buildsbytarget.chtml')
def _filter_hosts_by_arch(hosts, arch):
if arch == '__all__':
return hosts
else:
return [h for h in hosts if arch in h['arches'].split()]
def clusterhealth(environ, arch='__all__'):
arch = _validate_arch(arch)
values = _initValues(environ, 'Cluster health', 'reports')
server = _getServer(environ)
channels = server.listChannels()
server.multicall = True
for channel in channels:
server.listHosts(channelID=channel['id'])
max_enabled = 0
max_capacity = 0
arches = set()
for channel, [hosts] in zip(channels, server.multiCall()):
for host in hosts:
arches |= set(host['arches'].split())
hosts = _filter_hosts_by_arch(hosts, arch)
channel['enabled'] = len([x for x in hosts if x['enabled']])
channel['disabled'] = len(hosts) - channel['enabled']
channel['ready'] = len([x for x in hosts if x['ready']])
channel['capacity'] = sum([x['capacity'] for x in hosts])
channel['load'] = sum([x['task_load'] for x in hosts])
if max_enabled < channel['enabled']:
max_enabled = channel['enabled']
if max_capacity < channel['capacity']:
max_capacity = channel['capacity']
graphWidth = 400.0
# compute values for channels
for channel in channels:
try:
channel['capacityPerc'] = channel['capacity'] / max_capacity * 100
except ZeroDivisionError:
channel['capacityPerc'] = 0
try:
channel['enabledPerc'] = channel['enabled'] / max_enabled * 100
except ZeroDivisionError:
channel['enabledPerc'] = 0
if channel['capacity']:
channel['perc_load'] = min(100, channel['load'] / channel['capacity'] * 100)
else:
channel['perc_load'] = 0.0
if channel['enabled']:
channel['perc_ready'] = min(100, channel['ready'] / channel['enabled'] * 100)
else:
channel['perc_ready'] = 0.0
values['arch'] = arch
values['arches'] = sorted(arches)
values['graphWidth'] = graphWidth
values['channels'] = sorted(channels, key=lambda x: x['name'])
return _genHTML(environ, 'clusterhealth.chtml')
def recentbuilds(environ, user=None, tag=None, package=None):
values = _initValues(environ, 'Recent Build RSS')
server = _getServer(environ)
tagObj = None
if tag is not None:
tag = _validate_name_or_id(tag)
tagObj = server.getTag(tag, strict=True)
userObj = None
if user is not None:
user = _validate_name_or_id(user)
userObj = server.getUser(user, strict=True)
packageObj = None
if package:
package = _validate_name_or_id(package)
packageObj = server.getPackage(package, strict=True)
if tagObj is not None:
builds = server.listTagged(tagObj['id'], inherit=True,
package=(packageObj and packageObj['name'] or None),
owner=(userObj and userObj['name'] or None))
builds.sort(key=kojiweb.util.sortByKeyFuncNoneGreatest('completion_time'), reverse=True)
builds = builds[:20]
else:
kwargs = {}
if userObj:
kwargs['userID'] = userObj['id']
if packageObj:
kwargs['packageID'] = packageObj['id']
builds = server.listBuilds(queryOpts={'order': '-completion_time', 'limit': 20}, **kwargs)
server.multicall = True
for build in builds:
if build['task_id']:
server.getTaskInfo(build['task_id'], request=True)
else:
server.echo(None)
tasks = server.multiCall()
server.multicall = True
queryOpts = {'limit': 3}
for build in builds:
if build['state'] == koji.BUILD_STATES['COMPLETE']:
server.getChangelogEntries(build['build_id'], queryOpts=queryOpts)
else:
server.echo(None)
clogs = server.multiCall()
for i in range(len(builds)):
task = tasks[i][0]
if isinstance(task, list):
# this is the output of server.echo(None) above
task = None
builds[i]['task'] = task
builds[i]['changelog'] = clogs[i][0]
values['tag'] = tagObj
values['user'] = userObj
values['package'] = packageObj
values['builds'] = builds
values['weburl'] = _getBaseURL(environ)
environ['koji.headers'].append(['Content-Type', 'text/xml'])
return _genHTML(environ, 'recentbuilds.chtml')
_infoURLs = {'package': 'packageinfo?packageID=%(id)i',
'build': 'buildinfo?buildID=%(id)i',
'tag': 'taginfo?tagID=%(id)i',
'target': 'buildtargetinfo?targetID=%(id)i',
'user': 'userinfo?userID=%(id)i',
'host': 'hostinfo?hostID=%(id)i',
'rpm': 'rpminfo?rpmID=%(id)i',
'maven': 'archiveinfo?archiveID=%(id)i',
'win': 'archiveinfo?archiveID=%(id)i'}
_DEFAULT_SEARCH_ORDER = {
# For searches against large tables, use '-id' to show most recent first
'build': '-id',
'rpm': '-id',
'maven': '-id',
'win': '-id',
# for other tables, ordering by name makes much more sense
'tag': 'name',
'target': 'name',
'package': 'name',
# any type not listed will default to 'name'
}
def search(environ, start=None, order=None):
if start is not None:
start = int(start)
values = _initValues(environ, 'Search', 'search')
server = _getServer(environ)
values['error'] = None
form = environ['koji.form']
if 'terms' in form and form['terms']:
terms = form['terms'].value
terms = terms.strip()
type = form['type'].value
match = form['match'].value
values['terms'] = terms
values['type'] = type
values['match'] = match
if match not in ('glob', 'regexp', 'exact'):
raise koji.GenericError("Invalid match type: %r" % match)
if not _VALID_SEARCH_RE.match(terms):
values['error'] = 'Invalid search terms<br/>' + \
'Search terms may contain only these characters: ' + \
_VALID_SEARCH_CHARS + _VALID_SEARCH_SYMS
values['terms'] = ''
return _genHTML(environ, 'search.chtml')
if match == 'regexp':
try:
re.compile(terms)
except Exception:
values['error'] = 'Invalid regular expression'
values['terms'] = ''
return _genHTML(environ, 'search.chtml')
infoURL = _infoURLs.get(type)
if not infoURL:
raise koji.GenericError('unknown search type: %s' % type)
values['infoURL'] = infoURL
order = order or _DEFAULT_SEARCH_ORDER.get(type, 'name')
values['order'] = order
results = kojiweb.util.paginateMethod(server, values, 'search', args=(terms, type, match),
start=start, dataName='results', prefix='result',
order=order)
if not start and len(results) == 1:
# if we found exactly one result, skip the result list and redirect to the info page
# (you're feeling lucky)
_redirect(environ, infoURL % results[0])
else:
if type == 'maven':
typeLabel = 'Maven artifacts'
elif type == 'win':
typeLabel = 'Windows artifacts'
else:
typeLabel = '%ss' % type
values['typeLabel'] = typeLabel
return _genHTML(environ, 'search.chtml')
else:
return _genHTML(environ, 'search.chtml')
def api(environ):
values = _initValues(environ, 'API', 'api')
server = _getServer(environ)
values['methods'] = sorted(server._listapi(), key=lambda x: x['name'])
try:
values['koji_version'] = server.getKojiVersion()
except koji.GenericError:
values['koji_version'] = "Can't determine (older then 1.23)"
return _genHTML(environ, 'api.chtml')
def watchlogs(environ, taskID):
values = _initValues(environ)
if isinstance(taskID, list):
values['tasks'] = ', '.join([int(x) for x in taskID])
else:
values['tasks'] = int(taskID)
html = """
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<head>
<script type="text/javascript" src="/koji-static/js/watchlogs.js"></script>
<title>Logs for task %(tasks)s | %(siteName)s</title>
</head>
<body onload="watchLogs('logs')">
<pre id="logs">
<span>Loading logs for task %(tasks)s...</span>
</pre>
</body>
</html>
""" % values
return html
def repoinfo(environ, repoID):
values = _initValues(environ, 'Repo Info', 'tags')
server = _getServer(environ)
repoID = _validate_name_or_id(repoID)
values['repo_id'] = repoID
repo_info = server.repoInfo(repoID, strict=False)
values['repo'] = repo_info
if repo_info:
topurl = environ['koji.options']['KojiFilesURL']
pathinfo = koji.PathInfo(topdir=topurl)
if repo_info['dist']:
values['url'] = pathinfo.distrepo(repo_info['id'], repo_info['tag_name'])
else:
values['url'] = pathinfo.repo(repo_info['id'], repo_info['tag_name'])
values['repo_json'] = os.path.join(pathinfo.repo(repo_info['id'], repo_info['tag_name']),
'repo.json')
return _genHTML(environ, 'repoinfo.chtml')