debian-koji/www/kojiweb/index.py
2017-03-28 11:46:47 +02:00

2294 lines
80 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 os
import os.path
import re
import sys
import mimetypes
import Cookie
import datetime
import logging
import time
import koji
import kojiweb.util
from koji.server import ServerRedirect
from kojiweb.util import _initValues
from kojiweb.util import _genHTML
from kojiweb.util import _getValidTokens
from koji.util import sha1_constructor
# Convenience definition of a commonly-used sort function
_sortbyname = kojiweb.util.sortByKeyFunc('name')
#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')
shasum = sha1_constructor(value)
shasum.update(options['Secret'].value)
value = "%s:%s" % (shasum.hexdigest(), value)
cookies = Cookie.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 = Cookie.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 = Cookie.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.warn('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')
shasum = sha1_constructor(value)
shasum.update(options['Secret'].value)
if shasum.hexdigest() != sig:
authlogger.warn('invalid user cookie: %s:%s', sig, value)
return None
parts = value.split(":", 1)
if len(parts) != 2:
authlogger.warn('invalid signed user cookie: %s:%s', sig, value)
# no embedded timestamp
return None
user, timestamp = parts
try:
timestamp = float(timestamp)
except ValueError:
authlogger.warn('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 _krbLogin(environ, session, principal):
options = environ['koji.options']
wprinc = options['WebPrincipal']
keytab = options['WebKeytab']
ccache = options['WebCCache']
return session.krb_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 _krbLogin(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):
opts = environ['koji.options']
session = koji.ClientSession(opts['KojiHubURL'],
opts={'krbservice': opts['KrbService'],
'krb_rdns': opts['KrbRDNS']})
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):
if page:
# We'll work with the page we were given
pass
elif 'HTTP_REFERER' in environ:
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_kerb should have performed authentication before presenting this page')
if not _krbLogin(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(kojiweb.util.sortByKeyFunc('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 == 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',
'buildArch',
'chainbuild',
'maven',
'buildMaven',
'chainmaven',
'wrapperRPM',
'winbuild',
'vmExec',
'waitrepo',
'tagBuild',
'newRepo',
'createrepo',
'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', 'tagBuild', 'tagNotification', 'waitrepo', 'livecd', 'appliance', 'image', 'livemedia']
# Tasks that can have children
_PARENT_TASKS = ['build', 'chainbuild', 'maven', 'chainmaven', 'winbuild', 'newRepo', '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)
opts = {'decode': True}
if owner:
if owner.isdigit():
owner = int(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'] = _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:
try:
channelID = int(channelID)
except ValueError:
pass
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 != 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'] == 'buildArch':
buildTag = server.getTag(params[1])
values['buildTag'] = buildTag
elif task['method'] == 'buildMaven':
buildTag = params[1]
values['buildTag'] = buildTag
elif task['method'] == 'buildSRPMFromSCM':
if len(params) > 1:
buildTag = server.getTag(params[1])
values['buildTag'] = buildTag
elif task['method'] == 'tagBuild':
destTag = server.getTag(params[0])
build = server.getBuild(params[1])
values['destTag'] = destTag
values['build'] = build
elif task['method'] == 'newRepo':
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)
if task['state'] in (koji.TASK_STATES['CLOSED'], koji.TASK_STATES['FAILED']):
try:
result = server.getTaskResult(task['id'])
values['result'] = result
values['excClass'] = None
except:
excClass, exc = sys.exc_info()[:2]
values['result'] = exc
values['excClass'] = excClass
# clear the exception, since we're just using
# it for display purposes
sys.exc_clear()
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).iteritems():
paths += [(volume, relname) for volume in volumes]
values['output'] = sorted(paths, cmp = _sortByExtAndName)
if environ['koji.currentUser']:
values['perms'] = server.getUserPerms(environ['koji.currentUser']['id'])
else:
values['perms'] = []
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.iteritems():
for volume, file_stats in volumes_data.iteritems():
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(a, b):
"""Sort two filename tuples, first by extension, and then by name."""
aRoot, aExt = os.path.splitext(os.path.basename(a[1]))
bRoot, bExt = os.path.splitext(os.path.basename(b[1]))
return cmp(aExt, bExt) or cmp(aRoot, bRoot)
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)
def _chunk_file(server, environ, taskID, name, offset, size):
remaining = size
encode_int = koji.encode_int
while True:
if remaining <= 0:
break
chunk_size = 1048576
if remaining < chunk_size:
chunk_size = remaining
content = server.downloadTaskOutput(taskID, name, offset=encode_int(offset), size=chunk_size)
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 == 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'] = []
values['childID'] = childID
return _genHTML(environ, 'tags.chtml')
_PREFIX_CHARS = [chr(char) for char in range(48, 58) + 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 != None:
if tagID.isdigit():
tagID = int(tagID)
tag = server.getTag(tagID, strict=True)
values['tagID'] = tagID
values['tag'] = tag
user = None
if userID != None:
if userID.isdigit():
userID = int(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)
if packageID.isdigit():
packageID = int(packageID)
package = server.getPackage(packageID)
if package == 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)
if tagID.isdigit():
tagID = int(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(_sortbyname)
destTargets = server.getBuildTargets(destTagID=tag['id'])
destTargets.sort(_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 != 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 == 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 == 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)
if extrepoID.isdigit():
extrepoID = int(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)
build = server.getBuild(buildID)
values['title'] = koji.buildLabel(build) + ' | Build Info'
tags = server.listTags(build['id'])
tags.sort(_sortbyname)
rpms = server.listBuildRPMs(build['id'])
rpms.sort(_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]
headers = server.getRPMHeaders(srpm['id'], headers=['summary', 'description'])
values['summary'] = koji.fixEncoding(headers.get('summary'))
values['description'] = koji.fixEncoding(headers.get('description'))
values['changelog'] = server.getChangelogEntries(build['id'])
noarch_log_dest = 'noarch'
if build['task_id']:
task = server.getTaskInfo(build['task_id'], request=True)
if 'noarch' in rpmsByArch and \
[a for a in rpmsByArch.keys() if a not in ('noarch', 'src')]:
# This build has noarch and other-arch packages, indicating either
# noarch in extra-arches (kernel) or noarch subpackages.
# Point the log link to the arch of the buildArch task that the first
# noarch package came from. This will be correct in both the
# extra-arches case (noarch) and the subpackage case (one of the other
# arches). If noarch extra-arches and noarch subpackages are mixed in
# same build, this will become incorrect.
noarch_rpm = rpmsByArch['noarch'][0]
if noarch_rpm['buildroot_id']:
noarch_buildroot = server.getBuildroot(noarch_rpm['buildroot_id'])
if noarch_buildroot:
noarch_task = server.getTaskInfo(noarch_buildroot['task_id'], request=True)
if noarch_task:
noarch_log_dest = noarch_task['request'][2]
# 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=['summary', 'description'])
if srpm_headers:
values['summary'] = koji.fixEncoding(srpm_headers['summary'])
values['description'] = koji.fixEncoding(srpm_headers['description'])
changelog = server.getChangelogEntries(taskID=srpm_task['id'], filepath=srpm_path)
if changelog:
values['changelog'] = changelog
else:
task = None
values['build'] = build
values['tags'] = tags
values['rpmsByArch'] = rpmsByArch
values['task'] = task
values['typeinfo'] = typeinfo
values['archiveIndex'] = archiveIndex
values['noarch_log_dest'] = noarch_log_dest
if environ['koji.currentUser']:
values['perms'] = server.getUserPerms(environ['koji.currentUser']['id'])
else:
values['perms'] = []
for field in ['summary', 'description', 'changelog']:
if field not in values:
values[field] = None
values['start_time'] = build.get('start_time') or build['creation_time']
# 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_time'] = task['start_time']
if build['state'] == koji.BUILD_STATES['BUILDING']:
avgDuration = server.getAverageBuildDuration(build['package_id'])
if avgDuration != 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:
if userID.isdigit():
userID = int(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:
if tagID.isdigit():
tagID = int(tagID)
tag = server.getTag(tagID, strict=True)
values['tagID'] = tagID
values['tag'] = tag
package = None
if packageID:
if packageID.isdigit():
packageID = int(packageID)
package = server.getPackage(packageID, strict=True)
values['packageID'] = packageID
values['package'] = package
if state == 'all':
state = None
elif state != 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)
if userID.isdigit():
userID = int(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')
def rpminfo(environ, rpmID, fileOrder='name', fileStart=None, buildrootOrder='-id', buildrootStart=None):
values = _initValues(environ, 'RPM Info', 'builds')
server = _getServer(environ)
rpmID = int(rpmID)
rpm = server.getRPM(rpmID)
values['title'] = '%(name)s-%%s%(version)s-%(release)s.%(arch)s.rpm' % rpm + ' | RPM Info'
epochStr = ''
if rpm['epoch'] != None:
epochStr = '%s:' % rpm['epoch']
values['title'] = values['title'] % epochStr
build = None
if rpm['build_id'] != None:
build = server.getBuild(rpm['build_id'])
builtInRoot = None
if rpm['buildroot_id'] != None:
builtInRoot = server.getBuildroot(rpm['buildroot_id'])
if rpm['external_repo_id'] == 0:
values['provides'] = server.getRPMDeps(rpm['id'], koji.DEP_PROVIDE)
values['provides'].sort(_sortbyname)
values['obsoletes'] = server.getRPMDeps(rpm['id'], koji.DEP_OBSOLETE)
values['obsoletes'].sort(_sortbyname)
values['conflicts'] = server.getRPMDeps(rpm['id'], koji.DEP_CONFLICT)
values['conflicts'].sort(_sortbyname)
values['requires'] = server.getRPMDeps(rpm['id'], koji.DEP_REQUIRE)
values['requires'].sort(_sortbyname)
if koji.RPM_SUPPORTS_OPTIONAL_DEPS:
values['optional_deps'] = True
values['recommends'] = server.getRPMDeps(rpm['id'], koji.DEP_RECOMMEND)
values['recommends'].sort(_sortbyname)
values['suggests'] = server.getRPMDeps(rpm['id'], koji.DEP_SUGGEST)
values['suggests'].sort(_sortbyname)
values['supplements'] = server.getRPMDeps(rpm['id'], koji.DEP_SUPPLEMENT)
values['supplements'].sort(_sortbyname)
values['enhances'] = server.getRPMDeps(rpm['id'], koji.DEP_ENHANCE)
values['enhances'].sort(_sortbyname)
else:
values['optional_deps'] = False
headers = server.getRPMHeaders(rpm['id'], headers=['summary', 'description'])
values['summary'] = koji.fixEncoding(headers.get('summary'))
values['description'] = koji.fixEncoding(headers.get('description'))
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'] != 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
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 == 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:
if hostID.isdigit():
hostID = int(hostID)
host = server.getHost(hostID)
if host == 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 == 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(_sortbyname)
buildroots = server.listBuildroots(hostID=host['id'],
state=[state[1] for state in koji.BR_STATES.items() if state[0] != 'EXPIRED'])
buildroots.sort(kojiweb.util.sortByKeyFunc('-create_event_time'))
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 == 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(_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 == 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(_sortbyname)
values['channel'] = channel
values['hosts'] = hosts
return _genHTML(environ, 'channelinfo.chtml')
def buildrootinfo(environ, buildrootID, builtStart=None, builtOrder=None, componentStart=None, componentOrder=None):
values = _initValues(environ, 'Buildroot Info', 'hosts')
server = _getServer(environ)
buildrootID = int(buildrootID)
buildroot = server.getBuildroot(buildrootID)
if buildroot == 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 != None:
buildrootID = int(buildrootID)
buildroot = server.getBuildroot(buildrootID)
values['buildroot'] = buildroot
if buildroot == 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 != 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, buildrootID, type, start=None, order='filename'):
values = _initValues(environ, 'Archive List', 'hosts')
server = _getServer(environ)
buildrootID = int(buildrootID)
buildroot = server.getBuildroot(buildrootID)
if buildroot == 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('invalid type: %s' % type)
values['buildroot'] = buildroot
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 != None:
targetID = int(targetID)
target = server.getBuildTarget(targetID)
elif name != None:
target = server.getBuildTarget(name)
if target == 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 == 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 == None:
raise koji.GenericError('invalid tag ID: %i' % buildTagID)
destTagID = int(form.getfirst('destTag'))
destTag = server.getTag(destTagID)
if destTag == 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(_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 == 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(_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 == 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)
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 == 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 == 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
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, 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 recentbuilds(environ, user=None, tag=None, package=None):
values = _initValues(environ, 'Recent Build RSS')
server = _getServer(environ)
tagObj = None
if tag != None:
if tag.isdigit():
tag = int(tag)
tagObj = server.getTag(tag)
userObj = None
if user != None:
if user.isdigit():
user = int(user)
userObj = server.getUser(user)
packageObj = None
if package:
if package.isdigit():
package = int(package)
packageObj = server.getPackage(package)
if tagObj != None:
builds = server.listTagged(tagObj['id'], inherit=True, package=(packageObj and packageObj['name'] or None),
owner=(userObj and userObj['name'] or None))
builds.sort(kojiweb.util.sortByKeyFunc('-completion_time', noneGreatest=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'}
_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) + ']+$')
_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):
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 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
return _genHTML(environ, 'search.chtml')
if match == 'regexp':
try:
re.compile(terms)
except:
values['error'] = 'Invalid regular expression'
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, 'searchresults.chtml')
else:
return _genHTML(environ, 'search.chtml')
def watchlogs(environ, taskID):
values = _initValues(environ)
if isinstance(taskID, list):
values['tasks'] = ', '.join(taskID)
else:
values['tasks'] = 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