missing hyphen
This commit is contained in:
commit
722923c484
13 changed files with 895 additions and 57 deletions
324
builder/kojid
324
builder/kojid
|
|
@ -60,6 +60,10 @@ from gzip import GzipFile
|
|||
from optparse import OptionParser
|
||||
from StringIO import StringIO
|
||||
from xmlrpclib import Fault
|
||||
import pykickstart.parser as ksparser
|
||||
import pykickstart.handlers.control as kscontrol
|
||||
import hashlib
|
||||
import iso9660 # from pycdio
|
||||
|
||||
# our private modules
|
||||
sys.path.insert(0, '/usr/share/koji-builder/lib')
|
||||
|
|
@ -312,7 +316,7 @@ class BuildRoot(object):
|
|||
self.name = "%(tag_name)s-%(id)s-%(repoid)s" % vars(self)
|
||||
self.config = session.getBuildConfig(self.tag_id, event=self.event_id)
|
||||
|
||||
def _new(self, tag, arch, task_id, distribution=None, repo_id=None, install_group='build', setup_dns=False, maven_opts=None):
|
||||
def _new(self, tag, arch, task_id, repo_id=None, install_group='build', setup_dns=False, bind_opts=None, maven_opts=None):
|
||||
"""Create a brand new repo"""
|
||||
if not repo_id:
|
||||
raise koji.BuildrootError, "A repo id must be provided"
|
||||
|
|
@ -346,6 +350,7 @@ class BuildRoot(object):
|
|||
self.install_group = install_group
|
||||
self.setup_dns = setup_dns
|
||||
self.maven_opts = maven_opts
|
||||
self.bind_opts = bind_opts
|
||||
self._writeMockConfig()
|
||||
|
||||
def _writeMockConfig(self):
|
||||
|
|
@ -366,6 +371,7 @@ class BuildRoot(object):
|
|||
opts['use_host_resolv'] = self.setup_dns
|
||||
opts['install_group'] = self.install_group
|
||||
opts['maven_opts'] = self.maven_opts
|
||||
opts['bind_opts'] = self.bind_opts
|
||||
output = koji.genMockConfig(self.name, self.br_arch, managed=True, **opts)
|
||||
|
||||
#write config
|
||||
|
|
@ -1592,6 +1598,73 @@ class BaseTaskHandler(object):
|
|||
for filename in files:
|
||||
self.uploadFile(os.path.join(path, filename), relpath)
|
||||
|
||||
def genImageManifest(self, image, manifile):
|
||||
"""
|
||||
Using iso9660 from pycdio, get the file manifest of the given image,
|
||||
and save it to the text file manifile.
|
||||
"""
|
||||
fd = open(manifile, 'w')
|
||||
if not fd:
|
||||
raise koji.GenericError, \
|
||||
'Unable to open manifest file (%s) for writing!' % manifile
|
||||
iso = iso9660.ISO9660.IFS(source=image)
|
||||
if not iso.is_open():
|
||||
raise koji.GenericError, \
|
||||
'Could not open %s as an ISO-9660 image!' % image
|
||||
|
||||
# image metadata
|
||||
id = iso.get_application_id()
|
||||
if id is not None: fd.write("Application ID: %s\n" % id)
|
||||
id = iso.get_preparer_id()
|
||||
if id is not None: fd.write("Preparer ID: %s\n" % id)
|
||||
id = iso.get_publisher_id()
|
||||
if id is not None: fd.write("Publisher ID: %s\n" % id)
|
||||
id = iso.get_system_id()
|
||||
if id is not None: fd.write("System ID: %s\n" % id)
|
||||
id = iso.get_volume_id()
|
||||
if id is not None: fd.write("Volume ID: %s\n" % id)
|
||||
id = iso.get_volumeset_id()
|
||||
if id is not None: fd.write("Volumeset ID: %s\n" % id)
|
||||
|
||||
fd.write('\nSize(bytes) File Name\n')
|
||||
manifest = self.listImageDir(iso, '/')
|
||||
for a_file in manifest:
|
||||
fd.write(a_file)
|
||||
fd.close()
|
||||
iso.close()
|
||||
|
||||
def listImageDir(self, iso, path):
|
||||
"""
|
||||
Helper function called recursively by getImageManifest. Returns a
|
||||
listing of files/directories at the given path in an iso image obj.
|
||||
"""
|
||||
manifest = []
|
||||
file_stats = iso.readdir(path)
|
||||
for stat in file_stats:
|
||||
filename = stat[0]
|
||||
size = stat[2]
|
||||
is_dir = stat[4] == 2
|
||||
|
||||
if filename == '..':
|
||||
continue
|
||||
elif filename == '.':
|
||||
# path should always end in a trailing /
|
||||
filepath = path
|
||||
else:
|
||||
filepath = path + filename
|
||||
# identify directories with a trailing /
|
||||
if is_dir:
|
||||
filepath += '/'
|
||||
|
||||
if is_dir and filename != '.':
|
||||
# recurse into subdirectories
|
||||
manifest.extend(self.listImageDir(iso, filepath))
|
||||
else:
|
||||
# output information for the current directory and files
|
||||
manifest.append("%-10d %s\n" % (size, filepath))
|
||||
|
||||
return manifest
|
||||
|
||||
def localPath(self, relpath):
|
||||
"""Return a local path to a remote file.
|
||||
|
||||
|
|
@ -2722,7 +2795,254 @@ class TagBuildTask(BaseTaskHandler):
|
|||
exctype, value = sys.exc_info()[:2]
|
||||
session.host.tagNotification(False, tag_id, fromtag, build_id, user_id, ignore_success, "%s: %s" % (exctype, value))
|
||||
raise e
|
||||
|
||||
|
||||
# LiveCDTask begins with a mock chroot, and then installs livecd-tools into it
|
||||
# via the livecd-build group. livecd-creator is then executed in the chroot
|
||||
# to create the LiveCD image.
|
||||
#
|
||||
class LiveCDTask(BaseTaskHandler):
|
||||
|
||||
Methods = ['createLiveCD']
|
||||
_taskWeight = 1.5
|
||||
|
||||
def handler(self, arch, target, ksfile, opts=None):
|
||||
if not opts:
|
||||
opts = {}
|
||||
|
||||
global options
|
||||
target_info = session.getBuildTarget(target, strict=True)
|
||||
build_tag = target_info['build_tag']
|
||||
repo_info = session.getRepo(build_tag)
|
||||
|
||||
# Here we configure mock to bind mount a set of /dev directories
|
||||
# so livecd-creator can use them.
|
||||
# We use mknod to create the loopN device files later, but if we
|
||||
# wanted to have mock do that work, our bind_opts variable would have
|
||||
# this additional dictionary:
|
||||
# { 'files' : {
|
||||
# '/dev/loop0' : '/dev/loop0',
|
||||
# '/dev/loop1' : '/dev/loop1',
|
||||
# '/dev/loop2' : '/dev/loop2',
|
||||
# '/dev/loop3' : '/dev/loop3',
|
||||
# '/dev/loop4' : '/dev/loop4',
|
||||
# '/dev/loop5' : '/dev/loop5',
|
||||
# '/dev/loop6' : '/dev/loop6',
|
||||
# '/dev/loop7' : '/dev/loop7'}}
|
||||
bind_opts = {'dirs' : {
|
||||
'/dev/pts' : '/dev/pts',
|
||||
'/dev/mapper' : '/dev/mapper',
|
||||
'/selinux' : '/selinux'}
|
||||
}
|
||||
rootopts = {'install_group': 'livecd-build',
|
||||
'setup_dns': True,
|
||||
'repo_id': repo_info['id'],
|
||||
'bind_opts' : bind_opts}
|
||||
|
||||
broot = BuildRoot(build_tag, arch, self.id, **rootopts)
|
||||
|
||||
# create the mock chroot
|
||||
self.logger.debug("Initializing buildroot")
|
||||
broot.init()
|
||||
|
||||
# Note that if the KS file existed locally, then "ksfile" is a relative
|
||||
# path to it in the /mnt/koji/work directory. If not, then it is still
|
||||
# the parameter the user passed in initially, and we assume it is a
|
||||
# relative path in a remote scm. The user should have passed in an scm
|
||||
# url with --ksurl.
|
||||
tmpchroot = '/tmp'
|
||||
scmdir = os.path.join(broot.rootdir(), tmpchroot[1:])
|
||||
koji.ensuredir(scmdir)
|
||||
self.logger.debug("ksfile = %s" % ksfile)
|
||||
if opts.get('ksurl'):
|
||||
scm = SCM(opts['ksurl'])
|
||||
scm.assert_allowed(options.allowed_scms)
|
||||
logfile = os.path.join(self.workdir, 'checkout.log')
|
||||
scmsrcdir = scm.checkout(scmdir, self.getUploadDir(), logfile)
|
||||
kspath = os.path.join(scmsrcdir, ksfile)
|
||||
else:
|
||||
kspath = self.localPath("work/%s" % ksfile)
|
||||
|
||||
# XXX: If the ks file came from a local path and has %include
|
||||
# macros, livecd-creator will fail because the included
|
||||
# kickstarts were not copied into the chroot. For now we
|
||||
# require users to flatten their kickstart file if submitting
|
||||
# the task with a local path.
|
||||
#
|
||||
# Note that if an SCM URL was used instead, %include macros
|
||||
# may not be a problem if the included kickstarts are present
|
||||
# in the repository we checked out.
|
||||
|
||||
# Now we do some kickstart manipulation. If the user passed in a repo
|
||||
# url with --repo, then we substitute that in for the repo(s) specified
|
||||
# in the kickstart file. If --repo wasn't specified, then we use the
|
||||
# repo associated with the target passed in initially.
|
||||
self.uploadFile(kspath) # upload the original ks file
|
||||
if opts.get('ksversion'):
|
||||
version = ksparser.makeVersion(ksparser.stringToVersion(opts['ksversion']))
|
||||
else:
|
||||
version = ksparser.makeVersion()
|
||||
ks = ksparser.KickstartParser(version)
|
||||
repo_class = kscontrol.dataMap[ks.version]['RepoData']
|
||||
|
||||
try:
|
||||
ks.readKickstart(kspath)
|
||||
except IOError, (err, msg):
|
||||
raise koji.LiveCDError("Failed to read kickstart file "
|
||||
"'%s' : %s" % (ksfile, msg))
|
||||
except kserrors.KickstartError, e:
|
||||
raise koji.LiveCDError("Failed to parse kickstart file "
|
||||
"'%s' : %s" % (ksfile, e))
|
||||
|
||||
ks.handler.repo.repoList = [] # delete whatever the ks file told us
|
||||
if opts.get('repo'):
|
||||
user_repos = opts['repo'].split(',')
|
||||
index = 0
|
||||
for user_repo in user_repos:
|
||||
ks.handler.repo.repoList.append(repo_class(baseurl=user_repo,
|
||||
name='koji-override-%i' % index))
|
||||
index += 1
|
||||
else:
|
||||
topurl = getattr(options, 'topurl')
|
||||
if not topurl:
|
||||
raise koji.LiveCDError, 'topurl must be defined in kojid.conf'
|
||||
path_info = koji.PathInfo(topdir=topurl)
|
||||
repopath = path_info.repo(repo_info['id'],
|
||||
target_info['build_tag_name'])
|
||||
baseurl = '%s/%s' % (repopath, arch)
|
||||
self.logger.debug('BASEURL: %s' % baseurl)
|
||||
ks.handler.repo.repoList.append(repo_class(baseurl=baseurl,
|
||||
name='koji-%s-%i' % (target_info['build_tag_name'],
|
||||
repo_info['id'])))
|
||||
|
||||
# Write out the new ks file. Note that things may not be in the same
|
||||
# order and comments in the original ks file may be lost.
|
||||
kskoji = os.path.join(tmpchroot, 'koji-livecd-%s-%i.ks' % (target_info['build_tag_name'], self.id))
|
||||
kspath = os.path.join(broot.rootdir(), kskoji[1:])
|
||||
outfile = open(kspath, 'w')
|
||||
outfile.write(str(ks.handler))
|
||||
outfile.close()
|
||||
|
||||
# put the new ksfile in the output directory
|
||||
if not os.path.exists(kspath):
|
||||
raise koji.LiveCDError, "KS file missing: %s" % kspath
|
||||
self.uploadFile(kspath)
|
||||
|
||||
cachedir = os.path.join(tmpchroot, 'koji-livecd') # arbitrary
|
||||
livecd_log = os.path.join(tmpchroot, 'livecd.log') # path in chroot
|
||||
|
||||
# Create the loopback devices we need
|
||||
mknod_cmd = 'for i in $(seq 0 7); do mknod /dev/loop$i b 7 $i; done'
|
||||
rv = broot.mock(['--chroot', mknod_cmd])
|
||||
if rv:
|
||||
broot.expire()
|
||||
raise koji.LiveCDError, \
|
||||
"Could not create loopback device files: %s" % _parseStatus(rv, '"%s"' % mknod_cmd)
|
||||
|
||||
# Run livecd-creator
|
||||
livecd_cmd = ['/usr/bin/livecd-creator', '-c', kskoji, '-d', '-v', '--logfile', livecd_log,
|
||||
'--cache', cachedir]
|
||||
|
||||
# run the livecd-creator command
|
||||
rv = broot.mock(['--cwd', '/tmp', '--chroot', '--'] + livecd_cmd)
|
||||
self.uploadFile(os.path.join(broot.rootdir(), livecd_log[1:]))
|
||||
broot.expire()
|
||||
if rv:
|
||||
raise koji.LiveCDError, \
|
||||
"Could not create LiveCD: %s" % _parseStatus(rv, 'livecd-creator') + \
|
||||
"; see root.log or livecd.log for more information"
|
||||
|
||||
# Find the resultant iso
|
||||
# The cwd of the livecd-creator process is /tmp in the chroot, so that's
|
||||
# where it writes the .iso
|
||||
files = os.listdir(os.path.join(broot.rootdir(), 'tmp'))
|
||||
isofile = None
|
||||
for afile in files:
|
||||
if afile.endswith('.iso'):
|
||||
if not isofile:
|
||||
isofile = afile
|
||||
else:
|
||||
raise koji.LiveCDError, 'multiple .iso files found: %s and %s' % (isofile, afile)
|
||||
if not isofile:
|
||||
raise koji.LiveCDError, 'could not find iso file in chroot'
|
||||
|
||||
isosrc = os.path.join(broot.rootdir(), 'tmp', isofile)
|
||||
try:
|
||||
filesize = os.path.getsize(isosrc)
|
||||
except OSError:
|
||||
self.logger.error('Could not find the ISO. Did livecd-creator ' +
|
||||
'complete without errors?')
|
||||
raise koji.LiveCDError, 'missing image file: %s' % isosrc
|
||||
|
||||
# if filesize is greater than a 32-bit signed integer's range, the
|
||||
# python XMLRPC module will break.
|
||||
filesize = str(filesize)
|
||||
|
||||
# copy the iso out of the chroot. If we were given an isoname, this is
|
||||
# where the renaming happens.
|
||||
self.logger.debug('uploading image: %s' % isosrc)
|
||||
if opts.get('isoname'):
|
||||
isofile = opts['isoname']
|
||||
if not isofile.endswith('.iso'):
|
||||
isofile += '.iso'
|
||||
self.uploadFile(isosrc, remoteName=isofile)
|
||||
|
||||
# Generate the file manifest of the image
|
||||
manifest = os.path.join(broot.resultdir(), 'manifest.log')
|
||||
self.genImageManifest(isosrc, manifest)
|
||||
self.uploadFile(manifest)
|
||||
|
||||
if not opts.get('scratch'):
|
||||
# Read the rpm header information from the yum cache livecd-creator
|
||||
# used. We assume it was empty to start.
|
||||
#
|
||||
# LiveCD creator would have thrown an error if no repo was
|
||||
# specified, so we assume the ones we parse are ok.
|
||||
repos = os.listdir(os.path.join(broot.rootdir(), cachedir[1:]))
|
||||
if len(repos) == 0:
|
||||
raise koji.LiveCDError, 'No repos found in yum cache!'
|
||||
|
||||
hdrlist = []
|
||||
fields = ['name', 'version', 'release', 'epoch', 'arch', \
|
||||
'buildtime', 'sigmd5']
|
||||
for repo in repos:
|
||||
pkgpath = os.path.join(broot.rootdir(), cachedir[1:], repo,
|
||||
'packages')
|
||||
pkgs = os.listdir(pkgpath)
|
||||
for pkg in pkgs:
|
||||
pkgfile = os.path.join(pkgpath, pkg)
|
||||
hdr = koji.get_header_fields(pkgfile, fields)
|
||||
hdr['size'] = os.path.getsize(pkgfile)
|
||||
hdr['payloadhash'] = koji.hex_string(hdr['sigmd5'])
|
||||
del hdr['sigmd5']
|
||||
hdrlist.append(hdr)
|
||||
|
||||
# get a unique hash of the image file
|
||||
sha256sum = hashlib.sha256()
|
||||
image_fo = file(isosrc, 'r')
|
||||
while True:
|
||||
data = image_fo.read(1048576)
|
||||
sha256sum.update(data)
|
||||
if not len(data):
|
||||
break
|
||||
|
||||
hash = sha256sum.hexdigest()
|
||||
self.logger.debug('digest: %s' % hash)
|
||||
|
||||
# Import info about the image into the database, unless this is a
|
||||
# scratch image.
|
||||
broot.markExternalRPMs(hdrlist)
|
||||
image_id = session.host.importImage(self.id, isofile, filesize, arch,
|
||||
'LiveCD ISO', hash, hdrlist)
|
||||
|
||||
if opts.get('scratch'):
|
||||
return 'Scratch image created: %s' % \
|
||||
os.path.join(koji.pathinfo.work(),
|
||||
koji.pathinfo.taskrelpath(self.id), isofile)
|
||||
else:
|
||||
return 'Created image: %s' % \
|
||||
os.path.join(koji.pathinfo.imageFinalPath(),
|
||||
koji.pathinfo.livecdRelPath(image_id), isofile)
|
||||
|
||||
class BuildSRPMFromSCMTask(BaseTaskHandler):
|
||||
|
||||
Methods = ['buildSRPMFromSCM']
|
||||
|
|
|
|||
92
cli/koji
92
cli/koji
|
|
@ -4028,6 +4028,98 @@ def handle_remove_external_repo(options, session, args):
|
|||
continue
|
||||
session.removeExternalRepoFromTag(tag, repo)
|
||||
|
||||
# This handler is for spinning livecd images
|
||||
#
|
||||
def handle_spin_livecd(options, session, args):
|
||||
"""[admin] Create a live CD image given a kickstart file"""
|
||||
|
||||
# Usage & option parsing.
|
||||
usage = _("usage: %prog spin-livecd [options] <target> <arch> " +
|
||||
"<kickstart-file>")
|
||||
usage += _("\n(Specify the --help global option for a list of other " +
|
||||
"help options)")
|
||||
parser = OptionParser(usage=usage)
|
||||
parser.add_option("--nowait", action="store_true",
|
||||
help=_("Don't wait on livecd creation"))
|
||||
parser.add_option("--noprogress", action="store_true",
|
||||
help=_("Do not display progress of the upload"))
|
||||
parser.add_option("--background", action="store_true",
|
||||
help=_("Run the livecd creation task at a lower priority"))
|
||||
parser.add_option("--isoname",
|
||||
help=_("Use a custom name for the iso file"))
|
||||
parser.add_option("--ksurl", metavar="SCMURL",
|
||||
help=_("The URL to the SCM containing the kickstart file"))
|
||||
parser.add_option("--ksversion", metavar="VERSION",
|
||||
help=_("The syntax version used in the kickstart file"))
|
||||
parser.add_option("--scratch", action="store_true",
|
||||
help=_("Create a scratch LiveCD image."))
|
||||
parser.add_option("--repo",
|
||||
help=_("Specify a comma-separated list of repos that will override\n" +
|
||||
"the repo used to install RPMs in the LiveCD image. The \n" +
|
||||
"repo associated with the target is the default."))
|
||||
(task_options, args) = parser.parse_args(args)
|
||||
|
||||
# Make sure the target and kickstart is specified.
|
||||
if len(args) != 3:
|
||||
parser.error(_("Three arguments are required: an architecture, " +
|
||||
"a build target, and a relative \npath to a " +
|
||||
"kickstart file ."))
|
||||
assert False
|
||||
|
||||
activate_session(session)
|
||||
|
||||
# Set the task's priority. Users can only lower it with --background.
|
||||
priority = None
|
||||
if task_options.background:
|
||||
# relative to koji.PRIO_DEFAULT; higher means a "lower" priority.
|
||||
priority = 5
|
||||
if _running_in_bg() or task_options.noprogress:
|
||||
callback = None
|
||||
else:
|
||||
callback = _progress_callback
|
||||
|
||||
# We do some early sanity checking of the given target.
|
||||
# Kojid gets these values again later on, but we check now as a convenience
|
||||
# for the user.
|
||||
target = args[0]
|
||||
tmp_target = session.getBuildTarget(target)
|
||||
if not tmp_target:
|
||||
parser.error(_("Unknown build target: %s" % target))
|
||||
dest_tag = session.getTag(tmp_target['dest_tag'])
|
||||
if not dest_tag:
|
||||
parser.error(_("Unknown destination tag: %s" %
|
||||
tmp_target['dest_tag_name']))
|
||||
|
||||
# Set the architecture
|
||||
arch = koji.canonArch(args[1])
|
||||
|
||||
# Upload the KS file to the staging area.
|
||||
# If it's a URL, it's kojid's job to go get it when it does the checkout.
|
||||
ksfile = args[2]
|
||||
|
||||
if not task_options.ksurl:
|
||||
serverdir = _unique_path('cli-livecd')
|
||||
session.uploadWrapper(ksfile, serverdir, callback=callback)
|
||||
ksfile = os.path.join(serverdir, os.path.basename(ksfile))
|
||||
print
|
||||
|
||||
livecd_opts = {}
|
||||
for opt in ['scratch', 'ksurl', 'ksversion', 'isoname', 'repo']:
|
||||
if getattr(task_options, opt):
|
||||
livecd_opts[opt] = getattr(task_options, opt)
|
||||
|
||||
# finally, create the task. Flow continues in kojihub::livecd.
|
||||
task_id = session.livecd(arch, target, ksfile, opts=livecd_opts,
|
||||
priority=priority)
|
||||
|
||||
print "Created task:", task_id
|
||||
print "Task info: %s/taskinfo?taskID=%s" % (options.weburl, task_id)
|
||||
if _running_in_bg() or task_options.nowait:
|
||||
return
|
||||
else:
|
||||
session.logout()
|
||||
return watch_tasks(session,[task_id])
|
||||
|
||||
def handle_free_task(options, session, args):
|
||||
"[admin] Free a task"
|
||||
usage = _("usage: %prog free-task [options] <task-id> [<task-id> ...]")
|
||||
|
|
|
|||
|
|
@ -6,8 +6,10 @@ DROP TABLE build_notifications;
|
|||
DROP TABLE log_messages;
|
||||
|
||||
DROP TABLE buildroot_listing;
|
||||
DROP TABLE imageinfo_listing;
|
||||
|
||||
DROP TABLE rpminfo;
|
||||
DROP TABLE imageinfo;
|
||||
|
||||
DROP TABLE group_package_listing;
|
||||
DROP TABLE group_req_listing;
|
||||
|
|
@ -97,6 +99,7 @@ CREATE TABLE permissions (
|
|||
INSERT INTO permissions (name) VALUES ('admin');
|
||||
INSERT INTO permissions (name) VALUES ('build');
|
||||
INSERT INTO permissions (name) VALUES ('repo');
|
||||
INSERT INTO permissions (name) VALUES ('livecd');
|
||||
|
||||
CREATE TABLE user_perms (
|
||||
user_id INTEGER NOT NULL REFERENCES users(id),
|
||||
|
|
@ -168,6 +171,7 @@ CREATE TABLE channels (
|
|||
INSERT INTO channels (name) VALUES ('default');
|
||||
INSERT INTO channels (name) VALUES ('createrepo');
|
||||
INSERT INTO channels (name) VALUES ('maven');
|
||||
INSERT INTO channels (name) VALUES ('livecd');
|
||||
|
||||
-- Here we track the build machines
|
||||
-- each host has an entry in the users table also
|
||||
|
|
@ -424,6 +428,18 @@ CREATE TABLE buildroot (
|
|||
dirtyness INTEGER
|
||||
) WITHOUT OIDS;
|
||||
|
||||
-- track spun images (livecds, installation, VMs...)
|
||||
CREATE TABLE imageinfo (
|
||||
id SERIAL NOT NULL PRIMARY KEY,
|
||||
task_id INTEGER NOT NULL REFERENCES task(id),
|
||||
filename TEXT NOT NULL,
|
||||
filesize BIGINT NOT NULL,
|
||||
arch VARCHAR(16) NOT NULL,
|
||||
hash TEXT NOT NULL,
|
||||
mediatype TEXT NOT NULL
|
||||
) WITHOUT OIDS;
|
||||
CREATE INDEX imageinfo_task_id on imageinfo(task_id);
|
||||
|
||||
-- this table associates tags with builds. an entry here tags a package
|
||||
CREATE TABLE tag_listing (
|
||||
build_id INTEGER NOT NULL REFERENCES build (id),
|
||||
|
|
@ -574,6 +590,14 @@ CREATE TABLE buildroot_listing (
|
|||
) WITHOUT OIDS;
|
||||
CREATE INDEX buildroot_listing_rpms ON buildroot_listing(rpm_id);
|
||||
|
||||
-- tracks the contents of an image
|
||||
CREATE TABLE imageinfo_listing (
|
||||
image_id INTEGER NOT NULL REFERENCES imageinfo(id),
|
||||
rpm_id INTEGER NOT NULL REFERENCES rpminfo(id),
|
||||
UNIQUE (image_id, rpm_id)
|
||||
) WITHOUT OIDS;
|
||||
CREATE INDEX imageinfo_listing_rpms on imageinfo_listing(rpm_id);
|
||||
|
||||
CREATE TABLE log_messages (
|
||||
id SERIAL NOT NULL PRIMARY KEY,
|
||||
message TEXT NOT NULL,
|
||||
|
|
|
|||
229
hub/kojihub.py
229
hub/kojihub.py
|
|
@ -3522,7 +3522,7 @@ def query_buildroots(hostID=None, tagID=None, state=None, rpmID=None, archiveID=
|
|||
else:
|
||||
clauses.append('buildroot.state = %(state)i')
|
||||
if rpmID != None:
|
||||
joins.append('buildroot_listing ON buildroot.id = buildroot_listing.buildroot_id')
|
||||
joins.insert(0, 'buildroot_listing ON buildroot.id = buildroot_listing.buildroot_id')
|
||||
fields.append(('buildroot_listing.is_update', 'is_update'))
|
||||
clauses.append('buildroot_listing.rpm_id = %(rpmID)i')
|
||||
if archiveID != None:
|
||||
|
|
@ -5434,6 +5434,85 @@ def rpmdiff(basepath, rpmlist):
|
|||
raise koji.BuildError, 'mismatch when analyzing %s, rpmdiff output was:\n%s' % \
|
||||
(os.path.basename(first_rpm), output)
|
||||
|
||||
def importImageInternal(task_id, filename, filesize, arch, mediatype, hash, rpmlist):
|
||||
"""
|
||||
Import image info and the listing into the database, and move an image
|
||||
to the final resting place. The filesize may be reported as a string if it
|
||||
exceeds the 32-bit signed integer limit. This function will convert it if
|
||||
need be. Not called for scratch images.
|
||||
"""
|
||||
|
||||
#sanity checks
|
||||
host = Host()
|
||||
host.verify()
|
||||
task = Task(task_id)
|
||||
task.assertHost(host.id)
|
||||
|
||||
imageinfo = {}
|
||||
imageinfo['id'] = _singleValue("""SELECT nextval('imageinfo_id_seq')""")
|
||||
imageinfo['taskid'] = task_id
|
||||
imageinfo['filename'] = filename
|
||||
imageinfo['filesize'] = int(filesize)
|
||||
imageinfo['arch'] = arch
|
||||
imageinfo['mediatype'] = mediatype
|
||||
imageinfo['hash'] = hash
|
||||
q = """INSERT INTO imageinfo (id,task_id,filename,filesize,
|
||||
arch,mediatype,hash)
|
||||
VALUES (%(id)i,%(taskid)i,%(filename)s,%(filesize)i,
|
||||
%(arch)s,%(mediatype)s,%(hash)s)
|
||||
"""
|
||||
_dml(q, imageinfo)
|
||||
|
||||
q = """INSERT INTO imageinfo_listing (image_id,rpm_id)
|
||||
VALUES (%(image_id)i,%(rpm_id)i)"""
|
||||
|
||||
rpm_ids = []
|
||||
for an_rpm in rpmlist:
|
||||
location = an_rpm.get('location')
|
||||
if location:
|
||||
data = add_external_rpm(an_rpm, location, strict=False)
|
||||
else:
|
||||
data = get_rpm(an_rpm, strict=True)
|
||||
rpm_ids.append(data['id'])
|
||||
|
||||
image_id = imageinfo['id']
|
||||
for rpm_id in rpm_ids:
|
||||
_dml(q, locals())
|
||||
|
||||
return image_id
|
||||
|
||||
def moveImageResults(task_id, image_id, arch):
|
||||
"""
|
||||
Move the image file from the work/task directory into its more
|
||||
permanent resting place. This shouldn't be called for scratch images.
|
||||
"""
|
||||
source_path = os.path.join(koji.pathinfo.work(),
|
||||
koji.pathinfo.taskrelpath(task_id))
|
||||
final_path = os.path.join(koji.pathinfo.imageFinalPath(),
|
||||
koji.pathinfo.livecdRelPath(image_id))
|
||||
log_path = os.path.join(final_path, 'data', 'logs', arch)
|
||||
if os.path.exists(final_path) or os.path.exists(log_path):
|
||||
raise koji.GenericError, "Error moving LiveCD image: the final " + \
|
||||
"destination already exists!"
|
||||
koji.ensuredir(final_path)
|
||||
koji.ensuredir(log_path)
|
||||
|
||||
src_files = os.listdir(source_path)
|
||||
got_iso = False
|
||||
for fname in src_files:
|
||||
if fname.endswith('.iso'):
|
||||
got_iso = True
|
||||
dest_path = final_path
|
||||
else:
|
||||
dest_path = log_path
|
||||
os.rename(os.path.join(source_path, fname),
|
||||
os.path.join(dest_path, fname))
|
||||
os.symlink(os.path.join(dest_path, fname),
|
||||
os.path.join(source_path, fname))
|
||||
|
||||
if not got_iso:
|
||||
raise koji.GenericError, "Could not move the iso to the final destination!"
|
||||
|
||||
#
|
||||
# XMLRPC Methods
|
||||
#
|
||||
|
|
@ -5551,6 +5630,67 @@ class RootExports(object):
|
|||
|
||||
return make_task('wrapperRPM', [url, build_tag, build, None, {'repo_id': repo_info['id']}], **taskOpts)
|
||||
|
||||
# Create the livecd task. Called from handle_spin_livecd in the client.
|
||||
#
|
||||
def livecd (self, arch, target, ksfile, opts=None, priority=None):
|
||||
"""
|
||||
Create a live CD image using a kickstart file and group package list.
|
||||
"""
|
||||
|
||||
context.session.assertPerm('livecd')
|
||||
|
||||
taskOpts = {'channel': 'livecd'}
|
||||
taskOpts['arch'] = arch
|
||||
if priority:
|
||||
if priority < 0:
|
||||
if not context.session.hasPerm('admin'):
|
||||
raise koji.ActionNotAllowed, \
|
||||
'only admins may create high-priority tasks'
|
||||
|
||||
taskOpts['priority'] = koji.PRIO_DEFAULT + priority
|
||||
|
||||
return make_task('createLiveCD', [arch, target, ksfile, opts],
|
||||
**taskOpts)
|
||||
|
||||
# Database access to get imageinfo values. Used in parts of kojiweb.
|
||||
#
|
||||
def getImageInfo(self, imageID=None, taskID=None, strict=False):
|
||||
"""
|
||||
Return the row from imageinfo given an image_id OR build_root_id.
|
||||
It is an error if neither are specified, and image_id takes precedence.
|
||||
Filesize will be reported as a string if it exceeds the 32-bit signed
|
||||
integer limit.
|
||||
"""
|
||||
tables = ['imageinfo']
|
||||
fields = ['imageinfo.id', 'filename', 'filesize', 'imageinfo.arch', 'mediatype',
|
||||
'imageinfo.task_id', 'buildroot.id', 'hash']
|
||||
aliases = ['id', 'filename', 'filesize', 'arch', 'mediatype', 'task_id',
|
||||
'br_id', 'hash']
|
||||
joins = ['buildroot ON imageinfo.task_id = buildroot.task_id']
|
||||
if imageID:
|
||||
clauses = ['imageinfo.id = %(imageID)i']
|
||||
elif taskID:
|
||||
clauses = ['imageinfo.task_id = %(taskID)i']
|
||||
else:
|
||||
raise koji.GenericError, 'either imageID or taskID must be specified'
|
||||
|
||||
query = QueryProcessor(columns=fields, tables=tables, clauses=clauses,
|
||||
values=locals(), joins=joins, aliases=aliases)
|
||||
ret = query.executeOne()
|
||||
|
||||
if strict and not ret:
|
||||
if imageID:
|
||||
raise koji.GenericError, 'no image with ID: %i' % imageID
|
||||
else:
|
||||
raise koji.GenericError, 'no image for task ID: %i' % taskID
|
||||
|
||||
# additional tweaking
|
||||
if ret:
|
||||
# Always return filesize as a string instead of an int so XMLRPC doesn't
|
||||
# complain about 32-bit overflow
|
||||
ret['filesize'] = str(ret['filesize'])
|
||||
return ret
|
||||
|
||||
def hello(self,*args):
|
||||
return "Hello World"
|
||||
|
||||
|
|
@ -5777,7 +5917,11 @@ class RootExports(object):
|
|||
stat_map = {}
|
||||
for attr in dir(stat_info):
|
||||
if attr.startswith('st_'):
|
||||
stat_map[attr] = getattr(stat_info, attr)
|
||||
if attr == 'st_size':
|
||||
stat_map[attr] = str(getattr(stat_info, attr))
|
||||
else:
|
||||
stat_map[attr] = getattr(stat_info, attr)
|
||||
|
||||
result[relfilename] = stat_map
|
||||
else:
|
||||
result.append(relfilename)
|
||||
|
|
@ -6474,7 +6618,79 @@ class RootExports(object):
|
|||
mapping[int(key)] = mapping[key]
|
||||
return readFullInheritance(tag,event,reverse,stops,jumps)
|
||||
|
||||
listRPMs = staticmethod(list_rpms)
|
||||
def listRPMs(self, buildID=None, buildrootID=None, imageID=None, componentBuildrootID=None, hostID=None, arches=None, queryOpts=None):
|
||||
"""List RPMS. If buildID, imageID and/or buildrootID are specified,
|
||||
restrict the list of RPMs to only those RPMs that are part of that
|
||||
build, or were built in that buildroot. If componentBuildrootID is specified,
|
||||
restrict the list to only those RPMs that will get pulled into that buildroot
|
||||
when it is used to build another package. A list of maps is returned, each map
|
||||
containing the following keys:
|
||||
|
||||
- id
|
||||
- name
|
||||
- version
|
||||
- release
|
||||
- nvr (synthesized for sorting purposes)
|
||||
- arch
|
||||
- epoch
|
||||
- payloadhash
|
||||
- size
|
||||
- buildtime
|
||||
- build_id
|
||||
- buildroot_id
|
||||
- external_repo_id
|
||||
- external_repo_name
|
||||
|
||||
If componentBuildrootID is specified, two additional keys will be included:
|
||||
- component_buildroot_id
|
||||
- is_update
|
||||
|
||||
If no build has the given ID, or the build generated no RPMs,
|
||||
an empty list is returned."""
|
||||
fields = [('rpminfo.id', 'id'), ('rpminfo.name', 'name'), ('rpminfo.version', 'version'),
|
||||
('rpminfo.release', 'release'),
|
||||
("rpminfo.name || '-' || rpminfo.version || '-' || rpminfo.release", 'nvr'),
|
||||
('rpminfo.arch', 'arch'),
|
||||
('rpminfo.epoch', 'epoch'), ('rpminfo.payloadhash', 'payloadhash'),
|
||||
('rpminfo.size', 'size'), ('rpminfo.buildtime', 'buildtime'),
|
||||
('rpminfo.build_id', 'build_id'), ('rpminfo.buildroot_id', 'buildroot_id'),
|
||||
('rpminfo.external_repo_id', 'external_repo_id'),
|
||||
('external_repo.name', 'external_repo_name'),
|
||||
]
|
||||
joins = ['external_repo ON rpminfo.external_repo_id = external_repo.id']
|
||||
clauses = []
|
||||
|
||||
if buildID != None:
|
||||
clauses.append('rpminfo.build_id = %(buildID)i')
|
||||
if buildrootID != None:
|
||||
clauses.append('rpminfo.buildroot_id = %(buildrootID)i')
|
||||
if componentBuildrootID != None:
|
||||
fields.append(('buildroot_listing.buildroot_id as component_buildroot_id',
|
||||
'component_buildroot_id'))
|
||||
fields.append(('buildroot_listing.is_update', 'is_update'))
|
||||
joins.append('buildroot_listing ON rpminfo.id = buildroot_listing.rpm_id')
|
||||
clauses.append('buildroot_listing.buildroot_id = %(componentBuildrootID)i')
|
||||
|
||||
# image specific constraints
|
||||
if imageID != None:
|
||||
clauses.append('imageinfo_listing.image_id = %(imageID)i')
|
||||
joins.append('imageinfo_listing ON rpminfo.id = imageinfo_listing.rpm_id')
|
||||
|
||||
if hostID != None:
|
||||
joins.append('buildroot ON rpminfo.buildroot_id = buildroot.id')
|
||||
clauses.append('buildroot.host_id = %(hostID)i')
|
||||
if arches != None:
|
||||
if isinstance(arches, list) or isinstance(arches, tuple):
|
||||
clauses.append('rpminfo.arch IN %(arches)s')
|
||||
elif isinstance(arches, str):
|
||||
clauses.append('rpminfo.arch = %(arches)s')
|
||||
else:
|
||||
raise koji.GenericError, 'invalid type for "arches" parameter: %s' % type(arches)
|
||||
|
||||
query = QueryProcessor(columns=[f[0] for f in fields], aliases=[f[1] for f in fields],
|
||||
tables=['rpminfo'], joins=joins, clauses=clauses,
|
||||
values=locals(), opts=queryOpts)
|
||||
return query.execute()
|
||||
|
||||
def listBuildRPMs(self,build):
|
||||
"""Get information about all the RPMs generated by the build with the given
|
||||
|
|
@ -8226,6 +8442,13 @@ class HostExports(object):
|
|||
_untag_build(fromtag,build,user_id=user_id,force=force,strict=True)
|
||||
_tag_build(tag,build,user_id=user_id,force=force)
|
||||
|
||||
# Called from kojid::LiveCDTask
|
||||
def importImage(self, task_id, filename, filesize, arch, mediatype, hash, rpmlist):
|
||||
image_id = importImageInternal(task_id, filename, filesize, arch, mediatype,
|
||||
hash, rpmlist)
|
||||
moveImageResults(task_id, image_id, arch)
|
||||
return image_id
|
||||
|
||||
def tagNotification(self, is_successful, tag_id, from_id, build_id, user_id, ignore_success=False, failure_msg=''):
|
||||
"""Create a tag notification message.
|
||||
Handles creation of tagNotification tasks for hosts."""
|
||||
|
|
|
|||
|
|
@ -59,6 +59,8 @@ Requires: /usr/bin/git
|
|||
Requires: rpm-build
|
||||
Requires: redhat-rpm-config
|
||||
Requires: python-cheetah
|
||||
Requires: pykickstart
|
||||
Requires: pycdio
|
||||
%if 0%{?rhel} >= 5
|
||||
Requires: createrepo >= 0.4.11-2
|
||||
%endif
|
||||
|
|
|
|||
|
|
@ -281,6 +281,10 @@ class ServerOffline(GenericError):
|
|||
"""Raised when the server is offline"""
|
||||
faultCode = 1014
|
||||
|
||||
class LiveCDError(GenericError):
|
||||
"""Raised when LiveCD Image creation fails"""
|
||||
faultCode = 1015
|
||||
|
||||
class MultiCallInProgress(object):
|
||||
"""
|
||||
Placeholder class to be returned by method calls when in the process of
|
||||
|
|
@ -1211,6 +1215,10 @@ def genMockConfig(name, arch, managed=False, repoid=None, tag_name=None, **opts)
|
|||
'rpmbuild_timeout': 86400
|
||||
}
|
||||
|
||||
# bind_opts are used to mount parts (or all of) /dev if needed.
|
||||
# See kojid::LiveCDTask for a look at this option in action.
|
||||
bind_opts = opts.get('bind_opts')
|
||||
|
||||
files = {}
|
||||
if opts.get('use_host_resolv', False) and os.path.exists('/etc/hosts'):
|
||||
# if we're setting up DNS,
|
||||
|
|
@ -1273,6 +1281,15 @@ baseurl=%(url)s
|
|||
for key, value in plugin_conf.iteritems():
|
||||
parts.append("config_opts['plugin_conf'][%r] = %r\n" % (key, value))
|
||||
parts.append("\n")
|
||||
|
||||
if bind_opts:
|
||||
# This line is REQUIRED for mock to work if bind_opts defined.
|
||||
parts.append("config_opts['internal_dev_setup'] = False\n")
|
||||
for key in bind_opts.keys():
|
||||
for mnt_src, mnt_dest in bind_opts.get(key).iteritems():
|
||||
parts.append("config_opts['plugin_conf']['bind_mount_opts'][%r].append((%r, %r))\n" % (key, mnt_src, mnt_dest))
|
||||
parts.append("\n")
|
||||
|
||||
for key, value in macros.iteritems():
|
||||
parts.append("config_opts['macros'][%r] = %r\n" % (key, value))
|
||||
parts.append("\n")
|
||||
|
|
@ -1399,6 +1416,14 @@ class PathInfo(object):
|
|||
"""Return the relative path for the task work directory"""
|
||||
return "tasks/%s/%s" % (task_id % 10000, task_id)
|
||||
|
||||
def livecdRelPath(self, image_id):
|
||||
"""Return the relative path for the livecd image directory"""
|
||||
return os.path.join('livecd', str(image_id % 10000), str(image_id))
|
||||
|
||||
def imageFinalPath(self):
|
||||
"""Return the absolute path to where completed images can be found"""
|
||||
return os.path.join(self.topdir, 'images')
|
||||
|
||||
def work(self):
|
||||
"""Return the work dir"""
|
||||
return self.topdir + '/work'
|
||||
|
|
@ -1981,6 +2006,10 @@ def taskLabel(taskInfo):
|
|||
nvrs = taskInfo['request'][2]
|
||||
if isinstance(nvrs, list):
|
||||
extra += ', ' + ', '.join(nvrs)
|
||||
elif method == 'createLiveCD':
|
||||
if taskInfo.has_key('request'):
|
||||
arch, target, ksfile = taskInfo['request'][:3]
|
||||
extra = '%s, %s, %s' % (target, arch, os.path.basename(ksfile))
|
||||
|
||||
if extra:
|
||||
return '%s (%s)' % (method, extra)
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ Alias /koji "/usr/share/koji-web/scripts/"
|
|||
PythonOption KojiPackagesURL http://server.example.com/mnt/koji/packages
|
||||
PythonOption KojiMavenURL http://server.example.com/mnt/koji/maven2
|
||||
PythonOption KojiArchiveURL http://server.example.com/mnt/koji/archives
|
||||
PythonOption KojiImagesURL http://server.example.com/mnt/koji/images
|
||||
PythonOption WebPrincipal koji/web@EXAMPLE.COM
|
||||
PythonOption WebKeytab /etc/httpd.keytab
|
||||
PythonOption WebCCache /var/tmp/kojiweb.ccache
|
||||
|
|
|
|||
55
www/kojiweb/imageinfo.chtml
Normal file
55
www/kojiweb/imageinfo.chtml
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
#import koji
|
||||
#import koji.util
|
||||
#from os.path import basename
|
||||
#from kojiweb import util
|
||||
|
||||
#include "includes/header.chtml"
|
||||
|
||||
<h4>Information for image <a href="imageinfo?imageID=$image.id">$image.filename</a></h4>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>ID</th><td>$image.id</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>File Name</th><td>$image.filename</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>File Size</th><td>$image.filesize</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Arch</th><td>$image.arch</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Media Type</th><td>$image.mediatype</td>
|
||||
</tr>
|
||||
<tr>
|
||||
#if $len($image.hash) == 32
|
||||
<th>Digest (md5)</th><td>$image.hash</td>
|
||||
#elif $len($image.hash) == 40
|
||||
<th>Digest (sha1)</th><td>$image.hash</td>
|
||||
#elif $len($image.hash) == 64
|
||||
<th>Digest (sha256)</th><td>$image.hash</td>
|
||||
#elif $len($image.hash) == 96
|
||||
<th>Digest (sha384)</th><td>$image.hash</td>
|
||||
#elif $len($image.hash) == 128
|
||||
<th>Digest (sha512)</th><td>$image.hash</td>
|
||||
#else
|
||||
<th>Hash </th><td>$image.hash</td>
|
||||
#end if
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Task</th><td><a href="taskinfo?taskID=$task.id" class="task$util.taskState($task.state)">$koji.taskLabel($task)</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Buildroot</th><td><a href="buildrootinfo?buildrootID=$buildroot.id">/var/lib/mock/$buildroot.tag_name-$buildroot.id-$buildroot.repo_id</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th colspan="2"><a href="rpmlist?imageID=$image.id&type=image" title="RPMs that where installed into the LiveCD">Included RPMs</a></th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th colspan="2"><a href="$imageBase/$image.filename">Download Image</a> (<a href="$imageBase/data/logs/$image.arch/">build logs</a>)</th>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
#include "includes/footer.chtml"
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
#encoding utf-8
|
||||
#import koji
|
||||
#import random
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
|
||||
|
|
@ -5,25 +6,25 @@
|
|||
|
||||
#def greeting()
|
||||
#set $greetings = ('hello', 'hi', 'yo', "what's up", "g'day", 'back to work',
|
||||
'bonjour',
|
||||
'hallo',
|
||||
'ciao',
|
||||
'hola',
|
||||
'olá',
|
||||
'dobrý den',
|
||||
'zdravstvuite',
|
||||
'góðan daginn',
|
||||
'hej',
|
||||
'grüezi',
|
||||
'céad míle fáilte',
|
||||
'hylô',
|
||||
'bună ziua',
|
||||
'jó napot',
|
||||
'dobre dan',
|
||||
'你好',
|
||||
'こんにちは',
|
||||
'नमस्कार',
|
||||
'안녕하세요')
|
||||
'bonjour',
|
||||
'hallo',
|
||||
'ciao',
|
||||
'hola',
|
||||
u'olá',
|
||||
u'dobrý den',
|
||||
u'zdravstvuite',
|
||||
u'góðan daginn',
|
||||
'hej',
|
||||
u'grüezi',
|
||||
u'céad míle fáilte',
|
||||
u'hylô',
|
||||
u'bună ziua',
|
||||
u'jó napot',
|
||||
'dobre dan',
|
||||
u'你好',
|
||||
u'こんにちは',
|
||||
u'नमस्कार',
|
||||
u'안녕하세요')
|
||||
#echo $random.choice($greetings)##slurp
|
||||
#end def
|
||||
|
||||
|
|
|
|||
|
|
@ -362,9 +362,10 @@ _TASKS = ['build',
|
|||
'createrepo',
|
||||
'buildNotification',
|
||||
'tagNotification',
|
||||
'dependantTask']
|
||||
'dependantTask',
|
||||
'createLiveCD']
|
||||
# Tasks that can exist without a parent
|
||||
_TOPLEVEL_TASKS = ['build', 'buildNotification', 'chainbuild', 'newRepo', 'tagBuild', 'tagNotification', 'waitrepo']
|
||||
_TOPLEVEL_TASKS = ['build', 'buildNotification', 'chainbuild', 'newRepo', 'tagBuild', 'tagNotification', 'waitrepo', 'createLiveCD']
|
||||
# Tasks that can have children
|
||||
_PARENT_TASKS = ['build', 'chainbuild', 'newRepo']
|
||||
|
||||
|
|
@ -512,6 +513,8 @@ def taskinfo(req, taskID):
|
|||
elif task['method'] == 'buildMaven':
|
||||
buildTag = params[1]
|
||||
values['buildTag'] = buildTag
|
||||
elif task['method'] == 'createLiveCD':
|
||||
values['image'] = server.getImageInfo(taskID=taskID)
|
||||
elif task['method'] == 'buildSRPMFromSCM':
|
||||
if len(params) > 1:
|
||||
buildTag = server.getTag(params[1])
|
||||
|
|
@ -573,6 +576,20 @@ def taskinfo(req, taskID):
|
|||
|
||||
return _genHTML(req, 'taskinfo.chtml')
|
||||
|
||||
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')
|
||||
imageID = int(imageID)
|
||||
image = server.getImageInfo(imageID=imageID, strict=True)
|
||||
values['image'] = image
|
||||
values['title'] = image['filename'] + ' | Image Information'
|
||||
values['buildroot'] = server.getBuildroot(image['br_id'], strict=True)
|
||||
values['task'] = server.getTaskInfo(image['task_id'], request=True)
|
||||
values['imageBase'] = imageURL + '/' + koji.pathinfo.livecdRelPath(image['id'])
|
||||
return _genHTML(req, 'imageinfo.chtml')
|
||||
|
||||
def taskstatus(req, taskID):
|
||||
server = _getServer(req)
|
||||
|
||||
|
|
@ -583,7 +600,7 @@ def taskstatus(req, taskID):
|
|||
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:%i\n' % (filename, file_stats['st_size'])
|
||||
output += '%s:%s\n' % (filename, file_stats['st_size'])
|
||||
|
||||
return output
|
||||
|
||||
|
|
@ -623,8 +640,11 @@ def getfile(req, taskID, name, offset=None, size=None):
|
|||
req.headers_out['Content-Disposition'] = 'attachment; filename=%s' % name
|
||||
elif name.endswith('.log'):
|
||||
req.content_type = 'text/plain'
|
||||
elif name.endswith('.iso'):
|
||||
req.content_type = 'application/octet-stream'
|
||||
req.headers_out['Content-Disposition'] = 'attachment; filename=%s' % name
|
||||
|
||||
file_size = file_info['st_size']
|
||||
file_size = int(file_info['st_size'])
|
||||
if offset is None:
|
||||
offset = 0
|
||||
else:
|
||||
|
|
@ -1431,26 +1451,51 @@ def buildrootinfo(req, buildrootID, builtStart=None, builtOrder=None, componentS
|
|||
|
||||
return _genHTML(req, 'buildrootinfo.chtml')
|
||||
|
||||
def rpmlist(req, buildrootID, type, start=None, order='nvr'):
|
||||
def rpmlist(req, 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(req, 'RPM List', 'hosts')
|
||||
server = _getServer(req)
|
||||
|
||||
buildrootID = int(buildrootID)
|
||||
buildroot = server.getBuildroot(buildrootID)
|
||||
if buildroot == None:
|
||||
raise koji.GenericError, 'unknown buildroot ID: %i' % buildrootID
|
||||
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)
|
||||
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.getImageInfo(imageID=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['buildroot'] = buildroot
|
||||
values['type'] = type
|
||||
|
||||
values['order'] = order
|
||||
|
||||
return _genHTML(req, 'rpmlist.chtml')
|
||||
|
|
|
|||
|
|
@ -2,19 +2,39 @@
|
|||
|
||||
#include "includes/header.chtml"
|
||||
|
||||
#def getID()
|
||||
#if $type == 'image'
|
||||
imageID=$image.id #slurp
|
||||
#else
|
||||
buildrootID=$buildroot.id #slurp
|
||||
#end if
|
||||
#end def
|
||||
|
||||
#def getColspan()
|
||||
#if $type == 'component'
|
||||
"colspan=3"
|
||||
#elif $type == 'image'
|
||||
"colspan=2"
|
||||
#else
|
||||
"colspan=1"
|
||||
#end if
|
||||
#end def
|
||||
|
||||
#if $type == 'component'
|
||||
<h4>Component RPMs of buildroot $buildroot.tag_name-$buildroot.id-$buildroot.repo_id</h4>
|
||||
#elif $type == 'image'
|
||||
<h4>RPMs installed in <a href="imageinfo?imageID=$image.id">$image.filename</a></h4>
|
||||
#else
|
||||
<h4>RPMs built in buildroot $buildroot.tag_name-$buildroot.id-$buildroot.repo_id</h4>
|
||||
#end if
|
||||
|
||||
<table class="data-list">
|
||||
<tr>
|
||||
<td class="paginate" colspan="#if $type == 'component' then '3' else '1'#">
|
||||
<td class="paginate" $getColspan()>
|
||||
#if $len($rpmPages) > 1
|
||||
<form class="pageJump" action="">
|
||||
Page:
|
||||
<select onchange="javascript: window.location = 'rpmlist?buildrootID=$buildroot.id&start=' + this.value * $rpmRange + '$util.passthrough($self, 'order', 'type')';">
|
||||
<select onchange="javascript: window.location = 'rpmlist?$getID()&start=' + this.value * $rpmRange + '$util.passthrough($self, 'order', 'type')';">
|
||||
#for $pageNum in $rpmPages
|
||||
<option value="$pageNum"#if $pageNum == $rpmCurrentPage then ' selected="selected"' else ''#>#echo $pageNum + 1#</option>
|
||||
#end for
|
||||
|
|
@ -22,21 +42,23 @@
|
|||
</form>
|
||||
#end if
|
||||
#if $rpmStart > 0
|
||||
<a href="rpmlist?buildrootID=$buildroot.id&start=#echo $rpmStart - $rpmRange #$util.passthrough($self, 'order', 'type')"><<<</a>
|
||||
<a href="rpmlist?$getID()&start=#echo $rpmStart - $rpmRange #$util.passthrough($self, 'order', 'type')"><<<</a>
|
||||
#end if
|
||||
#if $totalRpms != 0
|
||||
<strong>RPMs #echo $rpmStart + 1 # through #echo $rpmStart + $rpmCount # of $totalRpms</strong>
|
||||
#end if
|
||||
#if $rpmStart + $rpmCount < $totalRpms
|
||||
<a href="rpmlist?buildrootID=$buildroot.id&start=#echo $rpmStart + $rpmRange#$util.passthrough($self, 'order', 'type')">>>></a>
|
||||
<a href="rpmlist?$getID()&start=#echo $rpmStart + $rpmRange#$util.passthrough($self, 'order', 'type')">>>></a>
|
||||
#end if
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="list-header">
|
||||
<th><a href="rpmlist?buildrootID=$buildroot.id&order=$util.toggleOrder($self, 'nvr')$util.passthrough($self, 'type')">NVR</a> $util.sortImage($self, 'nvr')</th>
|
||||
<th><a href="rpmlist?$getID()&order=$util.toggleOrder($self, 'nvr')$util.passthrough($self, 'type')">NVR</a> $util.sortImage($self, 'nvr')</th>
|
||||
#if $type == 'component'
|
||||
<th><a href="rpmlist?buildrootID=$buildroot.id&order=$util.toggleOrder($self, 'external_repo_name')$util.passthrough($self, 'type')">Origin</a> $util.sortImage($self, 'external_repo_name')</th>
|
||||
<th><a href="rpmlist?buildrootID=$buildroot.id&order=$util.toggleOrder($self, 'is_update')$util.passthrough($self, 'type')">Update?</a> $util.sortImage($self, 'is_update')</th>
|
||||
<th><a href="rpmlist?$getID()&order=$util.toggleOrder($self, 'external_repo_name')$util.passthrough($self, 'type')">Origin</a> $util.sortImage($self, 'external_repo_name')</th>
|
||||
<th><a href="rpmlist?$getID()&order=$util.toggleOrder($self, 'is_update')$util.passthrough($self, 'type')">Update?</a> $util.sortImage($self, 'is_update')</th>
|
||||
#elif $type == 'image'
|
||||
<th><a href="rpmlist?$getID()&order=$util.toggleOrder($self, 'external_repo_name')$util.passthrough($self, 'type')">Origin</a> $util.sortImage($self, 'external_repo_name')</th>
|
||||
#end if
|
||||
</tr>
|
||||
#if $len($rpms) > 0
|
||||
|
|
@ -44,13 +66,11 @@
|
|||
<tr class="$util.rowToggle($self)">
|
||||
#set $epoch = ($rpm.epoch != None and $str($rpm.epoch) + ':' or '')
|
||||
<td><a href="rpminfo?rpmID=$rpm.id">$rpm.name-$epoch$rpm.version-$rpm.release.${rpm.arch}.rpm</a></td>
|
||||
#if $type == 'component'
|
||||
#if $rpm.external_repo_id == 0
|
||||
<td>internal</td>
|
||||
#else
|
||||
<td><a href="externalrepoinfo?extrepoID=$rpm.external_repo_id">$rpm.external_repo_name</a></td>
|
||||
#end if
|
||||
#end if
|
||||
#if $type == 'component'
|
||||
#set $update = $rpm.is_update and 'yes' or 'no'
|
||||
<td class="$update">$util.imageTag($update)</td>
|
||||
|
|
@ -67,7 +87,7 @@
|
|||
#if $len($rpmPages) > 1
|
||||
<form class="pageJump" action="">
|
||||
Page:
|
||||
<select onchange="javascript: window.location = 'rpmlist?buildrootID=$buildroot.id&start=' + this.value * $rpmRange + '$util.passthrough($self, 'order', 'type')';">
|
||||
<select onchange="javascript: window.location = 'rpmlist?$getID()&start=' + this.value * $rpmRange + '$util.passthrough($self, 'order', 'type')';">
|
||||
#for $pageNum in $rpmPages
|
||||
<option value="$pageNum"#if $pageNum == $rpmCurrentPage then ' selected="selected"' else ''#>#echo $pageNum + 1#</option>
|
||||
#end for
|
||||
|
|
@ -75,13 +95,13 @@
|
|||
</form>
|
||||
#end if
|
||||
#if $rpmStart > 0
|
||||
<a href="rpmlist?buildrootID=$buildroot.id&start=#echo $rpmStart - $rpmRange #$util.passthrough($self, 'order', 'type')"><<<</a>
|
||||
<a href="rpmlist?$getID()&start=#echo $rpmStart - $rpmRange #$util.passthrough($self, 'order', 'type')"><<<</a>
|
||||
#end if
|
||||
#if $totalRpms != 0
|
||||
<strong>RPMs #echo $rpmStart + 1 # through #echo $rpmStart + $rpmCount # of $totalRpms</strong>
|
||||
#end if
|
||||
#if $rpmStart + $rpmCount < $totalRpms
|
||||
<a href="rpmlist?buildrootID=$buildroot.id&start=#echo $rpmStart + $rpmRange#$util.passthrough($self, 'order', 'type')">>>></a>
|
||||
<a href="rpmlist?$getID()&start=#echo $rpmStart + $rpmRange#$util.passthrough($self, 'order', 'type')">>>></a>
|
||||
#end if
|
||||
</td>
|
||||
</tr>
|
||||
|
|
|
|||
|
|
@ -142,6 +142,13 @@ $value
|
|||
#if $len($params) > 4
|
||||
$printOpts($params[4])
|
||||
#end if
|
||||
#elif $task.method == 'createLiveCD'
|
||||
<strong>Arch:</strong> $params[0]<br/>
|
||||
<strong>Target:</strong> <a href="buildtargetinfo?name=$params[1]">$params[1]</a><br/>
|
||||
<strong>Kickstart File:</strong> $params[2]<br/>
|
||||
#if $len($params) > 3
|
||||
$printOpts($params[3])
|
||||
#end if
|
||||
#elif $task.method == 'newRepo'
|
||||
<strong>Tag:</strong> <a href="taginfo?tagID=$tag.id">$tag.name</a><br/>
|
||||
#if $len($params) > 1
|
||||
|
|
@ -262,6 +269,14 @@ $value
|
|||
</td>
|
||||
</tr>
|
||||
#end if
|
||||
#if $task.method == 'createLiveCD' and $image
|
||||
<tr>
|
||||
<th>Image</th>
|
||||
<td>
|
||||
<a href="imageinfo?imageID=$image.id">$image.filename</a><br/>
|
||||
</td>
|
||||
</tr>
|
||||
#end if
|
||||
<tr>
|
||||
<th>Parent</th>
|
||||
<td>
|
||||
|
|
@ -320,7 +335,7 @@ ${excClass.__name__}: $cgi.escape($str($result))
|
|||
<br/>
|
||||
#end for
|
||||
#if $task.state not in ($koji.TASK_STATES.CLOSED, $koji.TASK_STATES.CANCELED, $koji.TASK_STATES.FAILED) and \
|
||||
$task.method in ('buildSRPMFromSCM', 'buildArch', 'buildMaven', 'wrapperRPM', 'createrepo')
|
||||
$task.method in ('buildSRPMFromSCM', 'buildArch', 'createLiveCD', 'buildMaven', 'wrapperRPM', 'createrepo')
|
||||
<br/>
|
||||
<a href="watchlogs?taskID=$task.id">Watch logs</a>
|
||||
#end if
|
||||
|
|
|
|||
|
|
@ -31,11 +31,22 @@ def _initValues(req, title='Build System Info', pageID='summary'):
|
|||
|
||||
return values
|
||||
|
||||
class DecodeUTF8(Cheetah.Filters.Filter):
|
||||
def filter(self, *args, **kw):
|
||||
"""Convert all strs to unicode objects"""
|
||||
result = super(DecodeUTF8, self).filter(*args, **kw)
|
||||
if isinstance(result, unicode):
|
||||
pass
|
||||
else:
|
||||
result = result.decode('utf-8', 'replace')
|
||||
return result
|
||||
|
||||
# Escape ampersands so the output can be valid XHTML
|
||||
class XHTMLFilter(Cheetah.Filters.EncodeUnicode):
|
||||
class XHTMLFilter(DecodeUTF8):
|
||||
def filter(self, *args, **kw):
|
||||
result = super(XHTMLFilter, self).filter(*args, **kw)
|
||||
result = result.replace('&', '&')
|
||||
result = result.replace('&amp;', '&')
|
||||
result = result.replace('&nbsp;', ' ')
|
||||
result = result.replace('&lt;', '<')
|
||||
result = result.replace('&gt;', '>')
|
||||
|
|
@ -59,7 +70,7 @@ def _genHTML(req, fileName):
|
|||
tmpl_class = Cheetah.Template.Template.compile(file=fileName)
|
||||
TEMPLATES[fileName] = tmpl_class
|
||||
tmpl_inst = tmpl_class(namespaces=[req._values], filter=XHTMLFilter)
|
||||
return str(tmpl_inst)
|
||||
return tmpl_inst.respond().encode('utf-8', 'replace')
|
||||
|
||||
def _truncTime():
|
||||
now = datetime.datetime.now()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue