debian-koji/www/kojiweb/index.py
2014-10-28 23:54:21 -04:00

2241 lines
78 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 Cheetah.Filters
import Cheetah.Template
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']
client_ca = options['ClientCA']
server_ca = options['KojiHubCA']
return session.ssl_login(client_cert, client_ca, 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 StandardError, '_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
else:
_redirect(environ, 'login')
assert False
def _getServer(environ):
opts = environ['koji.options']
session = koji.ClientSession(opts['KojiHubURL'],
opts={'krbservice': opts['KrbService']})
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)
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']
values['tasks'] = server.listTasks(opts=taskOpts, queryOpts={'order': '-id', 'limit': 10})
values['order'] = '-id'
if user:
packages = 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 form.has_key('save'):
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 form.has_key('success_only'):
success_only = True
else:
success_only = False
server.updateNotification(notification['id'], package_id, tag_id, success_only)
_redirect(environ, 'index')
elif form.has_key('cancel'):
_redirect(environ, 'index')
else:
values = _initValues(environ, 'Edit Notification')
values['notif'] = notification
packages = server.listPackages()
packages.sort(kojiweb.util.sortByKeyFunc('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 form.has_key('add'):
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 form.has_key('success_only'):
success_only = True
else:
success_only = False
server.createNotification(user['id'], package_id, tag_id, success_only)
_redirect(environ, 'index')
elif form.has_key('cancel'):
_redirect(environ, 'index')
else:
values = _initValues(environ, 'Edit Notification')
values['notif'] = None
packages = server.listPackages()
packages.sort(kojiweb.util.sortByKeyFunc('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',
'createImage']
# Tasks that can exist without a parent
_TOPLEVEL_TASKS = ['build', 'buildNotification', 'chainbuild', 'maven', 'chainmaven', 'wrapperRPM', 'winbuild', 'newRepo', 'tagBuild', 'tagNotification', 'waitrepo', 'livecd', 'appliance', 'image']
# Tasks that can have children
_PARENT_TASKS = ['build', 'chainbuild', 'maven', 'chainmaven', 'winbuild', 'newRepo', 'wrapperRPM', 'livecd', 'appliance', 'image']
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:
opts['method'] = method
else:
method = 'all'
values['method'] = method
values['alltasks'] = _TASKS
treeEnabled = True
if hostID or (method not in ['all'] + _PARENT_TASKS):
# 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:
# 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
output = server.listTaskOutput(task['id'])
output.sort(_sortByExtAndName)
values['output'] = output
if environ['koji.currentUser']:
values['perms'] = server.getUserPerms(environ['koji.currentUser']['id'])
else:
values['perms'] = []
topurl = environ['koji.options']['KojiFilesURL']
values['pathinfo'] = koji.PathInfo(topdir=topurl)
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)
output = '%i:%s\n' % (task['id'], koji.TASK_STATES[task['state']])
for filename, file_stats in files.items():
output += '%s:%s\n' % (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 filenames, first by extension, and then by name."""
aRoot, aExt = os.path.splitext(a)
bRoot, bExt = os.path.splitext(b)
return cmp(aExt, bExt) or cmp(aRoot, bRoot)
def getfile(environ, taskID, name, offset=None, size=None):
server = _getServer(environ)
taskID = int(taskID)
output = server.listTaskOutput(taskID, stat=True)
file_info = output.get(name)
if not file_info:
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
tags = 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
packages = kojiweb.util.paginateResults(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']
tags = kojiweb.util.paginateMethod(server, values, 'listTags', kw={'package': package['id']},
start=tagStart, dataName='tags', prefix='tag', order=tagOrder)
builds = 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 not tagsByChild.has_key(child_id):
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 form.has_key('add'):
params = {}
name = form['name'].value
params['arches'] = form['arches'].value
params['locked'] = bool(form.has_key('locked'))
permission = form['permission'].value
if permission != 'none':
params['perm'] = int(permission)
if mavenEnabled:
params['maven_support'] = bool(form.has_key('maven_support'))
params['maven_include_all'] = bool(form.has_key('maven_include_all'))
tagID = server.createTag(name, **params)
_redirect(environ, 'taginfo?tagID=%i' % tagID)
elif form.has_key('cancel'):
_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 form.has_key('save'):
params = {}
params['name'] = form['name'].value
params['arches'] = form['arches'].value
params['locked'] = bool(form.has_key('locked'))
permission = form['permission'].value
if permission == 'none':
params['perm'] = None
else:
params['perm'] = int(permission)
if mavenEnabled:
params['maven_support'] = bool(form.has_key('maven_support'))
params['maven_include_all'] = bool(form.has_key('maven_include_all'))
server.editTag2(tag['id'], **params)
_redirect(environ, 'taginfo?tagID=%i' % tag['id'])
elif form.has_key('cancel'):
_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 form.has_key('add') or form.has_key('save'):
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(form.has_key('intransitive'))
newDatum['noconfig'] = bool(form.has_key('noconfig'))
newDatum['pkg_filter'] = form.getfirst('pkg_filter')
data = server.getInheritanceData(tag['id'])
data.append(newDatum)
server.setInheritanceData(tag['id'], data)
elif form.has_key('cancel'):
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)
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)
mavenbuild = server.getMavenBuild(buildID)
winbuild = server.getWinBuild(buildID)
imagebuild = server.getImageBuild(buildID)
if mavenbuild:
archivetype = 'maven'
elif winbuild:
archivetype = 'win'
elif imagebuild:
archivetype = 'image'
else:
archivetype = None
archives = server.listArchives(build['id'], type=archivetype, queryOpts={'order': 'filename'})
archivesByExt = {}
for archive in archives:
archivesByExt.setdefault(os.path.splitext(archive['filename'])[1][1:], []).append(archive)
rpmsByArch = {}
debuginfoByArch = {}
for rpm in rpms:
if koji.is_debuginfo(rpm['name']):
debuginfoByArch.setdefault(rpm['arch'], []).append(rpm)
else:
rpmsByArch.setdefault(rpm['arch'], []).append(rpm)
if rpmsByArch.has_key('src'):
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 rpmsByArch.has_key('noarch') 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['debuginfoByArch'] = debuginfoByArch
values['task'] = task
values['mavenbuild'] = mavenbuild
values['winbuild'] = winbuild
values['imagebuild'] = imagebuild
values['archives'] = archives
values['archivesByExt'] = archivesByExt
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 not values.has_key(field):
values[field] = None
values['start_time'] = build['creation_time']
# the build start time is not accurate for maven and win builds, get it from the
# task start time instead
if mavenbuild or winbuild:
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
topurl = environ['koji.options']['KojiFilesURL']
values['pathinfo'] = koji.PathInfo(topdir=topurl)
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
if type in ('maven', 'win', 'image'):
pass
elif type == 'all':
type = None
else:
type = None
values['type'] = type
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
builds = 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:
builds = 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
users = 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})
packages = kojiweb.util.paginateResults(server, values, 'listPackages', kw={'userID': user['id'], 'with_dups': True},
start=packageStart, dataName='packages', prefix='package', order=packageOrder, pageSize=10)
builds = 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['requires'] = server.getRPMDeps(rpm['id'], koji.DEP_REQUIRE)
values['requires'].sort(_sortbyname)
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)
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
files = 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'])
files = 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 form.has_key('save'):
arches = form['arches'].value
capacity = float(form['capacity'].value)
description = form['description'].value
comment = form['comment'].value
enabled = bool(form.has_key('enabled'))
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 form.has_key('cancel'):
_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)
values['title'] = '%(tag_name)s-%(id)i-%(repo_id)i' % buildroot + ' | Buildroot Info'
if buildroot == None:
raise koji.GenericError, 'unknown buildroot ID: %i' % buildrootID
task = server.getTaskInfo(buildroot['task_id'], request=True)
values['buildroot'] = buildroot
values['task'] = task
return _genHTML(environ, 'buildrootinfo.chtml')
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
rpms = None
if type == 'component':
rpms = kojiweb.util.paginateMethod(server, values, 'listRPMs',
kw={'componentBuildrootID': buildroot['id']},
start=start, dataName='rpms', prefix='rpm', order=order)
elif type == 'built':
rpms = 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':
rpms = 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
archives = None
if type == 'component':
rpms = kojiweb.util.paginateMethod(server, values, 'listArchives', kw={'componentBuildrootID': buildroot['id']},
start=start, dataName='archives', prefix='archive', order=order)
elif type == 'built':
rpms = 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)
targets = 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 form.has_key('save'):
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 form.has_key('cancel'):
_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 form.has_key('add'):
# 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 form.has_key('cancel'):
_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):
server = _getServer(environ)
values = _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 = server.getAllArches()
hostArchList.sort()
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 = server.getAllArches()
hostArchList.sort()
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) + ']+$')
def search(environ, start=None, order='name'):
values = _initValues(environ, 'Search', 'search')
server = _getServer(environ)
values['error'] = None
form = environ['koji.form']
if form.has_key('terms') 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
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