support for split storage

This commit is contained in:
Mike McLean 2011-01-28 16:46:30 -05:00
parent 919716b8be
commit 73d44e199a
12 changed files with 272 additions and 103 deletions

View file

@ -52,7 +52,7 @@ import Cheetah.Template
from ConfigParser import ConfigParser
from fnmatch import fnmatch
from gzip import GzipFile
from optparse import OptionParser
from optparse import OptionParser, SUPPRESS_HELP
from StringIO import StringIO
#imports for LiveCD and Appliance handler
@ -221,20 +221,8 @@ class BuildRoot(object):
repo_id = self.repoid
tag_name = self.tag_name
topurl = None
topdir = None
if hasattr(self.options, 'topurl'):
topurl = self.options.topurl
if hasattr(self.options, 'topdir'):
topdir = self.options.topdir
if topurl:
pi = koji.PathInfo(topdir=topurl)
repourl = pi.repo(repo_id, tag_name) + '/maven2'
elif topdir:
pi = koji.PathInfo(topdir=topdir)
repourl = 'file://' + pi.repo(repo_id, tag_name) + '/maven2'
else:
raise koji.BuildError, 'either topurl or topdir must be specified in the config file'
pi = koji.PathInfo(topdir=self.options.topurl)
repourl = pi.repo(repo_id, tag_name) + '/maven2'
localrepo = localrepodir[len(self.rootdir()):]
settings = """<settings xmlns="http://maven.apache.org/POM/4.0.0"
@ -1745,10 +1733,7 @@ class ImageTask(BaseTaskHandler):
ks.handler.repo.repoList.append(repo_class(baseurl=user_repo, name='koji-override-%i' % index))
index += 1
else:
topurl = getattr(self.options, 'topurl')
if not topurl:
raise koji.LiveCDError, 'topurl must be defined in kojid.conf'
path_info = koji.PathInfo(topdir=topurl)
path_info = koji.PathInfo(topdir=self.options.topurl)
repopath = path_info.repo(repo_info['id'],
target_info['build_tag_name'])
baseurl = '%s/%s' % (repopath, arch)
@ -2426,7 +2411,9 @@ Build Info: %(weburl)s/buildinfo?buildID=%(build_id)i\r
output += "\r\n\r\n"
else:
output = ''
pathinfo = koji.PathInfo(topdir=self.options.topurl)
buildurl = pathinfo.build(build)
# list states here to make them go in the correct order
for task_state in ['failed', 'canceled', 'closed']:
if tasks[task_state]:
@ -2444,12 +2431,12 @@ Build Info: %(weburl)s/buildinfo?buildID=%(build_id)i\r
output += "%s:\r\n" % filetype
for file in task[filetype]:
if filetype == 'rpms':
output += " %s\r\n" % '/'.join([self.options.pkgurl, build['name'], build['version'], build['release'], task['build_arch'], file])
output += " %s\r\n" % '/'.join([buildurl, task['build_arch'], file])
elif filetype == 'logs':
if tasks[task_state] != 'closed':
output += " %s/getfile?taskID=%s&name=%s\r\n" % (weburl, task['id'], file)
else:
output += " %s\r\n" % '/'.join([self.options.pkgurl, build['name'], build['version'], build['release'], 'data', 'logs', task['build_arch'], file])
output += " %s\r\n" % '/'.join([buildurl, 'data', 'logs', task['build_arch'], file])
elif task[filetype] == 'misc':
output += " %s/getfile?taskID=%s&name=%s\r\n" % (weburl, task['id'], file)
output += "\r\n"
@ -2575,7 +2562,7 @@ class CreaterepoTask(BaseTaskHandler):
def create_local_repo(self, rinfo, arch, pkglist, groupdata, oldrepo):
koji.ensuredir(self.outdir)
cmd = ['/usr/bin/createrepo', '-vd', '-o', self.outdir, '-u', self.options.pkgurl]
cmd = ['/usr/bin/createrepo', '-vd', '-o', self.outdir, '-u', self.options.topurl]
if pkglist is not None:
cmd.extend(['-i', pkglist])
if os.path.isfile(groupdata):
@ -2602,8 +2589,7 @@ class CreaterepoTask(BaseTaskHandler):
if pkglist is None:
cmd.append(self.outdir)
else:
pkgdir = os.path.join(self.pathinfo.topdir, 'packages/')
cmd.append(pkgdir)
cmd.append(self.options.topdir)
logfile = '%s/createrepo.log' % self.workdir
status = log_output(self.session, cmd[0], cmd, logfile, self.getUploadDir(), logerror=True)
@ -2713,6 +2699,7 @@ class WaitrepoTask(BaseTaskHandler):
def get_options():
"""process options from command line and config file"""
# parse command line args
logger = logging.getLogger("koji.build")
parser = OptionParser()
parser.add_option("-c", "--config", dest="configFile",
help="use alternate configuration file", metavar="FILE",
@ -2748,7 +2735,7 @@ def get_options():
parser.add_option("--mockdir", help="Specify mockdir")
parser.add_option("--mockuser", help="User to run mock as")
parser.add_option("-s", "--server", help="url of XMLRPC server")
parser.add_option("--pkgurl", help="url of packages directory")
parser.add_option("--pkgurl", help=SUPPRESS_HELP)
(options, args) = parser.parse_args()
if args:
@ -2829,10 +2816,17 @@ def get_options():
koji.ensuredir(options.workdir)
if not options.server:
parser.error("--server argument required")
msg = "the server option is required"
logger.error(msg)
parser.error(msg)
if not options.pkgurl:
parser.error("--pkgurl argument required")
if not options.topurl:
msg = "the topurl option is required"
logger.error(msg)
parser.error(msg)
if options.pkgurl:
logger.warning("The pkgurl option is obsolete")
return options

View file

@ -35,8 +35,8 @@
; The URL for the xmlrpc server
server=http://hub.example.com/kojihub
; The URL for the packages tree
pkgurl=http://hub.example.com/packages
; The URL for the file access
topurl=http://hub.example.com/kojifiles
; A space-separated list of hostname:repository[:use_common] tuples that kojid is authorized to checkout from (no quotes).
; Wildcards (as supported by fnmatch) are allowed.

View file

@ -118,7 +118,8 @@ def get_options():
parser.add_option("-s", "--server", help=_("url of XMLRPC server"))
parser.add_option("--topdir", help=_("specify topdir"))
parser.add_option("--weburl", help=_("url of the Koji web interface"))
parser.add_option("--pkgurl", help=_("url of the Koji package tree"))
parser.add_option("--topurl", help=_("url for Koji file access"))
parser.add_option("--pkgurl", help=optparse.SUPPRESS_HELP)
parser.add_option("--help-commands", action="store_true", default=False, help=_("list commands"))
(options, args) = parser.parse_args()
@ -151,7 +152,8 @@ def get_options():
defaults = {
'server' : 'http://localhost/kojihub',
'weburl' : 'http://localhost/koji',
'pkgurl' : 'http://localhost/packages',
'topurl' : None,
'pkgurl' : None,
'topdir' : '/mnt/koji',
'max_retries' : None,
'retry_interval': None,
@ -205,6 +207,18 @@ def get_options():
koji.BASEDIR = options.topdir
koji.pathinfo.topdir = options.topdir
#pkgurl is obsolete
if options.pkgurl:
if options.topurl:
print "Warning: the pkgurl option is obsolete"
else:
suggest = re.sub(r'/packages/?$', '', options.pkgurl)
if suggest != options.pkgurl:
print "Warning: the pkgurl option is obsolete, using topurl=%r" % suggest
options.topurl = suggest
else:
print "Warning: The pkgurl option is obsolete, please use topurl instead"
return options, cmd, args[1:]
def ensure_connection(session):
@ -5311,7 +5325,7 @@ def anon_handle_download_build(options, session, args):
parser.add_option("--latestfrom", dest="latestfrom", help=_("Download the latest build from this tag"))
parser.add_option("--debuginfo", action="store_true", help=_("Also download -debuginfo rpms"))
parser.add_option("--key", help=_("Download rpms signed with the given key"))
parser.add_option("--topurl", metavar="URL",
parser.add_option("--topurl", metavar="URL", default=options.topurl,
help=_("URL under which Koji files are accessible"))
parser.add_option("-q", "--quiet", action="store_true", help=_("Do not display progress meter"),
default=options.quiet)
@ -5350,6 +5364,12 @@ def anon_handle_download_build(options, session, args):
print "No such build: %s" % build
return 1
if not suboptions.topurl:
print "You must specify --topurl to download files"
return 1
pathinfo = koji.PathInfo(topdir=suboptions.topurl)
build_url = pathinfo.build(info)
urls = []
if suboptions.type:
archives = session.listArchives(buildID=info['id'], type=suboptions.type)
@ -5357,18 +5377,13 @@ def anon_handle_download_build(options, session, args):
print "No %s archives available for %s" % (suboptions.type, koji.buildLabel(info))
return 1
if suboptions.type == 'maven':
if not suboptions.topurl:
print "You must specify --topurl to download Maven archives"
return 1
maven_pi = koji.PathInfo(topdir=suboptions.topurl)
maven_info = session.getMavenBuild(info['id'], strict=True)
for archive in archives:
urls.append((maven_pi.mavenbuild(info, maven_info) + '/' + archive['filename'], archive['filename']))
urls.append((pathinfo.mavenbuild(info, maven_info) + '/' + archive['filename'], archive['filename']))
elif suboptions.type == 'win':
for archive in archives:
url = '%s/%s/%s/%s/win/%s' % (options.pkgurl, info['name'], info['version'], info['release'],
koji.pathinfo.winfile(archive))
urls.append((url, koji.pathinfo.winfile(archive)))
url = '%s/win/%s' % (build_url, pathinfo.winfile(archive))
urls.append((url, pathinfo.winfile(archive)))
else:
# can't happen
assert False
@ -5387,11 +5402,10 @@ def anon_handle_download_build(options, session, args):
if not suboptions.debuginfo and koji.is_debuginfo(rpm['name']):
continue
if suboptions.key:
fname = koji.pathinfo.signed(rpm, suboptions.key)
fname = pathinfo.signed(rpm, suboptions.key)
else:
fname = koji.pathinfo.rpm(rpm)
url = '%s/%s/%s/%s/%s' % (options.pkgurl, info['name'], info['version'], info['release'],
fname)
fname = pathinfo.rpm(rpm)
url = '%s/%s' % (build_url, fname)
urls.append((url, os.path.basename(fname)))
if suboptions.quiet:
@ -5533,7 +5547,7 @@ def anon_handle_search(options, session, args):
usage = _("usage: %prog search [options] search_type pattern")
usage += _('\nAvailable search types: %s') % ', '.join(_search_types)
usage += _("\n(Specify the --help global option for a list of other help options)")
parser = optparse.OptionParser(usage=usage)
parser = OptionParser(usage=usage)
parser.add_option("-r", "--regex", action="store_true", help=_("treat pattern as regex"))
parser.add_option("--exact", action="store_true", help=_("exact matches only"))
main_options = options

View file

@ -0,0 +1,14 @@
BEGIN;
CREATE TABLE volume (
id SERIAL NOT NULL PRIMARY KEY,
name TEXT UNIQUE NOT NULL
) WITHOUT OIDS;
INSERT INTO volume (id, name) VALUES (0, 'DEFAULT');
ALTER TABLE build ADD COLUMN volume_id INTEGER REFERENCES volume (id);
UPDATE build SET volume_id = 0;
ALTER TABLE build ALTER COLUMN volume_id SET NOT NULL;
COMMIT;

View file

@ -261,6 +261,13 @@ CREATE TABLE package (
-- (implicitly created by unique constraint)
CREATE TABLE volume (
id SERIAL NOT NULL PRIMARY KEY,
name TEXT UNIQUE NOT NULL
) WITHOUT OIDS;
INSERT INTO volume (id, name) VALUES (0, 'DEFAULT');
-- here we track the built packages
-- this is at the srpm level, since builds are by srpm
-- see rpminfo for isolated packages
@ -269,6 +276,7 @@ CREATE TABLE package (
-- null, or may point to a deleted task.
CREATE TABLE build (
id SERIAL NOT NULL PRIMARY KEY,
volume_id INTEGER NOT NULL REFERENCES volume (id),
pkg_id INTEGER NOT NULL REFERENCES package (id) DEFERRABLE,
version TEXT NOT NULL,
release TEXT NOT NULL,

View file

@ -3,6 +3,7 @@
#
Alias /kojihub "/usr/share/koji-hub/XMLRPC"
Alias /kojifiles "/mnt/koji/"
<Directory /usr/share/koji-hub>
SetHandler mod_python
@ -15,6 +16,13 @@ Alias /kojihub "/usr/share/koji-hub/XMLRPC"
PythonAutoReload Off
</Directory>
<Directory "/mnt/koji">
Options Indexes
AllowOverride None
Order allow,deny
Allow from all
</Directory>
# uncomment this to enable authentication via SSL client certificates
# <Location /kojihub/ssllogin>
# SSLVerifyClient require

View file

@ -40,6 +40,7 @@ import os
import random
import re
import rpm
import shutil
import stat
import subprocess
import sys
@ -1087,6 +1088,7 @@ def readTaggedBuilds(tag,event=None,inherit=False,latest=False,package=None,owne
('build.epoch', 'epoch'), ('build.state', 'state'), ('build.completion_time', 'completion_time'),
('build.task_id','task_id'),
('events.id', 'creation_event_id'), ('events.time', 'creation_time'),
('volume.id', 'volume_id'), ('volume.name', 'volume_name'),
('package.id', 'package_id'), ('package.name', 'package_name'),
('package.name', 'name'),
("package.name || '-' || build.version || '-' || build.release", 'nvr'),
@ -1115,6 +1117,7 @@ def readTaggedBuilds(tag,event=None,inherit=False,latest=False,package=None,owne
JOIN users ON users.id = build.owner
JOIN events ON events.id = build.create_event
JOIN package ON package.id = build.pkg_id
JOIN volume ON volume.id = build.volume_id
WHERE %s AND tag_id=%%(tagid)s
AND build.state=%%(st_complete)i
""" % (', '.join([pair[0] for pair in fields]), type_join, eventCondition(event, 'tag_listing'))
@ -2080,6 +2083,7 @@ def repo_init(tag, with_src=False, with_debuginfo=False, event=None):
packages = {}
for repoarch in repo_arches:
packages.setdefault(repoarch, [])
relpathinfo = koji.PathInfo(topdir='')
for rpminfo in rpms:
if not with_debuginfo and koji.is_debuginfo(rpminfo['name']):
continue
@ -2094,7 +2098,8 @@ def repo_init(tag, with_src=False, with_debuginfo=False, event=None):
# Do not create a repo for arches not in the arch list for this tag
continue
build = builds[rpminfo['build_id']]
rpminfo['path'] = "%s/%s" % (koji.pathinfo.build(build), koji.pathinfo.rpm(rpminfo))
rpminfo['relpath'] = "%s/%s" % (relpathinfo.build(build), relpathinfo.rpm(rpminfo))
rpminfo['relpath'] = rpminfo['relpath'].lstrip('/')
packages.setdefault(repoarch,[]).append(rpminfo)
#generate comps and groups.spec
groupsdir = "%s/groups" % (repodir)
@ -2107,7 +2112,7 @@ def repo_init(tag, with_src=False, with_debuginfo=False, event=None):
if context.opts.get('EnableMaven') and tinfo['maven_support']:
maven_builds = maven_tag_packages(tinfo, event_id)
#link packages
#generate pkglist files
for arch in packages.iterkeys():
if arch in ['src','noarch']:
continue
@ -2117,16 +2122,16 @@ def repo_init(tag, with_src=False, with_debuginfo=False, event=None):
pkglist = file(os.path.join(repodir, arch, 'pkglist'), 'w')
logger.info("Creating package list for %s" % arch)
for rpminfo in packages[arch]:
pkglist.write(rpminfo['path'].split(os.path.join(koji.pathinfo.topdir, 'packages/'))[1] + '\n')
pkglist.write(rpminfo['relpath'] + '\n')
#noarch packages
for rpminfo in packages.get('noarch',[]):
pkglist.write(rpminfo['path'].split(os.path.join(koji.pathinfo.topdir, 'packages/'))[1] + '\n')
pkglist.write(rpminfo['relpath'] + '\n')
# srpms
if with_src:
srpmdir = "%s/%s" % (repodir,'src')
koji.ensuredir(srpmdir)
for rpminfo in packages.get('src',[]):
pkglist.write(rpminfo['path'].split(os.path.join(koji.pathinfo.topdir, 'packages/'))[1] + '\n')
pkglist.write(rpminfo['relpath'] + '\n')
pkglist.close()
#write list of blocked packages
blocklist = file(os.path.join(repodir, arch, 'blocklist'), 'w')
@ -3066,6 +3071,8 @@ def get_build(buildInfo, strict=False):
task_id: ID of the task that kicked off the build
owner_id: ID of the user who kicked off the build
owner_name: name of the user who kicked off the build
volume_id: ID of the storage volume
volume_name: name of the storage volume
creation_event_id: id of the create_event
creation_time: time the build was created (text)
creation_ts: time the build was created (epoch)
@ -3086,6 +3093,7 @@ def get_build(buildInfo, strict=False):
('build.epoch', 'epoch'), ('build.state', 'state'), ('build.completion_time', 'completion_time'),
('build.task_id', 'task_id'), ('events.id', 'creation_event_id'), ('events.time', 'creation_time'),
('package.id', 'package_id'), ('package.name', 'package_name'), ('package.name', 'name'),
('volume.id', 'volume_id'), ('volume.name', 'volume_name'),
("package.name || '-' || build.version || '-' || build.release", 'nvr'),
('EXTRACT(EPOCH FROM events.time)','creation_ts'),
('EXTRACT(EPOCH FROM build.completion_time)','completion_ts'),
@ -3094,6 +3102,7 @@ def get_build(buildInfo, strict=False):
FROM build
JOIN events ON build.create_event = events.id
JOIN package on build.pkg_id = package.id
JOIN volume on build.volume_id = volume.id
JOIN users on build.owner = users.id
WHERE build.id = %%(buildID)i""" % ', '.join([pair[0] for pair in fields])
@ -3891,6 +3900,77 @@ def new_package(name,strict=True):
c.execute(q,locals())
return pkg_id
def add_volume(name, strict=True):
"""Add a new storage volume in the database"""
context.session.assertPerm('admin')
if strict:
volinfo = lookup_name('volume', name, strict=False)
if volinfo:
raise koji.GenericError, 'volume %s already exists' % name
volinfo = lookup_name('volume', name, strict=False, create=True)
def remove_volume(volume):
"""Remove unused storage volume from the database"""
context.session.assertPerm('admin')
volinfo = lookup_name('volume', volume, strict=True)
query = QueryProcessor(tables=['build'], clauses=['volume_id=%(id)i'],
values=volinfo, columns=['id'], opts={'limit':1})
if query.execute():
raise koji.GenericError, 'volume %(name)s has build references' % volinfo
delete = """DELETE FROM volume WHERE id=%(id)i"""
_dml(delete, volinfo)
def list_volumes():
"""List storage volumes"""
return QueryProcessor(tables=['volume'], columns=['id', 'name']).execute()
def change_build_volume(build, volume, strict=True):
"""Move a build to a different storage volume"""
context.session.assertPerm('admin')
volinfo = lookup_name('volume', volume, strict=True)
binfo = get_build(build, strict=True)
if binfo['volume_id'] == volinfo['id']:
if strict:
raise koji.GenericError, "Build %(nvr)s already on volume %(volume_name)s" % binfo
else:
#nothing to do
return
state = koji.BUILD_STATES[binfo['state']]
if state not in ['COMPLETE', 'DELETED']:
raise koji.GenericError, "Build %s is %s" % (binfo['nvr'], state)
# First copy the build dir(s)
dir_moves = []
old_binfo = binfo.copy()
binfo['volume_id'] = volinfo['id']
binfo['volume_name'] = volinfo['name']
olddir = koji.pathinfo.build(old_binfo)
if os.path.exists(olddir):
newdir = koji.pathinfo.build(binfo)
dir_moves.append([olddir, newdir])
maven_info = get_maven_build(binfo['id'])
if maven_info:
olddir = koji.pathinfo.mavenbuild(old_binfo, maven_info)
if os.path.exists(olddir):
newdir = koji.pathinfo.mavenbuild(binfo, maven_info)
dir_moves.append([olddir, newdir])
for olddir, newdir in dir_moves:
shutil.copytree(olddir, newdir, symlinks=True)
# Second, update the db
koji.plugin.run_callbacks('preBuildStateChange', attribute='volume_id', old=old_binfo['volume_id'], new=volinfo['id'], info=binfo)
update = UpdateProcessor('build', clauses=['id=%(id)i'], values=binfo)
update.set(volume_id=volinfo['id'])
update.execute()
# Third, delete the old content
for olddir, newdir in dir_moves:
koji.util.rmtree(olddir)
koji.plugin.run_callbacks('postBuildStateChange', attribute='volume_id', old=old_binfo['volume_id'], new=volinfo['id'], info=binfo)
def new_build(data):
"""insert a new build entry"""
data = data.copy()
@ -3908,6 +3988,7 @@ def new_build(data):
data.setdefault('completion_time', 'NOW')
data.setdefault('owner',context.session.user_id)
data.setdefault('task_id',None)
data.setdefault('volume_id', 0)
#check for existing build
# TODO - table lock?
q="""SELECT id,state,task_id FROM build
@ -3939,14 +4020,11 @@ def new_build(data):
else:
koji.plugin.run_callbacks('preBuildStateChange', attribute='state', old=None, new=data['state'], info=data)
#insert the new data
data = dslice(data, ['pkg_id', 'version', 'release', 'epoch', 'state', 'volume_id',
'task_id', 'owner', 'completion_time'])
data['id'] = _singleValue("SELECT nextval('build_id_seq')")
q="""
INSERT INTO build (id,pkg_id,version,release,epoch,state,
task_id,owner,completion_time)
VALUES (%(id)i,%(pkg_id)i,%(version)s,%(release)s,%(epoch)s,
%(state)s,%(task_id)s,%(owner)s,%(completion_time)s)
"""
_dml(q, data)
insert = InsertProcessor('build', data=data)
insert.execute()
koji.plugin.run_callbacks('postBuildStateChange', attribute='state', old=None, new=data['state'], info=data)
#return build_id
return data['id']
@ -4037,12 +4115,11 @@ def import_build(srpm, rpms, brmap=None, task_id=None, build_id=None, logs=None)
WHERE id=%(build_id)i"""
_dml(update,locals())
koji.plugin.run_callbacks('postBuildStateChange', attribute='state', old=binfo['state'], new=st_complete, info=binfo)
build['id'] = build_id
# now to handle the individual rpms
for relpath in [srpm] + rpms:
fn = "%s/%s" % (uploadpath,relpath)
rpminfo = import_rpm(fn,build,brmap.get(relpath))
import_rpm_file(fn,build,rpminfo)
rpminfo = import_rpm(fn, binfo, brmap.get(relpath))
import_rpm_file(fn, binfo, rpminfo)
add_rpm_sig(rpminfo['id'], koji.rip_rpm_sighdr(fn))
if logs:
for key, files in logs.iteritems():
@ -4050,10 +4127,10 @@ def import_build(srpm, rpms, brmap=None, task_id=None, build_id=None, logs=None)
key = None
for relpath in files:
fn = "%s/%s" % (uploadpath,relpath)
import_build_log(fn, build, subdir=key)
import_build_log(fn, binfo, subdir=key)
koji.plugin.run_callbacks('postImport', type='build', srpm=srpm, rpms=rpms, brmap=brmap,
task_id=task_id, build_id=build_id, build=binfo, logs=logs)
return build
return binfo
def import_rpm(fn,buildinfo=None,brootid=None,wrapper=False):
"""Import a single rpm into the database
@ -4079,14 +4156,11 @@ def import_rpm(fn,buildinfo=None,brootid=None,wrapper=False):
if buildinfo is None:
#figure it out for ourselves
if rpminfo['sourcepackage'] == 1:
buildinfo = rpminfo.copy()
build_id = find_build_id(buildinfo)
if build_id:
# build already exists
buildinfo['id'] = build_id
else:
buildinfo = get_build(rpminfo, strict=False)
if not buildinfo:
# create a new build
buildinfo['id'] = new_build(rpminfo)
build_id = new_build(rpminfo)
buildinfo = get_build(build_id, strict=True)
else:
#figure it out from sourcerpm string
buildinfo = get_build(koji.parse_NVRA(rpminfo['sourcerpm']))
@ -4113,29 +4187,27 @@ def import_rpm(fn,buildinfo=None,brootid=None,wrapper=False):
#add rpminfo entry
rpminfo['id'] = _singleValue("""SELECT nextval('rpminfo_id_seq')""")
rpminfo['build'] = buildinfo
rpminfo['build_id'] = buildinfo['id']
rpminfo['size'] = os.path.getsize(fn)
rpminfo['payloadhash'] = koji.hex_string(hdr[rpm.RPMTAG_SIGMD5])
rpminfo['brootid'] = brootid
rpminfo['buildroot_id'] = brootid
rpminfo['external_repo_id'] = 0
koji.plugin.run_callbacks('preImport', type='rpm', rpm=rpminfo, build=buildinfo,
filepath=fn)
q = """INSERT INTO rpminfo (id,name,version,release,epoch,
build_id,arch,buildtime,buildroot_id,
external_repo_id,
size,payloadhash)
VALUES (%(id)i,%(name)s,%(version)s,%(release)s,%(epoch)s,
%(build_id)s,%(arch)s,%(buildtime)s,%(brootid)s,
0,
%(size)s,%(payloadhash)s)
"""
_dml(q, rpminfo)
data = rpminfo.copy()
del data['sourcepackage']
del data['sourcerpm']
insert = InsertProcessor('rpminfo', data=data)
insert.execute()
koji.plugin.run_callbacks('postImport', type='rpm', rpm=rpminfo, build=buildinfo,
filepath=fn)
#extra fields for return
rpminfo['build'] = buildinfo
rpminfo['brootid'] = brootid
return rpminfo
def add_external_rpm(rpminfo, external_repo, strict=True):
@ -6897,6 +6969,11 @@ class RootExports(object):
def buildReferences(self, build, limit=None):
return build_references(get_build(build, strict=True)['id'], limit)
addVolume = staticmethod(add_volume)
removeVolume = staticmethod(remove_volume)
listVolumes = staticmethod(list_volumes)
changeBuildVolume = staticmethod(change_build_volume)
def createEmptyBuild(self, name, version, release, epoch, owner=None):
context.session.assertPerm('admin')
data = { 'name' : name, 'version' : version, 'release' : release,
@ -7364,6 +7441,7 @@ class RootExports(object):
return readTaggedArchives(tag, event=event, inherit=inherit, latest=latest, package=package, type=type)
def listBuilds(self, packageID=None, userID=None, taskID=None, prefix=None, state=None,
volumeID=None,
createdBefore=None, createdAfter=None,
completeBefore=None, completeAfter=None, type=None, typeInfo=None, queryOpts=None):
"""List package builds.
@ -7371,7 +7449,8 @@ class RootExports(object):
If userID is specified, restrict the results to builds owned by the given user.
If taskID is specfied, restrict the results to builds with the given task ID. If taskID is -1,
restrict the results to builds with a non-null taskID.
One or more of packageID, userID, and taskID may be specified.
If volumeID is specified, restrict the results to builds stored on that volume
One or more of packageID, userID, volumeID, and taskID may be specified.
If prefix is specified, restrict the results to builds whose package name starts with that
prefix.
If createdBefore and/or createdAfter are specified, restrict the results to builds whose
@ -7400,6 +7479,8 @@ class RootExports(object):
- nvr (synthesized for sorting purposes)
- owner_id
- owner_name
- volume_id
- volume_name
- creation_event_id
- creation_time
- creation_ts
@ -7421,18 +7502,22 @@ class RootExports(object):
('EXTRACT(EPOCH FROM events.time)','creation_ts'),
('EXTRACT(EPOCH FROM build.completion_time)','completion_ts'),
('package.id', 'package_id'), ('package.name', 'package_name'), ('package.name', 'name'),
('volume.id', 'volume_id'), ('volume.name', 'volume_name'),
("package.name || '-' || build.version || '-' || build.release", 'nvr'),
('users.id', 'owner_id'), ('users.name', 'owner_name')]
tables = ['build']
joins = ['events ON build.create_event = events.id',
'package ON build.pkg_id = package.id',
'volume ON build.volume_id = volume.id',
'users ON build.owner = users.id']
clauses = []
if packageID != None:
clauses.append('package.id = %(packageID)i')
if userID != None:
clauses.append('users.id = %(userID)i')
if volumeID != None:
clauses.append('volume.id = %(packageID)i')
if taskID != None:
if taskID == -1:
clauses.append('build.task_id IS NOT NULL')

View file

@ -1396,9 +1396,15 @@ class PathInfo(object):
topdir = property(topdir, _set_topdir)
def volumedir(self, volume):
if volume == 'DEFAULT' or volume is None:
return self.topdir
#else
return self.topdir + ("/vol/%s" % volume)
def build(self,build):
"""Return the directory where a build belongs"""
return self.topdir + ("/packages/%(name)s/%(version)s/%(release)s" % build)
return self.volumedir(build.get('volume_name')) + ("/packages/%(name)s/%(version)s/%(release)s" % build)
def mavenbuild(self, build, maveninfo):
"""Return the directory where the Maven build exists in the global store (/mnt/koji/maven2)"""
@ -1406,7 +1412,7 @@ class PathInfo(object):
artifact_id = maveninfo['artifact_id']
version = maveninfo['version']
release = build['release']
return self.topdir + ("/maven2/%(group_path)s/%(artifact_id)s/%(version)s/%(release)s" % locals())
return self.volumedir(build.get('volume_name')) + ("/maven2/%(group_path)s/%(artifact_id)s/%(version)s/%(release)s" % locals())
def winbuild(self, build):
"""Return the directory where the Windows build exists"""

View file

@ -19,6 +19,8 @@ import re
import time
import koji
import os
import os.path
import stat
try:
from hashlib import md5 as md5_constructor
@ -110,6 +112,46 @@ def dslice(dict, keys, strict=True):
ret[key] = dict[key]
return ret
def rmtree(path):
"""Delete a directory tree without crossing fs boundaries"""
st = os.lstat(path)
if not stat.S_ISDIR(st.st_mode):
raise koji.GenericError, "Not a directory: %s" % path
dev = st.st_dev
dirlist = []
for dirpath, dirnames, filenames in os.walk(path):
dirlist.append(dirpath)
newdirs = []
dirsyms = []
for fn in dirnames:
path = os.path.join(dirpath, fn)
st = os.lstat(path)
if st.st_dev != dev:
# don't cross fs boundary
continue
if stat.S_ISLNK(st.st_mode):
#os.walk includes symlinks to dirs here
dirsyms.append(fn)
continue
newdirs.append(fn)
#only walk our filtered dirs
dirnames[:] = newdirs
for fn in filenames + dirsyms:
path = os.path.join(dirpath, fn)
st = os.lstat(path)
if st.st_dev != dev:
#shouldn't happen, but just to be safe...
continue
os.unlink(path)
dirlist.reverse()
for dirpath in dirlist:
if os.listdir(dirpath):
# dir not empty. could happen if a mount was present
continue
os.rmdir(dirpath)
def eventFromOpts(session, opts):
"""Determine event id from standard cli options

View file

@ -11,9 +11,7 @@ Alias /koji "/usr/share/koji-web/scripts/"
PythonDebug On
PythonOption SiteName Koji
PythonOption KojiHubURL http://hub.example.com/kojihub
PythonOption KojiPackagesURL http://server.example.com/mnt/koji/packages
PythonOption KojiMavenURL http://server.example.com/mnt/koji/maven2
PythonOption KojiImagesURL http://server.example.com/mnt/koji/images
PythonOption KojiFilesURL http://server.example.com/mnt/koji
PythonOption WebPrincipal koji/web@EXAMPLE.COM
PythonOption WebKeytab /etc/httpd.keytab
PythonOption WebCCache /var/tmp/kojiweb.ccache

View file

@ -3,12 +3,10 @@
#from kojiweb import util
#include "includes/header.chtml"
#set $nvrpath = '%(name)s/%(version)s/%(release)s' % $build
#set $nvrpath = $pathinfo.build($build)
#set $archiveurl = ''
#if $mavenbuild
#set $archiveurl = '%s/%s/%s/%s/%s' % ($mavenBase, $mavenbuild['group_id'].replace('.', '/'),
$mavenbuild['artifact_id'], $mavenbuild['version'],
$build['release'])
#set $archiveurl = $pathinfo.mavenbuild($build,$mavenbuild)
#end if
<h4>Information for build <a href="buildinfo?buildID=$build.id">$koji.buildLabel($build)</a></h4>
@ -108,9 +106,10 @@
<tr><th>src</th><th></th></tr>
#for $rpm in $rpmsByArch['src']
#set $rpmfile = '%(name)s-%(version)s-%(release)s.%(arch)s.rpm' % $rpm
#set $rpmpath = $pathinfo.rpm($rpm)
<tr>
<td></td>
<td>$rpmfile (<a href="rpminfo?rpmID=$rpm.id">info</a>) (<a href="$downloadBase/$nvrpath/$rpm.arch/$rpmfile">download</a>)</td>
<td>$rpmfile (<a href="rpminfo?rpmID=$rpm.id">info</a>) (<a href="$nvrpath/$rpmpath">download</a>)</td>
</tr>
#end for
#end if
@ -125,9 +124,9 @@
<td>
#if $task
#if $arch == 'noarch'
(<a href="$downloadBase/$nvrpath/data/logs/$noarch_log_dest/">build logs</a>)
(<a href="$nvrpath/data/logs/$noarch_log_dest/">build logs</a>)
#else
(<a href="$downloadBase/$nvrpath/data/logs/$arch/">build logs</a>)
(<a href="$nvrpath/data/logs/$arch/">build logs</a>)
#end if
#end if
</td>
@ -135,9 +134,10 @@
#for $rpm in $rpmsByArch[$arch] + $debuginfoByArch.get($arch, [])
<tr>
#set $rpmfile = '%(name)s-%(version)s-%(release)s.%(arch)s.rpm' % $rpm
#set $rpmpath = $pathinfo.rpm($rpm)
<td></td>
<td>
$rpmfile (<a href="rpminfo?rpmID=$rpm.id">info</a>) (<a href="$downloadBase/$nvrpath/$rpm.arch/$rpmfile">download</a>)
$rpmfile (<a href="rpminfo?rpmID=$rpm.id">info</a>) (<a href="$nvrpath/$rpmpath">download</a>)
</td>
</tr>
#end for
@ -160,9 +160,9 @@
<td>
#if $ext == $exts[0]
#if $mavenbuild
(<a href="$downloadBase/$nvrpath/data/logs/maven2/">build logs</a>)
(<a href="$nvrpath/data/logs/maven2/">build logs</a>)
#elif $winbuild
(<a href="$downloadBase/$nvrpath/data/logs/win/">build logs</a>)
(<a href="$nvrpath/data/logs/win/">build logs</a>)
#end if
#end if
</td>
@ -174,7 +174,7 @@
#if $mavenbuild
$archive.filename (<a href="archiveinfo?archiveID=$archive.id">info</a>) (<a href="$archiveurl/$archive.filename">download</a>)
#elif $winbuild
$koji.pathinfo.winfile($archive) (<a href="archiveinfo?archiveID=$archive.id">info</a>) (<a href="$downloadBase/$nvrpath/win/$koji.pathinfo.winfile($archive)">download</a>)
$pathinfo.winfile($archive) (<a href="archiveinfo?archiveID=$archive.id">info</a>) (<a href="$pathinfo.winbuild($build)/$pathinfo.winfile($archive)">download</a>)
#end if
</td>
</tr>

View file

@ -612,7 +612,7 @@ def imageinfo(req, imageID):
"""Do some prep work and generate the imageinfo page for kojiweb."""
server = _getServer(req)
values = _initValues(req, 'Image Information')
imageURL = req.get_options().get('KojiImagesURL', 'http://localhost/images')
imageURL = req.get_options().get('KojiFilesURL', 'http://localhost') + '/images'
imageID = int(imageID)
image = server.getImageInfo(imageID=imageID, strict=True)
values['image'] = image
@ -1137,8 +1137,8 @@ def buildinfo(req, buildID):
else:
values['estCompletion'] = None
values['downloadBase'] = req.get_options().get('KojiPackagesURL', 'http://localhost/packages')
values['mavenBase'] = req.get_options().get('KojiMavenURL', 'http://localhost/maven2')
topurl = req.get_options().get('KojiFilesURL', 'http://localhost/')
values['pathinfo'] = koji.PathInfo(topdir=topurl)
return _genHTML(req, 'buildinfo.chtml')