Merge remote-tracking branch 'origin' into cgen
This commit is contained in:
commit
e8a30591fa
45 changed files with 772 additions and 284 deletions
3
Makefile
3
Makefile
|
|
@ -105,6 +105,7 @@ force-tag::
|
|||
# @$(MAKE) tag TAG_OPTS="-F $(TAG_OPTS)"
|
||||
|
||||
DESTDIR ?= /
|
||||
TYPE = systemd
|
||||
install:
|
||||
@if [ "$(DESTDIR)" = "" ]; then \
|
||||
echo " "; \
|
||||
|
|
@ -115,4 +116,4 @@ install:
|
|||
mkdir -p $(DESTDIR)
|
||||
|
||||
for d in $(SUBDIRS); do make DESTDIR=`cd $(DESTDIR); pwd` \
|
||||
-C $$d install; [ $$? = 0 ] || exit 1; done
|
||||
-C $$d install TYPE=$(TYPE); [ $$? = 0 ] || exit 1; done
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
|
||||
BINFILES = kojid
|
||||
LIBEXECFILES = mergerepos
|
||||
SYSTEMDSYSTEMUNITDIR = $(shell pkg-config systemd --variable=systemdsystemunitdir)
|
||||
TYPE = systemd
|
||||
|
||||
_default:
|
||||
@echo "nothing to make. try make install"
|
||||
|
|
@ -9,7 +10,7 @@ clean:
|
|||
rm -f *.o *.so *.pyc *~
|
||||
|
||||
|
||||
install:
|
||||
_install:
|
||||
@if [ "$(DESTDIR)" = "" ]; then \
|
||||
echo " "; \
|
||||
echo "ERROR: A destdir is required"; \
|
||||
|
|
@ -23,13 +24,19 @@ install:
|
|||
install -p -m 755 $(LIBEXECFILES) $(DESTDIR)/usr/libexec/kojid
|
||||
|
||||
mkdir -p $(DESTDIR)/etc/mock/koji
|
||||
|
||||
mkdir -p $(DESTDIR)/etc/kojid
|
||||
install -p -m 644 kojid.conf $(DESTDIR)/etc/kojid/kojid.conf
|
||||
|
||||
install-systemd: _install
|
||||
mkdir -p $(DESTDIR)$(SYSTEMDSYSTEMUNITDIR)
|
||||
install -p -m 644 kojid.service $(DESTDIR)$(SYSTEMDSYSTEMUNITDIR)
|
||||
|
||||
install-sysv: _install
|
||||
mkdir -p $(DESTDIR)/etc/rc.d/init.d
|
||||
install -p -m 755 kojid.init $(DESTDIR)/etc/rc.d/init.d/kojid
|
||||
|
||||
mkdir -p $(DESTDIR)/etc/sysconfig
|
||||
install -p -m 644 kojid.sysconfig $(DESTDIR)/etc/sysconfig/kojid
|
||||
|
||||
mkdir -p $(DESTDIR)/etc/kojid
|
||||
install -p -m 644 kojid.conf $(DESTDIR)/etc/kojid/kojid.conf
|
||||
|
||||
|
||||
install: install-$(TYPE)
|
||||
|
|
|
|||
191
builder/kojid
191
builder/kojid
|
|
@ -5,7 +5,7 @@
|
|||
#
|
||||
# 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;
|
||||
# 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,
|
||||
|
|
@ -250,12 +250,10 @@ class BuildRoot(object):
|
|||
entries = []
|
||||
if plugin:
|
||||
tag_name = 'pluginRepository'
|
||||
id_suffix = 'plugin-repo'
|
||||
name_prefix = 'Plugin repository for Koji'
|
||||
else:
|
||||
tag_name = 'repository'
|
||||
id_suffix = 'repo'
|
||||
name_prefix = 'Repository for Koji'
|
||||
id_suffix = 'repo'
|
||||
name_prefix = 'Repository for Koji'
|
||||
for dep in self.deps:
|
||||
if isinstance(dep, (int, long)):
|
||||
# dep is a task ID, the url points to the task output directory
|
||||
|
|
@ -1032,11 +1030,11 @@ class BaseBuildTask(BaseTaskHandler):
|
|||
tag_arches = [koji.canonArch(a) for a in tag['arches'].split()]
|
||||
host_arches = hostdata['arches'].split()
|
||||
if not set(tag_arches).intersection(host_arches):
|
||||
self.logger.info('Task %s (%s): tag arches (%s) and ' \
|
||||
'host arches (%s) are disjoint' % \
|
||||
(self.id, self.method,
|
||||
', '.join(tag_arches), ', '.join(host_arches)))
|
||||
return False
|
||||
self.logger.info('Task %s (%s): tag arches (%s) and ' \
|
||||
'host arches (%s) are disjoint' % \
|
||||
(self.id, self.method,
|
||||
', '.join(tag_arches), ', '.join(host_arches)))
|
||||
return False
|
||||
#otherwise...
|
||||
# This is in principle an error condition, but this is not a good place
|
||||
# to fail. Instead we proceed and let the task fail normally.
|
||||
|
|
@ -1283,7 +1281,17 @@ class BuildMavenTask(BaseBuildTask):
|
|||
dirnames.remove(skip)
|
||||
for filename in filenames:
|
||||
filepath = os.path.join(dirpath, filename)
|
||||
zfo.write(filepath, filepath[roottrim:])
|
||||
if os.path.islink(filepath):
|
||||
content = os.readlink(filepath)
|
||||
st = os.lstat(filepath)
|
||||
mtime = time.localtime(st.st_mtime)
|
||||
info = zipfile.ZipInfo(filepath[roottrim:])
|
||||
info.external_attr |= 0120000 << 16L # symlink file type
|
||||
info.compress_type = zipfile.ZIP_STORED
|
||||
info.date_time = mtime[:6]
|
||||
zfo.writestr(info, content)
|
||||
else:
|
||||
zfo.write(filepath, filepath[roottrim:])
|
||||
zfo.close()
|
||||
|
||||
def checkHost(self, hostdata):
|
||||
|
|
@ -2389,7 +2397,7 @@ class ImageTask(BaseTaskHandler):
|
|||
|
||||
def fetchKickstart(self, broot, ksfile):
|
||||
"""
|
||||
Retrieve the kickstart file we were given (locally or remotely) and
|
||||
Retrieve the kickstart file we were given (locally or remotely) and
|
||||
upload it.
|
||||
|
||||
Note that if the KS file existed locally, then "ksfile" is a relative
|
||||
|
|
@ -2488,7 +2496,7 @@ class ImageTask(BaseTaskHandler):
|
|||
|
||||
# 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('/tmp', 'koji-image-%s-%i.ks' %
|
||||
kskoji = os.path.join('/tmp', 'koji-image-%s-%i.ks' %
|
||||
(target_info['build_tag_name'], self.id))
|
||||
kojikspath = os.path.join(broot.rootdir(), kskoji[1:])
|
||||
outfile = open(kojikspath, 'w')
|
||||
|
|
@ -2503,7 +2511,7 @@ class ImageTask(BaseTaskHandler):
|
|||
|
||||
def getImagePackages(self, cachepath):
|
||||
"""
|
||||
Read RPM header information from the yum cache available in the
|
||||
Read RPM header information from the yum cache available in the
|
||||
given path. Returns a list of dictionaries for each RPM included.
|
||||
"""
|
||||
found = False
|
||||
|
|
@ -2524,8 +2532,8 @@ class ImageTask(BaseTaskHandler):
|
|||
raise koji.LiveCDError, 'No repos found in yum cache!'
|
||||
return hdrlist
|
||||
|
||||
# ApplianceTask begins with a mock chroot, and then installs appliance-tools
|
||||
# into it via the appliance-build group. appliance-creator is then executed
|
||||
# ApplianceTask begins with a mock chroot, and then installs appliance-tools
|
||||
# into it via the appliance-build group. appliance-creator is then executed
|
||||
# in the chroot to create the appliance image.
|
||||
#
|
||||
class ApplianceTask(ImageTask):
|
||||
|
|
@ -2548,7 +2556,7 @@ class ApplianceTask(ImageTask):
|
|||
if opts == None:
|
||||
opts = {}
|
||||
self.opts = opts
|
||||
broot = self.makeImgBuildRoot(build_tag, repo_info, arch,
|
||||
broot = self.makeImgBuildRoot(build_tag, repo_info, arch,
|
||||
'appliance-build')
|
||||
kspath = self.fetchKickstart(broot, ksfile)
|
||||
self.readKickstart(kspath, opts)
|
||||
|
|
@ -2561,11 +2569,11 @@ class ApplianceTask(ImageTask):
|
|||
app_log = '/tmp/appliance.log'
|
||||
os.mkdir(opath)
|
||||
|
||||
cmd = ['/usr/bin/appliance-creator', '-c', kskoji, '-d', '-v',
|
||||
cmd = ['/usr/bin/appliance-creator', '-c', kskoji, '-d', '-v',
|
||||
'--logfile', app_log, '--cache', cachedir, '-o', odir]
|
||||
for arg_name in ('vmem', 'vcpu', 'format'):
|
||||
arg = opts.get(arg_name)
|
||||
if arg != None:
|
||||
if arg != None:
|
||||
cmd.extend(['--%s' % arg_name, arg])
|
||||
appname = '%s-%s-%s' % (name, version, release)
|
||||
cmd.extend(['--name', appname])
|
||||
|
|
@ -2582,7 +2590,7 @@ class ApplianceTask(ImageTask):
|
|||
results = []
|
||||
for directory, subdirs, files in os.walk(opath):
|
||||
for f in files:
|
||||
results.append(os.path.join(broot.rootdir(), 'tmp',
|
||||
results.append(os.path.join(broot.rootdir(), 'tmp',
|
||||
directory, f))
|
||||
self.logger.debug('output: %s' % results)
|
||||
if len(results) == 0:
|
||||
|
|
@ -2610,7 +2618,7 @@ class ApplianceTask(ImageTask):
|
|||
cachedir[1:]))
|
||||
broot.markExternalRPMs(hdrlist)
|
||||
imgdata['rpmlist'] = hdrlist
|
||||
|
||||
|
||||
broot.expire()
|
||||
return imgdata
|
||||
|
||||
|
|
@ -2706,7 +2714,7 @@ class LiveCDTask(ImageTask):
|
|||
livecd_log = '/tmp/livecd.log'
|
||||
cmd = ['/usr/bin/livecd-creator', '-c', kskoji, '-d', '-v',
|
||||
'--logfile', livecd_log, '--cache', cachedir]
|
||||
# we set the fs label to the same as the isoname if it exists,
|
||||
# we set the fs label to the same as the isoname if it exists,
|
||||
# taking at most 32 characters
|
||||
isoname = '%s-%s-%s' % (name, version, release)
|
||||
cmd.extend(['-f', isoname[:32]])
|
||||
|
|
@ -2833,7 +2841,7 @@ class OzImageTask(BaseTaskHandler):
|
|||
"'%s' : %s" % (kspath, e))
|
||||
return ks
|
||||
|
||||
def prepareKickstart(self, kspath):
|
||||
def prepareKickstart(self, kspath, install_tree):
|
||||
"""
|
||||
Process the ks file to be used for controlled image generation. This
|
||||
method also uploads the modified kickstart file to the task output
|
||||
|
|
@ -2870,6 +2878,8 @@ class OzImageTask(BaseTaskHandler):
|
|||
self.logger.debug('BASEURL: %s' % baseurl)
|
||||
ks.handler.repo.repoList.append(repo_class(
|
||||
baseurl=baseurl, name='koji-override-0'))
|
||||
# inject the URL of the install tree into the kickstart
|
||||
ks.handler.url.url = install_tree
|
||||
return ks
|
||||
|
||||
def writeKickstart(self, ksobj, ksname):
|
||||
|
|
@ -3051,7 +3061,7 @@ class BaseImageTask(OzImageTask):
|
|||
Some image formats require others to be processed first, which is why
|
||||
we have to do this. raw files in particular may not be kept.
|
||||
"""
|
||||
supported = ('raw', 'raw-xz', 'vmdk', 'qcow', 'qcow2', 'vdi', 'rhevm-ova', 'vsphere-ova', 'docker')
|
||||
supported = ('raw', 'raw-xz', 'vmdk', 'qcow', 'qcow2', 'vdi', 'rhevm-ova', 'vsphere-ova', 'docker', 'vagrant-virtualbox', 'vagrant-libvirt', 'vpc')
|
||||
for f in formats:
|
||||
if f not in supported:
|
||||
raise koji.ApplianceError('Invalid format: %s' % f)
|
||||
|
|
@ -3081,8 +3091,11 @@ class BaseImageTask(OzImageTask):
|
|||
'vdi': self._buildConvert,
|
||||
'qcow': self._buildConvert,
|
||||
'qcow2': self._buildConvert,
|
||||
'vpc': self._buildConvert,
|
||||
'rhevm-ova': self._buildOVA,
|
||||
'vsphere-ova': self._buildOVA,
|
||||
'vagrant-virtualbox': self._buildOVA,
|
||||
'vagrant-libvirt': self._buildOVA,
|
||||
'docker': self._buildDocker
|
||||
}
|
||||
# add a handler to the logger so that we capture ImageFactory's logging
|
||||
|
|
@ -3221,6 +3234,17 @@ class BaseImageTask(OzImageTask):
|
|||
img_opts = {}
|
||||
if self.opts.get('ova_option'):
|
||||
img_opts = dict([o.split('=') for o in self.opts.get('ova_option')])
|
||||
# As far as Image Factory is concerned, vagrant boxes are just another type of OVA
|
||||
# We communicate the desire for vagrant-specific formatting by adding the *_ova_format
|
||||
# options and turning the underlying format option back into one of the two target
|
||||
# image types ('vsphere-ova' or 'rhevm-ova') that are used to generate the intermediate
|
||||
# disk image
|
||||
if format == 'vagrant-virtualbox':
|
||||
format = 'vsphere-ova'
|
||||
img_opts['vsphere_ova_format'] = 'vagrant-virtualbox'
|
||||
if format == 'vagrant-libvirt':
|
||||
format = 'rhevm-ova'
|
||||
img_opts['rhevm_ova_format'] = 'vagrant-libvirt'
|
||||
targ = self._do_target_image(self.base_img.base_image.identifier,
|
||||
format.replace('-ova', ''))
|
||||
targ2 = self._do_target_image(targ.target_image.identifier, 'OVA',
|
||||
|
|
@ -3278,7 +3302,11 @@ class BaseImageTask(OzImageTask):
|
|||
@returns
|
||||
a dict with some metadata about the image
|
||||
"""
|
||||
newimg = os.path.join(self.workdir, self.imgname + '.%s' % format)
|
||||
self.logger.debug('converting an image to "%s"' % format)
|
||||
ofmt = format
|
||||
if format == 'vpc':
|
||||
ofmt = 'vhd'
|
||||
newimg = os.path.join(self.workdir, self.imgname + '.%s' % ofmt)
|
||||
cmd = ['/usr/bin/qemu-img', 'convert', '-f', 'raw', '-O',
|
||||
format, self.base_img.base_image.data, newimg]
|
||||
if format in ('qcow', 'qcow2'):
|
||||
|
|
@ -3301,7 +3329,7 @@ class BaseImageTask(OzImageTask):
|
|||
|
||||
# First, prepare the kickstart to use the repos we tell it
|
||||
kspath = self.fetchKickstart()
|
||||
ks = self.prepareKickstart(kspath)
|
||||
ks = self.prepareKickstart(kspath, inst_tree)
|
||||
kskoji = self.writeKickstart(ks,
|
||||
os.path.join(self.workdir, 'koji-%s-%i-base.ks' %
|
||||
(self.target_info['build_tag_name'], self.id)))
|
||||
|
|
@ -3381,8 +3409,15 @@ class BaseImageTask(OzImageTask):
|
|||
newimg = images[format]['image']
|
||||
if 'ova' in format or format == 'raw-xz':
|
||||
newname = self.imgname + '.' + format.replace('-', '.')
|
||||
elif 'vagrant' in format:
|
||||
# This embeds the vagrant target and the ".box" format in the name
|
||||
# Previously, based on filename, these looked like OVAs
|
||||
# This was confusing to many people
|
||||
newname = self.imgname + '.' + format + '.box'
|
||||
elif format == 'docker':
|
||||
newname = self.imgname + '.' + 'tar.xz'
|
||||
elif format == 'vpc':
|
||||
newname = self.imgname + '.' + 'vhd'
|
||||
else:
|
||||
newname = self.imgname + '.' + format
|
||||
if format != 'docker':
|
||||
|
|
@ -3398,7 +3433,7 @@ class BaseImageTask(OzImageTask):
|
|||
class BuildIndirectionImageTask(OzImageTask):
|
||||
Methods = ['indirectionimage']
|
||||
|
||||
# So, these are copied directly from the base image class
|
||||
# So, these are copied directly from the base image class
|
||||
# Realistically, we want to inherit methods from both BuildImageTask
|
||||
# and OzImageTask.
|
||||
# TODO: refactor - my initial suggestion would be to have OzImageTask
|
||||
|
|
@ -3427,7 +3462,7 @@ class BuildIndirectionImageTask(OzImageTask):
|
|||
def fetchHubOrSCM(self, filepath, fileurl):
|
||||
"""
|
||||
Retrieve a file either from the hub or a remote scm
|
||||
|
||||
|
||||
If fileurl is None we assume we are being asked to retrieve from
|
||||
the hub and that filepath is relative to /mnt/koji/work.
|
||||
if fileurl contains a value we assume a remote SCM.
|
||||
|
|
@ -3466,7 +3501,7 @@ class BuildIndirectionImageTask(OzImageTask):
|
|||
def _task_to_image(task_id):
|
||||
""" Take a task ID and turn it into an Image Factory Base Image object """
|
||||
pim = PersistentImageManager.default_manager()
|
||||
taskinfo = self.session.getTaskInfo(task_id)
|
||||
taskinfo = self.session.getTaskInfo(task_id)
|
||||
taskstate = koji.TASK_STATES[taskinfo['state']].lower()
|
||||
if taskstate != 'closed':
|
||||
raise koji.BuildError("Input task (%d) must be in closed state - current state is (%s)" %
|
||||
|
|
@ -3475,21 +3510,21 @@ class BuildIndirectionImageTask(OzImageTask):
|
|||
if taskmethod != "createImage":
|
||||
raise koji.BuildError("Input task method must be 'createImage' - actual method (%s)" %
|
||||
(taskmethod))
|
||||
result = self.session.getTaskResult(task_id)
|
||||
files = self.session.listTaskOutput(task_id)
|
||||
result = self.session.getTaskResult(task_id)
|
||||
files = self.session.listTaskOutput(task_id)
|
||||
|
||||
# This approach works for both scratch and saved/formal images
|
||||
# The downside is that we depend on the output file naming convention
|
||||
def _match_name(inlist, namere):
|
||||
for filename in inlist:
|
||||
if re.search(namere, filename):
|
||||
return filename
|
||||
task_diskimage = _match_name(result['files'], ".*qcow2$")
|
||||
task_tdl = _match_name(result['files'], "tdl.*xml")
|
||||
def _match_name(inlist, namere):
|
||||
for filename in inlist:
|
||||
if re.search(namere, filename):
|
||||
return filename
|
||||
task_diskimage = _match_name(result['files'], ".*qcow2$")
|
||||
task_tdl = _match_name(result['files'], "tdl.*xml")
|
||||
|
||||
task_dir = os.path.join(koji.pathinfo.work(),koji.pathinfo.taskrelpath(task_id))
|
||||
diskimage_full = os.path.join(task_dir, task_diskimage)
|
||||
tdl_full = os.path.join(task_dir, task_tdl)
|
||||
task_dir = os.path.join(koji.pathinfo.work(),koji.pathinfo.taskrelpath(task_id))
|
||||
diskimage_full = os.path.join(task_dir, task_diskimage)
|
||||
tdl_full = os.path.join(task_dir, task_tdl)
|
||||
|
||||
if not (os.path.isfile(diskimage_full) and os.path.isfile(tdl_full)):
|
||||
raise koji.BuildError("Missing TDL or qcow2 image for task (%d) - possible expired scratch build" % (task_id))
|
||||
|
|
@ -3508,7 +3543,7 @@ class BuildIndirectionImageTask(OzImageTask):
|
|||
factory_base_image.status = 'COMPLETE'
|
||||
# Now save it
|
||||
pim.save_image(factory_base_image)
|
||||
|
||||
|
||||
# We can now reference this object directly or via its UUID in persistent storage
|
||||
return factory_base_image
|
||||
|
||||
|
|
@ -3518,10 +3553,10 @@ class BuildIndirectionImageTask(OzImageTask):
|
|||
build = self.session.getBuild(nvr)
|
||||
if not build:
|
||||
raise koji.BuildError("Could not find build for (%s)" % (nvr))
|
||||
|
||||
|
||||
buildarchives = self.session.listArchives(build['id'])
|
||||
if not buildarchives:
|
||||
raise koji.Builderror("Could not retrieve archives for build (%s) from NVR (%s)" %
|
||||
raise koji.Builderror("Could not retrieve archives for build (%s) from NVR (%s)" %
|
||||
(build['id'], nvr))
|
||||
|
||||
buildfiles = [ x['filename'] for x in buildarchives ]
|
||||
|
|
@ -3539,7 +3574,7 @@ class BuildIndirectionImageTask(OzImageTask):
|
|||
tdl_full = os.path.join(builddir, build_tdl)
|
||||
|
||||
if not (os.path.isfile(diskimage_full) and os.path.isfile(tdl_full)):
|
||||
raise koji.BuildError("Missing TDL (%s) or qcow2 (%s) image for image (%s) - this should never happen" %
|
||||
raise koji.BuildError("Missing TDL (%s) or qcow2 (%s) image for image (%s) - this should never happen" %
|
||||
(build_tdl, build_diskimage, nvr))
|
||||
|
||||
# The sequence to recreate a valid persistent image is as follows
|
||||
|
|
@ -3588,12 +3623,12 @@ class BuildIndirectionImageTask(OzImageTask):
|
|||
release = opts['release']
|
||||
|
||||
# TODO: Another mostly copy-paste
|
||||
if not release:
|
||||
release = self.getRelease(name, version)
|
||||
if '-' in version:
|
||||
raise koji.ApplianceError('The Version may not have a hyphen')
|
||||
if '-' in release:
|
||||
raise koji.ApplianceError('The Release may not have a hyphen')
|
||||
if not release:
|
||||
release = self.getRelease(name, version)
|
||||
if '-' in version:
|
||||
raise koji.ApplianceError('The Version may not have a hyphen')
|
||||
if '-' in release:
|
||||
raise koji.ApplianceError('The Release may not have a hyphen')
|
||||
|
||||
indirection_template = self.fetchHubOrSCM(opts.get('indirection_template'),
|
||||
opts.get('indirection_template_url'))
|
||||
|
|
@ -3601,24 +3636,24 @@ class BuildIndirectionImageTask(OzImageTask):
|
|||
self.logger.debug('Got indirection template %s' % (indirection_template))
|
||||
|
||||
try:
|
||||
if opts['utility_image_build']:
|
||||
utility_factory_image = _nvr_to_image(opts['utility_image_build'], opts['arch'])
|
||||
else:
|
||||
utility_factory_image = _task_to_image(int(opts['utility_image_task']))
|
||||
if opts['utility_image_build']:
|
||||
utility_factory_image = _nvr_to_image(opts['utility_image_build'], opts['arch'])
|
||||
else:
|
||||
utility_factory_image = _task_to_image(int(opts['utility_image_task']))
|
||||
|
||||
if opts['base_image_build']:
|
||||
base_factory_image = _nvr_to_image(opts['base_image_build'], opts['arch'])
|
||||
else:
|
||||
base_factory_image = _task_to_image(int(opts['base_image_task']))
|
||||
if opts['base_image_build']:
|
||||
base_factory_image = _nvr_to_image(opts['base_image_build'], opts['arch'])
|
||||
else:
|
||||
base_factory_image = _task_to_image(int(opts['base_image_task']))
|
||||
except Exception, e:
|
||||
self.logger.exception(e)
|
||||
raise
|
||||
|
||||
# OK - We have a template and two input images - lets build
|
||||
bld_info = None
|
||||
if not opts['scratch']:
|
||||
bld_info = self.initImageBuild(name, version, release,
|
||||
target_info, opts)
|
||||
bld_info = None
|
||||
if not opts['scratch']:
|
||||
bld_info = self.initImageBuild(name, version, release,
|
||||
target_info, opts)
|
||||
|
||||
try:
|
||||
return self._do_indirection(opts, base_factory_image, utility_factory_image,
|
||||
|
|
@ -3667,9 +3702,9 @@ class BuildIndirectionImageTask(OzImageTask):
|
|||
open(target_image.data, "w").write("Mock build from task ID: %s" %
|
||||
(str(self.id)))
|
||||
target_image.status='COMPLETE'
|
||||
else:
|
||||
else:
|
||||
target = bd.builder_for_target_image('indirection',
|
||||
image_id=base_factory_image.identifier,
|
||||
image_id=base_factory_image.identifier,
|
||||
parameters=params)
|
||||
target.target_thread.join()
|
||||
except Exception, e:
|
||||
|
|
@ -3697,7 +3732,7 @@ class BuildIndirectionImageTask(OzImageTask):
|
|||
myresults['logs'] = [ os.path.basename(ozlog) ]
|
||||
myresults['arch'] = opts['arch']
|
||||
# TODO: This should instead track the two input images: base and utility
|
||||
myresults['rpmlist'] = [ ]
|
||||
myresults['rpmlist'] = [ ]
|
||||
|
||||
# This is compatible with some helper methods originally implemented for the base
|
||||
# image build. In the original usage, the dict contains an entry per build arch
|
||||
|
|
@ -3825,7 +3860,7 @@ class BuildSRPMFromSCMTask(BaseBuildTask):
|
|||
raise koji.BuildError, "Multiple srpms found in %s: %s" % (sourcedir, ", ".join(srpms))
|
||||
else:
|
||||
srpm = srpms[0]
|
||||
|
||||
|
||||
# check srpm name
|
||||
h = koji.get_rpm_header(srpm)
|
||||
name = h[rpm.RPMTAG_NAME]
|
||||
|
|
@ -3844,7 +3879,7 @@ class BuildSRPMFromSCMTask(BaseBuildTask):
|
|||
broot.expire()
|
||||
|
||||
return {'srpm': "%s/%s" % (uploadpath, srpm_name),
|
||||
'logs': ["%s/%s" % (uploadpath, os.path.basename(f))
|
||||
'logs': ["%s/%s" % (uploadpath, os.path.basename(f))
|
||||
for f in log_files],
|
||||
'brootid': brootid,
|
||||
}
|
||||
|
|
@ -3969,16 +4004,16 @@ Build Info: %(weburl)s/buildinfo?buildID=%(build_id)i\r
|
|||
if not data:
|
||||
data = {}
|
||||
taskinfo = self.session.getTaskInfo(task_id)
|
||||
|
||||
|
||||
if not taskinfo:
|
||||
# invalid task_id
|
||||
return data
|
||||
|
||||
|
||||
if taskinfo['host_id']:
|
||||
hostinfo = self.session.getHost(taskinfo['host_id'])
|
||||
else:
|
||||
hostinfo = None
|
||||
|
||||
|
||||
result = None
|
||||
try:
|
||||
result = self.session.getTaskResult(task_id)
|
||||
|
|
@ -3994,17 +4029,17 @@ Build Info: %(weburl)s/buildinfo?buildID=%(build_id)i\r
|
|||
sys.exc_clear()
|
||||
if not result:
|
||||
result = 'Unknown'
|
||||
|
||||
|
||||
files = self.session.listTaskOutput(task_id)
|
||||
logs = [filename for filename in files if filename.endswith('.log')]
|
||||
rpms = [filename for filename in files if filename.endswith('.rpm') and not filename.endswith('.src.rpm')]
|
||||
srpms = [filename for filename in files if filename.endswith('.src.rpm')]
|
||||
misc = [filename for filename in files if filename not in logs + rpms + srpms]
|
||||
|
||||
|
||||
logs.sort()
|
||||
rpms.sort()
|
||||
misc.sort()
|
||||
|
||||
|
||||
data[task_id] = {}
|
||||
data[task_id]['id'] = taskinfo['id']
|
||||
data[task_id]['method'] = taskinfo['method']
|
||||
|
|
@ -4018,7 +4053,7 @@ Build Info: %(weburl)s/buildinfo?buildID=%(build_id)i\r
|
|||
data[task_id]['rpms'] = rpms
|
||||
data[task_id]['srpms'] = srpms
|
||||
data[task_id]['misc'] = misc
|
||||
|
||||
|
||||
children = self.session.getTaskChildren(task_id)
|
||||
for child in children:
|
||||
data = self._getTaskData(child['id'], data)
|
||||
|
|
@ -4057,7 +4092,7 @@ Build Info: %(weburl)s/buildinfo?buildID=%(build_id)i\r
|
|||
elif build['state'] == koji.BUILD_STATES['FAILED']:
|
||||
failure_data = task_data[task_id]['result']
|
||||
failed_hosts = ['%s (%s)' % (task['host'], task['arch']) for task in task_data.values() if task['host'] and task['state'] == 'failed']
|
||||
failure_info = "\r\n%s (%d) failed on %s:\r\n %s" % (build_nvr, build_id,
|
||||
failure_info = "\r\n%s (%d) failed on %s:\r\n %s" % (build_nvr, build_id,
|
||||
', '.join(failed_hosts),
|
||||
failure_data)
|
||||
|
||||
|
|
@ -4066,14 +4101,14 @@ Build Info: %(weburl)s/buildinfo?buildID=%(build_id)i\r
|
|||
tasks = {'failed' : [task for task in task_data.values() if task['state'] == 'failed'],
|
||||
'canceled' : [task for task in task_data.values() if task['state'] == 'canceled'],
|
||||
'closed' : [task for task in task_data.values() if task['state'] == 'closed']}
|
||||
|
||||
|
||||
srpms = []
|
||||
for taskinfo in task_data.values():
|
||||
for srpmfile in taskinfo['srpms']:
|
||||
srpms.append(srpmfile)
|
||||
srpms = self.uniq(srpms)
|
||||
srpms.sort()
|
||||
|
||||
|
||||
if srpms:
|
||||
output = "SRPMS:\r\n"
|
||||
for srpm in srpms:
|
||||
|
|
@ -4564,7 +4599,7 @@ if __name__ == "__main__":
|
|||
except koji.AuthError, e:
|
||||
quit("Error: Unable to log in: %s" % e)
|
||||
except xmlrpclib.ProtocolError:
|
||||
quit("Error: Unable to connect to server %s" % (options.server))
|
||||
quit("Error: Unable to connect to server %s" % (options.server))
|
||||
elif options.user:
|
||||
try:
|
||||
# authenticate using user/password
|
||||
|
|
|
|||
14
builder/kojid.service
Normal file
14
builder/kojid.service
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
[Unit]
|
||||
Description=Koji build server
|
||||
Documentation=https://fedoraproject.org/wiki/Koji/ServerHowTo
|
||||
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
ExecStart=/usr/sbin/kojid \
|
||||
--fg \
|
||||
--force-lock \
|
||||
--verbose
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
146
cli/koji
146
cli/koji
|
|
@ -6,7 +6,7 @@
|
|||
#
|
||||
# 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;
|
||||
# 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,
|
||||
|
|
@ -141,7 +141,7 @@ def get_options():
|
|||
parser.add_option("--debug-xmlrpc", action="store_true", default=False,
|
||||
help=_("show xmlrpc debug output"))
|
||||
parser.add_option("-q", "--quiet", action="store_true", default=False,
|
||||
help=_("run quietly"))
|
||||
help=_("run quietly"))
|
||||
parser.add_option("--skip-main", action="store_true", default=False,
|
||||
help=_("don't actually run main"))
|
||||
parser.add_option("-s", "--server", help=_("url of XMLRPC server"))
|
||||
|
|
@ -205,7 +205,7 @@ def get_options():
|
|||
'krbservice': 'host',
|
||||
'cert': '~/.koji/client.crt',
|
||||
'ca': '~/.koji/clientca.crt',
|
||||
'serverca': '~/.koji/serverca.crt',
|
||||
'serverca': '~/.koji/serverca.crt',
|
||||
'authtype': None
|
||||
}
|
||||
#note: later config files override earlier ones
|
||||
|
|
@ -263,7 +263,7 @@ def get_options():
|
|||
# expand paths here, so we don't have to worry about it later
|
||||
value = os.path.expanduser(getattr(options, name))
|
||||
setattr(options, name, value)
|
||||
|
||||
|
||||
#honor topdir
|
||||
if options.topdir:
|
||||
koji.BASEDIR = options.topdir
|
||||
|
|
@ -427,7 +427,7 @@ def display_task_results(tasks):
|
|||
for task in [task for task in tasks.values() if task.level == 0]:
|
||||
state = task.info['state']
|
||||
task_label = task.str()
|
||||
|
||||
|
||||
if state == koji.TASK_STATES['CLOSED']:
|
||||
print '%s completed successfully' % task_label
|
||||
elif state == koji.TASK_STATES['FAILED']:
|
||||
|
|
@ -526,7 +526,7 @@ def watch_logs(session, tasklist, opts):
|
|||
while contents:
|
||||
if not taskoffsets.has_key(log):
|
||||
taskoffsets[log] = 0
|
||||
|
||||
|
||||
contents = session.downloadTaskOutput(task_id, log, taskoffsets[log], 16384)
|
||||
taskoffsets[log] += len(contents)
|
||||
if contents:
|
||||
|
|
@ -556,7 +556,7 @@ def handle_add_group(options, session, args):
|
|||
assert False
|
||||
tag = args[0]
|
||||
group = args[1]
|
||||
|
||||
|
||||
activate_session(session)
|
||||
if not session.hasPerm('admin'):
|
||||
print "This action requires admin privileges"
|
||||
|
|
@ -566,7 +566,7 @@ def handle_add_group(options, session, args):
|
|||
if not dsttag:
|
||||
print "Unknown tag: %s" % tag
|
||||
return 1
|
||||
|
||||
|
||||
groups = dict([(p['name'], p['group_id']) for p in session.getTagGroups(tag, inherit=False)])
|
||||
group_id = groups.get(group, None)
|
||||
if group_id is not None:
|
||||
|
|
@ -574,7 +574,7 @@ def handle_add_group(options, session, args):
|
|||
return 1
|
||||
|
||||
session.groupListAdd(tag, group)
|
||||
|
||||
|
||||
def handle_add_host(options, session, args):
|
||||
"[admin] Add a host"
|
||||
usage = _("usage: %prog add-host [options] hostname arch [arch2 ...]")
|
||||
|
|
@ -694,7 +694,7 @@ def handle_remove_host_from_channel(options, session, args):
|
|||
if channel not in hostchannels:
|
||||
print "Host %s is not a member of channel %s" % (host, channel)
|
||||
return 1
|
||||
|
||||
|
||||
session.removeHostFromChannel(host, channel)
|
||||
|
||||
def handle_remove_channel(options, session, args):
|
||||
|
|
@ -903,7 +903,7 @@ def handle_build(options, session, args):
|
|||
parser.error(_("Exactly two arguments (a build target and a SCM URL or srpm file) are required"))
|
||||
assert False
|
||||
if build_opts.arch_override and not build_opts.scratch:
|
||||
parser.error(_("--arch_override is only allowed for --scratch builds"))
|
||||
parser.error(_("--arch_override is only allowed for --scratch builds"))
|
||||
activate_session(session)
|
||||
target = args[0]
|
||||
if target.lower() == "none" and build_opts.repo_id:
|
||||
|
|
@ -987,7 +987,7 @@ def handle_chain_build(options, session, args):
|
|||
return 1
|
||||
|
||||
sources = args[1:]
|
||||
|
||||
|
||||
src_list = []
|
||||
build_level = []
|
||||
#src_lists is a list of lists of sources to build.
|
||||
|
|
@ -1017,7 +1017,7 @@ def handle_chain_build(options, session, args):
|
|||
if build_opts.background:
|
||||
#relative to koji.PRIO_DEFAULT
|
||||
priority = 5
|
||||
|
||||
|
||||
task_id = session.chainBuild(src_list, target, priority=priority)
|
||||
|
||||
print "Created task:", task_id
|
||||
|
|
@ -2534,7 +2534,7 @@ def anon_handle_latest_build(options, session, args):
|
|||
print "%-40s %-20s %s" % ("Build","Tag","Built by")
|
||||
print "%s %s %s" % ("-"*40, "-"*20, "-"*16)
|
||||
options.quiet = True
|
||||
|
||||
|
||||
output = [ fmt % x for x in data]
|
||||
output.sort()
|
||||
for line in output:
|
||||
|
|
@ -2927,7 +2927,7 @@ def anon_handle_list_hosts(options, session, args):
|
|||
for host in hosts:
|
||||
session.getLastHostUpdate(host['id'])
|
||||
updateList = session.multiCall()
|
||||
|
||||
|
||||
for host, [update] in zip(hosts, updateList):
|
||||
if update is None:
|
||||
host['update'] = '-'
|
||||
|
|
@ -3166,12 +3166,12 @@ def handle_clone_tag(options, session, args):
|
|||
activate_session(session)
|
||||
|
||||
if not session.hasPerm('admin') and not options.test:
|
||||
print "This action requires admin privileges"
|
||||
return
|
||||
print "This action requires admin privileges"
|
||||
return
|
||||
|
||||
if args[0] == args[1]:
|
||||
sys.stdout.write('Source and destination tags must be different.\n')
|
||||
return
|
||||
return
|
||||
# store tags.
|
||||
srctag = session.getTag(args[0])
|
||||
dsttag = session.getTag(args[1])
|
||||
|
|
@ -3433,7 +3433,7 @@ def handle_add_target(options, session, args):
|
|||
if not session.hasPerm('admin'):
|
||||
print "This action requires admin privileges"
|
||||
return 1
|
||||
|
||||
|
||||
chkbuildtag = session.getTag(build_tag)
|
||||
chkdesttag = session.getTag(dest_tag)
|
||||
if not chkbuildtag:
|
||||
|
|
@ -3445,7 +3445,7 @@ def handle_add_target(options, session, args):
|
|||
if not chkdesttag:
|
||||
print "Destination tag does not exist: %s" % dest_tag
|
||||
return 1
|
||||
|
||||
|
||||
session.createBuildTarget(name, build_tag, dest_tag)
|
||||
|
||||
def handle_edit_target(options, session, args):
|
||||
|
|
@ -3509,13 +3509,13 @@ def handle_remove_target(options, session, args):
|
|||
if not session.hasPerm('admin'):
|
||||
print "This action requires admin privileges"
|
||||
return
|
||||
|
||||
|
||||
target = args[0]
|
||||
target_info = session.getBuildTarget(target)
|
||||
if not target_info:
|
||||
print "Build target %s does not exist" % target
|
||||
return 1
|
||||
|
||||
|
||||
session.deleteBuildTarget(target_info['id'])
|
||||
|
||||
def handle_remove_tag(options, session, args):
|
||||
|
|
@ -4680,7 +4680,7 @@ def handle_edit_tag_inheritance(options, session, args):
|
|||
return 1
|
||||
print _("Error: Key constraints may be broken. Exiting.")
|
||||
return 1
|
||||
|
||||
|
||||
# len(data) == 1
|
||||
data = data[0]
|
||||
|
||||
|
|
@ -5101,7 +5101,7 @@ def handle_image_build_indirection(options, session, args):
|
|||
usage += _("\n %prog image-build --config FILE")
|
||||
usage += _("\n\n(Specify the --help global option for a list of other " +
|
||||
"help options)")
|
||||
parser = OptionParser(usage=usage)
|
||||
parser = OptionParser(usage=usage)
|
||||
parser.add_option("--config",
|
||||
help=_("Use a configuration file to define image-build options " +
|
||||
"instead of command line options (they will be ignored)."))
|
||||
|
|
@ -5151,19 +5151,19 @@ def _build_image_indirection(options, task_opts, session, args):
|
|||
"""
|
||||
|
||||
# Do some sanity checks before even attempting to create the session
|
||||
if not (bool(task_opts.utility_image_task) !=
|
||||
if not (bool(task_opts.utility_image_task) !=
|
||||
bool(task_opts.utility_image_build)):
|
||||
raise koji.GenericError, _("You must specify either a utility-image task or build ID/NVR")
|
||||
|
||||
if not (bool(task_opts.base_image_task) !=
|
||||
if not (bool(task_opts.base_image_task) !=
|
||||
bool(task_opts.base_image_build)):
|
||||
raise koji.GenericError, _("You must specify either a base-image task or build ID/NVR")
|
||||
|
||||
required_opts = [ 'name', 'version', 'arch', 'target', 'indirection_template', 'results_loc' ]
|
||||
optional_opts = [ 'indirection_template_url', 'scratch', 'utility_image_task', 'utility_image_build',
|
||||
'base_image_task', 'base_image_build', 'release', 'skip_tag' ]
|
||||
'base_image_task', 'base_image_build', 'release', 'skip_tag' ]
|
||||
|
||||
missing = [ ]
|
||||
missing = [ ]
|
||||
for opt in required_opts:
|
||||
if not getattr(task_opts, opt, None):
|
||||
missing.append(opt)
|
||||
|
|
@ -5238,11 +5238,10 @@ def _build_image_indirection(options, task_opts, session, args):
|
|||
# return
|
||||
|
||||
|
||||
|
||||
|
||||
def handle_image_build(options, session, args):
|
||||
"""Create a disk image given an install tree"""
|
||||
formats = ('vmdk', 'qcow', 'qcow2', 'vdi', 'rhevm-ova', 'vsphere-ova',
|
||||
formats = ('vmdk', 'qcow', 'qcow2', 'vdi', 'vpc', 'rhevm-ova',
|
||||
'vsphere-ova', 'vagrant-virtualbox', 'vagrant-libvirt',
|
||||
'docker', 'raw-xz')
|
||||
usage = _("usage: %prog image-build [options] <name> <version> " +
|
||||
"<target> <install-tree-url> <arch> [<arch>...]")
|
||||
|
|
@ -5909,7 +5908,7 @@ def handle_move_build(opts, session, args):
|
|||
activate_session(session)
|
||||
tasks = []
|
||||
builds = []
|
||||
|
||||
|
||||
if options.all:
|
||||
for arg in args[2:]:
|
||||
pkg = session.getPackage(arg)
|
||||
|
|
@ -5923,10 +5922,10 @@ def handle_move_build(opts, session, args):
|
|||
build = session.getBuild(arg)
|
||||
if not build:
|
||||
print _("Invalid build %s, skipping." % arg)
|
||||
continue
|
||||
continue
|
||||
if not build in builds:
|
||||
builds.append(build)
|
||||
|
||||
|
||||
for build in builds:
|
||||
task_id = session.moveBuild(args[0], args[1], build['id'], options.force)
|
||||
tasks.append(task_id)
|
||||
|
|
@ -6045,7 +6044,7 @@ def anon_handle_download_build(options, session, args):
|
|||
elif len(args) > 1:
|
||||
parser.error(_("Only a single package N-V-R or build ID may be specified"))
|
||||
assert False
|
||||
|
||||
|
||||
activate_session(session)
|
||||
build = args[0]
|
||||
|
||||
|
|
@ -6060,7 +6059,7 @@ def anon_handle_download_build(options, session, args):
|
|||
print "No associated builds for task %s" % build
|
||||
return 1
|
||||
build = builds[0]['build_id']
|
||||
|
||||
|
||||
if suboptions.latestfrom:
|
||||
# We want the latest build, not a specific build
|
||||
try:
|
||||
|
|
@ -6074,7 +6073,7 @@ def anon_handle_download_build(options, session, args):
|
|||
info = builds[0]
|
||||
else:
|
||||
info = session.getBuild(build)
|
||||
|
||||
|
||||
if info is None:
|
||||
print "No such build: %s" % build
|
||||
return 1
|
||||
|
|
@ -6134,7 +6133,7 @@ def anon_handle_download_build(options, session, args):
|
|||
pg = None
|
||||
else:
|
||||
pg = progress.TextMeter()
|
||||
|
||||
|
||||
for url, relpath in urls:
|
||||
file = grabber.urlopen(url, progress_obj=pg, text=relpath)
|
||||
|
||||
|
|
@ -6443,6 +6442,77 @@ def handle_moshimoshi(options, session, args):
|
|||
if u.get("krb_principal", None) is not None:
|
||||
print "Authenticated via Kerberos principal %s" % (u["krb_principal"])
|
||||
|
||||
|
||||
def handle_runroot(options, session, args):
|
||||
"[admin] Run a command in a buildroot"
|
||||
usage = _("usage: %prog runroot [options] <tag> <arch> <command>")
|
||||
usage += _("\n(Specify the --help global option for a list of other help options)")
|
||||
parser = OptionParser(usage=usage)
|
||||
parser.disable_interspersed_args()
|
||||
parser.add_option("-p", "--package", action="append", default=[], help=_("make sure this package is in the chroot"))
|
||||
parser.add_option("-m", "--mount", action="append", default=[], help=_("mount this directory read-write in the chroot"))
|
||||
parser.add_option("--skip-setarch", action="store_true", default=False,
|
||||
help=_("bypass normal setarch in the chroot"))
|
||||
parser.add_option("-w", "--weight", type='int', help=_("set task weight"))
|
||||
parser.add_option("--channel-override", help=_("use a non-standard channel"))
|
||||
parser.add_option("--task-id", action="store_true", default=False,
|
||||
help=_("Print the ID of the runroot task"))
|
||||
parser.add_option("--use-shell", action="store_true", default=False,
|
||||
help=_("Run command through a shell, otherwise uses exec"))
|
||||
parser.add_option("--repo-id", type="int", help=_("ID of the repo to use"))
|
||||
|
||||
(opts, args) = parser.parse_args(args)
|
||||
|
||||
if len(args) < 3:
|
||||
parser.error(_("Incorrect number of arguments"))
|
||||
activate_session(session)
|
||||
tag = args[0]
|
||||
arch = args[1]
|
||||
if opts.use_shell:
|
||||
# everything must be correctly quoted
|
||||
command = ' '.join(args[2:])
|
||||
else:
|
||||
command = args[2:]
|
||||
try:
|
||||
task_id = session.runroot(tag, arch, command,
|
||||
channel=opts.channel_override,
|
||||
packages=opts.package, mounts=opts.mount,
|
||||
repo_id=opts.repo_id,
|
||||
skip_setarch=opts.skip_setarch,
|
||||
weight=opts.weight)
|
||||
except koji.GenericError as e:
|
||||
if 'Invalid method' in str(e):
|
||||
print "* The runroot plugin appears to not be installed on the",
|
||||
print "koji hub. Please contact the administrator."
|
||||
raise
|
||||
if opts.task_id:
|
||||
print task_id
|
||||
|
||||
try:
|
||||
while True:
|
||||
# wait for the task to finish
|
||||
if session.taskFinished(task_id):
|
||||
break
|
||||
time.sleep(options.poll_interval)
|
||||
except KeyboardInterrupt:
|
||||
# this is probably the right thing to do here
|
||||
print "User interrupt: canceling runroot task"
|
||||
session.cancelTask(task_id)
|
||||
return
|
||||
output = None
|
||||
if "runroot.log" in session.listTaskOutput(task_id):
|
||||
output = session.downloadTaskOutput(task_id, "runroot.log")
|
||||
if output:
|
||||
sys.stdout.write(output)
|
||||
info = session.getTaskInfo(task_id)
|
||||
if info is None:
|
||||
sys.exit(1)
|
||||
state = koji.TASK_STATES[info['state']]
|
||||
if state in ('FAILED', 'CANCELED'):
|
||||
sys.exit(1)
|
||||
return
|
||||
|
||||
|
||||
def handle_help(options, session, args):
|
||||
"List available commands"
|
||||
usage = _("usage: %prog help [options]")
|
||||
|
|
|
|||
|
|
@ -190,6 +190,7 @@ INSERT INTO channels (name) VALUES ('maven');
|
|||
INSERT INTO channels (name) VALUES ('livecd');
|
||||
INSERT INTO channels (name) VALUES ('appliance');
|
||||
INSERT INTO channels (name) VALUES ('vm');
|
||||
INSERT INTO channels (name) VALUES ('image');
|
||||
|
||||
-- Here we track the build machines
|
||||
-- each host has an entry in the users table also
|
||||
|
|
@ -788,6 +789,8 @@ insert into archivetypes (name, description, extensions) values ('so', 'Shared l
|
|||
insert into archivetypes (name, description, extensions) values ('txt', 'Text file', 'txt');
|
||||
insert into archivetypes (name, description, extensions) values ('vhd', 'Hyper-V image', 'vhd');
|
||||
insert into archivetypes (name, description, extensions) values ('wsf', 'Windows script file', 'wsf');
|
||||
insert into archivetypes (name, description, extensions) values ('box', 'Vagrant Box Image', 'box');
|
||||
insert into archivetypes (name, description, extensions) values ('raw-xz', 'xz compressed raw disk image', 'raw.xz');
|
||||
|
||||
|
||||
-- Do we want to enforce a constraint that a build can only generate one
|
||||
|
|
|
|||
|
|
@ -29,7 +29,10 @@ Alias /kojihub /usr/share/koji-hub/kojixmlrpc.py
|
|||
Alias /kojifiles "/mnt/koji/"
|
||||
|
||||
<Directory "/mnt/koji">
|
||||
Options Indexes
|
||||
Options Indexes SymLinksIfOwnerMatch
|
||||
#If your top /mnt/koji directory is not owned by the httpd user, then
|
||||
#you will need to follow all symlinks instead, e.g.
|
||||
#Options Indexes FollowSymLinks
|
||||
AllowOverride None
|
||||
Require all granted
|
||||
#If you have httpd <= 2.2, you'll want the following two lines instead
|
||||
|
|
|
|||
208
hub/kojihub.py
208
hub/kojihub.py
|
|
@ -5,7 +5,7 @@
|
|||
#
|
||||
# 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;
|
||||
# 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,
|
||||
|
|
@ -41,7 +41,6 @@ from koji.util import md5_constructor
|
|||
from koji.util import sha1_constructor
|
||||
from koji.util import dslice
|
||||
import os
|
||||
import random
|
||||
import re
|
||||
import rpm
|
||||
import shutil
|
||||
|
|
@ -253,7 +252,7 @@ class Task(object):
|
|||
self.runCallbacks('postTaskStateChange', info, 'priority', priority)
|
||||
|
||||
if recurse:
|
||||
"""Change priority of child tasks"""
|
||||
# Change priority of child tasks
|
||||
q = """SELECT id FROM task WHERE parent = %(task_id)s"""
|
||||
for (child_id,) in _fetchMulti(q, locals()):
|
||||
Task(child_id).setPriority(priority, recurse=True)
|
||||
|
|
@ -709,8 +708,12 @@ def writeInheritanceData(tag_id, changes, clear=False):
|
|||
insert.make_create()
|
||||
insert.execute()
|
||||
|
||||
def readFullInheritance(tag_id,event=None,reverse=False,stops={},jumps={}):
|
||||
def readFullInheritance(tag_id,event=None,reverse=False,stops=None,jumps=None):
|
||||
"""Returns a list representing the full, ordered inheritance from tag"""
|
||||
if stops is None:
|
||||
stops = {}
|
||||
if jumps is None:
|
||||
jumps = {}
|
||||
order = []
|
||||
readFullInheritanceRecurse(tag_id,event,order,stops,{},{},0,None,False,[],reverse,jumps)
|
||||
return order
|
||||
|
|
@ -3449,8 +3452,8 @@ def list_rpms(buildID=None, buildrootID=None, imageID=None, componentBuildrootID
|
|||
|
||||
# image specific constraints
|
||||
if imageID != None:
|
||||
clauses.append('image_listing.image_id = %(imageID)i')
|
||||
joins.append('image_listing ON rpminfo.id = image_listing.rpm_id')
|
||||
clauses.append('image_listing.image_id = %(imageID)i')
|
||||
joins.append('image_listing ON rpminfo.id = image_listing.rpm_id')
|
||||
|
||||
if hostID != None:
|
||||
joins.append('standard_buildroot ON rpminfo.buildroot_id = standard_buildroot.id')
|
||||
|
|
@ -3560,7 +3563,7 @@ def list_archives(buildID=None, buildrootID=None, imageID=None, componentBuildro
|
|||
checksum_type: the checksum type (integer)
|
||||
|
||||
If componentBuildrootID is specified, then the map will also contain the following key:
|
||||
project: whether the archive was pulled in as a project dependency, or as part of the
|
||||
project: whether the archive was pulled in as a project dependency, or as part of the
|
||||
build environment setup (boolean)
|
||||
|
||||
If 'type' is specified, then the archives listed will be limited
|
||||
|
|
@ -3597,7 +3600,7 @@ def list_archives(buildID=None, buildrootID=None, imageID=None, componentBuildro
|
|||
an empty list is returned.
|
||||
"""
|
||||
values = {}
|
||||
|
||||
|
||||
tables = ['archiveinfo']
|
||||
joins = ['archivetypes on archiveinfo.type_id = archivetypes.id']
|
||||
fields = [('archiveinfo.id', 'id'),
|
||||
|
|
@ -3906,8 +3909,8 @@ def get_archive_file(archive_id, filename):
|
|||
for file_info in files:
|
||||
if file_info['name'] == filename:
|
||||
return file_info
|
||||
else:
|
||||
return None
|
||||
#otherwise
|
||||
return None
|
||||
|
||||
def list_task_output(taskID, stat=False):
|
||||
"""List the files generated by the task with the given ID. This
|
||||
|
|
@ -4735,6 +4738,119 @@ def _import_wrapper(task_id, build_info, rpm_results):
|
|||
import_build_log(os.path.join(rpm_task_dir, log),
|
||||
build_info, subdir='noarch')
|
||||
|
||||
def merge_scratch(task_id):
|
||||
"""Import rpms from a scratch build into an existing build, retaining
|
||||
buildroot metadata and build logs."""
|
||||
task = Task(task_id)
|
||||
try:
|
||||
task_info = task.getInfo(request=True)
|
||||
except koji.GenericError:
|
||||
raise koji.ImportError, 'invalid task: %s' % task_id
|
||||
if task_info['state'] != koji.TASK_STATES['CLOSED']:
|
||||
raise koji.ImportError, 'task %s did not complete successfully' % task_id
|
||||
if task_info['method'] != 'build':
|
||||
raise koji.ImportError, 'task %s is not a build task' % task_id
|
||||
if len(task_info['request']) < 3 or not task_info['request'][2].get('scratch'):
|
||||
raise koji.ImportError, 'task %s is not a scratch build' % task_id
|
||||
|
||||
# sanity check the task, and extract data required for import
|
||||
srpm = None
|
||||
tasks = {}
|
||||
for child in task.getChildren():
|
||||
if child['method'] != 'buildArch':
|
||||
continue
|
||||
info = {'rpms': [],
|
||||
'logs': []}
|
||||
for output in list_task_output(child['id']):
|
||||
if output.endswith('.src.rpm'):
|
||||
srpm_name = os.path.basename(output)
|
||||
if not srpm:
|
||||
srpm = srpm_name
|
||||
else:
|
||||
if srpm != srpm_name:
|
||||
raise koji.ImportError, 'task srpm names do not match: %s, %s' % \
|
||||
(srpm, srpm_name)
|
||||
elif output.endswith('.noarch.rpm'):
|
||||
continue
|
||||
elif output.endswith('.rpm'):
|
||||
rpminfo = koji.parse_NVRA(os.path.basename(output))
|
||||
if 'arch' not in info:
|
||||
info['arch'] = rpminfo['arch']
|
||||
elif info['arch'] != rpminfo['arch']:
|
||||
raise koji.ImportError, 'multiple arches generated by task %s: %s, %s' % \
|
||||
(child['id'], info['arch'], rpminfo['arch'])
|
||||
info['rpms'].append(output)
|
||||
elif output.endswith('.log'):
|
||||
info['logs'].append(output)
|
||||
if not info['rpms']:
|
||||
continue
|
||||
if not info['logs']:
|
||||
raise koji.ImportError, 'task %s is missing logs' % child['id']
|
||||
buildroots = query_buildroots(taskID=child['id'],
|
||||
queryOpts={'order': '-id', 'limit': 1})
|
||||
if not buildroots:
|
||||
raise koji.ImportError, 'no buildroot associated with task %s' % child['id']
|
||||
info['buildroot_id'] = buildroots[0]['id']
|
||||
tasks[child['id']] = info
|
||||
if not tasks:
|
||||
raise koji.ImportError, 'nothing to do for task %s' % task_id
|
||||
|
||||
# sanity check the build
|
||||
build_nvr = koji.parse_NVRA(srpm)
|
||||
build = get_build(build_nvr)
|
||||
if not build:
|
||||
raise koji.ImportError, 'no such build: %(name)s-%(version)s-%(release)s' % \
|
||||
build_nvr
|
||||
if build['state'] != koji.BUILD_STATES['COMPLETE']:
|
||||
raise koji.ImportError, '%s did not complete successfully' % build['nvr']
|
||||
if not build['task_id']:
|
||||
raise koji.ImportError, 'no task for %s' % build['nvr']
|
||||
build_task_info = Task(build['task_id']).getInfo(request=True)
|
||||
# Intentionally skip checking the build task state.
|
||||
# There are cases where the build can be valid even though the task has failed,
|
||||
# e.g. tagging failures.
|
||||
|
||||
# compare the task and build and make sure they are compatible with importing
|
||||
if task_info['request'][0] != build_task_info['request'][0]:
|
||||
raise koji.ImportError, 'SCM URLs for the task and build do not match: %s, %s' % \
|
||||
(task_info['request'][0], build_task_info['request'][0])
|
||||
build_arches = set()
|
||||
for rpm in list_rpms(buildID=build['id']):
|
||||
if rpm['arch'] == 'src':
|
||||
build_srpm = '%s.src.rpm' % rpm['nvr']
|
||||
if srpm != build_srpm:
|
||||
raise koji.ImportError, 'task and build srpm names do not match: %s, %s' % \
|
||||
(srpm, build_srpm)
|
||||
elif rpm['arch'] == 'noarch':
|
||||
continue
|
||||
else:
|
||||
build_arches.add(rpm['arch'])
|
||||
if not build_arches:
|
||||
raise koji.ImportError, 'no arch-specific rpms found for %s' % build['nvr']
|
||||
task_arches = set([t['arch'] for t in tasks.values()])
|
||||
overlapping_arches = task_arches.intersection(build_arches)
|
||||
if overlapping_arches:
|
||||
raise koji.ImportError, 'task %s and %s produce rpms with the same arches: %s' % \
|
||||
(task_info['id'], build['nvr'], ', '.join(overlapping_arches))
|
||||
|
||||
# everything looks good, do the import
|
||||
for task_id, info in tasks.items():
|
||||
taskpath = koji.pathinfo.task(task_id)
|
||||
for filename in info['rpms']:
|
||||
filepath = os.path.realpath(os.path.join(taskpath, filename))
|
||||
rpminfo = import_rpm(filepath, build, info['buildroot_id'])
|
||||
import_rpm_file(filepath, build, rpminfo)
|
||||
add_rpm_sig(rpminfo['id'], koji.rip_rpm_sighdr(filepath))
|
||||
for logname in info['logs']:
|
||||
logpath = os.path.realpath(os.path.join(taskpath, logname))
|
||||
import_build_log(logpath, build, subdir=info['arch'])
|
||||
|
||||
# flag tags whose content has changed, so relevant repos can be regen'ed
|
||||
for tag in list_tags(build=build['id']):
|
||||
set_tag_update(tag['id'], 'IMPORT')
|
||||
|
||||
return build['id']
|
||||
|
||||
def get_archive_types():
|
||||
"""Return a list of all supported archivetypes"""
|
||||
select = """SELECT id, name, description, extensions FROM archivetypes
|
||||
|
|
@ -4779,11 +4895,11 @@ def get_archive_type(filename=None, type_name=None, type_id=None, strict=False):
|
|||
elif len(results) > 1:
|
||||
# this should never happen, and is a misconfiguration in the database
|
||||
raise koji.GenericError, 'multiple matches for file extension: %s' % ext
|
||||
#otherwise
|
||||
if strict:
|
||||
raise koji.GenericError, 'unsupported file extension: %s' % ext
|
||||
else:
|
||||
if strict:
|
||||
raise koji.GenericError, 'unsupported file extension: %s' % ext
|
||||
else:
|
||||
return None
|
||||
return None
|
||||
|
||||
def new_maven_build(build, maven_info):
|
||||
"""
|
||||
|
|
@ -4855,7 +4971,7 @@ def old_image_data(old_image_id):
|
|||
ret = query.executeOne()
|
||||
|
||||
if not ret:
|
||||
raise koji.GenericError, 'no old image with ID: %i' % imageID
|
||||
raise koji.GenericError, 'no old image with ID: %i' % old_image_id
|
||||
return ret
|
||||
|
||||
def check_old_image_files(old):
|
||||
|
|
@ -5992,7 +6108,7 @@ def get_notification_recipients(build, tag_id, state):
|
|||
for this tag and the user who submitted the build. The list will not contain
|
||||
duplicates.
|
||||
"""
|
||||
|
||||
|
||||
clauses = []
|
||||
|
||||
if build:
|
||||
|
|
@ -7413,7 +7529,7 @@ class RootExports(object):
|
|||
elif md5sum is None:
|
||||
verify = None
|
||||
else:
|
||||
verify, digest = info
|
||||
verify, digest = md5sum
|
||||
sum_cls = get_verify_class(verify)
|
||||
if offset != -1:
|
||||
if size is not None:
|
||||
|
|
@ -7585,7 +7701,7 @@ class RootExports(object):
|
|||
"""
|
||||
Import an archive file and associate it with a build. The archive can
|
||||
be any non-rpm filetype supported by Koji.
|
||||
|
||||
|
||||
filepath: path to the archive file (relative to the Koji workdir)
|
||||
buildinfo: information about the build to associate the archive with
|
||||
May be a string (NVR), integer (buildID), or dict (containing keys: name, version, release)
|
||||
|
|
@ -7603,7 +7719,7 @@ class RootExports(object):
|
|||
elif type == 'image':
|
||||
context.session.assertPerm('image-import')
|
||||
else:
|
||||
koji.GenericError, 'unsupported archive type: %s' % type
|
||||
raise koji.GenericError, 'unsupported archive type: %s' % type
|
||||
buildinfo = get_build(buildinfo, strict=True)
|
||||
fullpath = '%s/%s' % (koji.pathinfo.work(), filepath)
|
||||
import_archive(fullpath, buildinfo, type, typeInfo)
|
||||
|
|
@ -7690,6 +7806,36 @@ class RootExports(object):
|
|||
for tag in list_tags(build=rpminfo['build_id']):
|
||||
set_tag_update(tag['id'], 'IMPORT')
|
||||
|
||||
def mergeScratch(self, task_id):
|
||||
"""Import the rpms generated by a scratch build, and associate
|
||||
them with an existing build.
|
||||
|
||||
To be eligible for import, the build must:
|
||||
- be successfully completed
|
||||
- contain at least one arch-specific rpm
|
||||
|
||||
The task must:
|
||||
- be a 'build' task
|
||||
- be successfully completed
|
||||
- use the exact same SCM URL as the build
|
||||
- contain at least one arch-specific rpm
|
||||
- have no overlap between the arches of the rpms it contains and
|
||||
the rpms contained by the build
|
||||
- contain a .src.rpm whose filename exactly matches the .src.rpm
|
||||
of the build
|
||||
|
||||
Only arch-specific rpms will be imported. noarch rpms and the src
|
||||
rpm will be skipped. Build logs and buildroot metadata from the
|
||||
scratch build will be imported along with the rpms.
|
||||
|
||||
This is useful for bootstrapping a new arch. RPMs can be built
|
||||
for the new arch using a scratch build and then merged into an
|
||||
existing build, incrementally expanding arch coverage and avoiding
|
||||
the need for a mass-rebuild to support the new arch.
|
||||
"""
|
||||
context.session.assertPerm('admin')
|
||||
return merge_scratch(task_id)
|
||||
|
||||
def addExternalRPM(self, rpminfo, external_repo, strict=True):
|
||||
"""Import an external RPM
|
||||
|
||||
|
|
@ -8272,7 +8418,11 @@ class RootExports(object):
|
|||
context.session.assertPerm('admin')
|
||||
return writeInheritanceData(tag,data,clear=clear)
|
||||
|
||||
def getFullInheritance(self,tag,event=None,reverse=False,stops={},jumps={}):
|
||||
def getFullInheritance(self,tag,event=None,reverse=False,stops=None,jumps=None):
|
||||
if stops is None:
|
||||
stops = {}
|
||||
if jumps is None:
|
||||
jumps = {}
|
||||
if not isinstance(tag,int):
|
||||
#lookup tag id
|
||||
tag = get_tag_id(tag,strict=True)
|
||||
|
|
@ -8600,7 +8750,7 @@ class RootExports(object):
|
|||
raise koji.GenericError, 'user already exists: %s' % username
|
||||
if krb_principal and get_user(krb_principal):
|
||||
raise koji.GenericError, 'user with this Kerberos principal already exists: %s' % krb_principal
|
||||
|
||||
|
||||
return context.session.createUser(username, status=status, krb_principal=krb_principal)
|
||||
|
||||
def enableUser(self, username):
|
||||
|
|
@ -8609,14 +8759,14 @@ class RootExports(object):
|
|||
if not user:
|
||||
raise koji.GenericError, 'unknown user: %s' % username
|
||||
set_user_status(user, koji.USER_STATUS['NORMAL'])
|
||||
|
||||
|
||||
def disableUser(self, username):
|
||||
"""Disable logins by the specified user"""
|
||||
user = get_user(username)
|
||||
if not user:
|
||||
raise koji.GenericError, 'unknown user: %s' % username
|
||||
set_user_status(user, koji.USER_STATUS['BLOCKED'])
|
||||
|
||||
|
||||
#group management calls
|
||||
newGroup = staticmethod(new_group)
|
||||
addGroupMember = staticmethod(add_group_member)
|
||||
|
|
@ -9266,11 +9416,11 @@ class RootExports(object):
|
|||
notificationUser = self.getUser(user_id)
|
||||
if not notificationUser:
|
||||
raise koji.GenericError, 'invalid user ID: %s' % user_id
|
||||
|
||||
|
||||
if not (notificationUser['id'] == currentUser['id'] or self.hasPerm('admin')):
|
||||
raise koji.GenericError, 'user %s cannot create notifications for user %s' % \
|
||||
(currentUser['name'], notificationUser['name'])
|
||||
|
||||
|
||||
email = '%s@%s' % (notificationUser['name'], context.opts['EmailDomain'])
|
||||
insert = """INSERT INTO build_notifications
|
||||
(user_id, package_id, tag_id, success_only, email)
|
||||
|
|
@ -9297,7 +9447,7 @@ class RootExports(object):
|
|||
_dml(delete, locals())
|
||||
|
||||
def _prepareSearchTerms(self, terms, matchType):
|
||||
"""Process the search terms before passing them to the database.
|
||||
r"""Process the search terms before passing them to the database.
|
||||
If matchType is "glob", "_" will be replaced with "\_" (to match literal
|
||||
underscores), "?" will be replaced with "_", and "*" will
|
||||
be replaced with "%". If matchType is "regexp", no changes will be
|
||||
|
|
@ -10137,7 +10287,7 @@ class HostExports(object):
|
|||
if len(poms) == 0:
|
||||
pass
|
||||
elif len(poms) == 1:
|
||||
# This directory has a .pom file, so get the Maven group_id,
|
||||
# This directory has a .pom file, so get the Maven group_id,
|
||||
# artifact_id, and version from it and associate those with
|
||||
# the artifacts in this directory
|
||||
pom_path = os.path.join(maven_task_dir, relpath, poms[0])
|
||||
|
|
@ -10202,7 +10352,7 @@ class HostExports(object):
|
|||
if not context.opts.get('EnableWin'):
|
||||
raise koji.GenericError, 'Windows support not enabled'
|
||||
else:
|
||||
koji.GenericError, 'unsupported archive type: %s' % type
|
||||
raise koji.GenericError, 'unsupported archive type: %s' % type
|
||||
import_archive(filepath, buildinfo, type, typeInfo)
|
||||
|
||||
def importWrapperRPMs(self, task_id, build_id, rpm_results):
|
||||
|
|
@ -10378,7 +10528,7 @@ class HostExports(object):
|
|||
|
||||
def importImage(self, task_id, build_id, results):
|
||||
"""
|
||||
Import a built image, populating the database with metadata and
|
||||
Import a built image, populating the database with metadata and
|
||||
moving the image to its final location.
|
||||
"""
|
||||
for sub_results in results.values():
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
#
|
||||
# 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;
|
||||
# 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,
|
||||
|
|
@ -192,7 +192,7 @@ class HandlerAccess(object):
|
|||
return self.__reg.get(__name)(*args, **kwargs)
|
||||
|
||||
def get(self, name):
|
||||
return self.__Reg.get(name)
|
||||
return self.__reg.get(name)
|
||||
|
||||
|
||||
class ModXMLRPCRequestHandler(object):
|
||||
|
|
@ -292,7 +292,7 @@ class ModXMLRPCRequestHandler(object):
|
|||
if context.opts.get('LockOut') and \
|
||||
context.method not in ('login', 'krbLogin', 'sslLogin', 'logout') and \
|
||||
not context.session.hasPerm('admin'):
|
||||
raise koji.ServerOffline, "Server disabled for maintenance"
|
||||
raise koji.ServerOffline, "Server disabled for maintenance"
|
||||
|
||||
def _dispatch(self, method, params):
|
||||
func = self._get_handler(method)
|
||||
|
|
@ -569,7 +569,7 @@ def get_policy(opts, plugins):
|
|||
if pname != test.policy:
|
||||
continue
|
||||
elif pname not in test.policy:
|
||||
continue
|
||||
continue
|
||||
# in case of name overlap, last one wins
|
||||
# hence plugins can override builtin tests
|
||||
merged[name] = test
|
||||
|
|
|
|||
85
koji.spec
85
koji.spec
|
|
@ -1,5 +1,12 @@
|
|||
%{!?python_sitelib: %define python_sitelib %(%{__python} -c "from distutils.sysconfig import get_python_lib; print get_python_lib()")}
|
||||
|
||||
%if 0%{?fedora} >= 21 || 0%{?redhat} >= 7
|
||||
%global use_systemd 1
|
||||
%else
|
||||
%global use_systemd 0
|
||||
%global install_opt TYPE=sysv
|
||||
%endif
|
||||
|
||||
%define baserelease 1
|
||||
#build with --define 'testbuild 1' to have a timestamp appended to release
|
||||
%if "x%{?testbuild}" == "x1"
|
||||
|
|
@ -23,6 +30,10 @@ Requires: rpm-python
|
|||
Requires: pyOpenSSL
|
||||
Requires: python-urlgrabber
|
||||
BuildRequires: python
|
||||
%if %{use_systemd}
|
||||
BuildRequires: systemd
|
||||
BuildRequires: pkgconfig
|
||||
%endif
|
||||
|
||||
%description
|
||||
Koji is a system for building and tracking RPMS. The base package
|
||||
|
|
@ -63,11 +74,17 @@ License: LGPLv2 and GPLv2+
|
|||
#mergerepos (from createrepo) is GPLv2+
|
||||
Requires: %{name} = %{version}-%{release}
|
||||
Requires: mock >= 0.9.14
|
||||
Requires(pre): /usr/sbin/useradd
|
||||
%if %{use_systemd}
|
||||
Requires(post): systemd
|
||||
Requires(preun): systemd
|
||||
Requires(postun): systemd
|
||||
%else
|
||||
Requires(post): /sbin/chkconfig
|
||||
Requires(post): /sbin/service
|
||||
Requires(preun): /sbin/chkconfig
|
||||
Requires(preun): /sbin/service
|
||||
Requires(pre): /usr/sbin/useradd
|
||||
%endif
|
||||
Requires: /usr/bin/cvs
|
||||
Requires: /usr/bin/svn
|
||||
Requires: /usr/bin/git
|
||||
|
|
@ -90,10 +107,16 @@ Summary: Koji virtual machine management daemon
|
|||
Group: Applications/System
|
||||
License: LGPLv2
|
||||
Requires: %{name} = %{version}-%{release}
|
||||
%if %{use_systemd}
|
||||
Requires(post): systemd
|
||||
Requires(preun): systemd
|
||||
Requires(postun): systemd
|
||||
%else
|
||||
Requires(post): /sbin/chkconfig
|
||||
Requires(post): /sbin/service
|
||||
Requires(preun): /sbin/chkconfig
|
||||
Requires(preun): /sbin/service
|
||||
%endif
|
||||
Requires: libvirt-python
|
||||
Requires: libxml2-python
|
||||
Requires: /usr/bin/virt-clone
|
||||
|
|
@ -109,6 +132,11 @@ Group: Applications/Internet
|
|||
License: LGPLv2
|
||||
Requires: postgresql-python
|
||||
Requires: %{name} = %{version}-%{release}
|
||||
%if %{use_systemd}
|
||||
Requires(post): systemd
|
||||
Requires(preun): systemd
|
||||
Requires(postun): systemd
|
||||
%endif
|
||||
|
||||
%description utils
|
||||
Utilities for the Koji system
|
||||
|
|
@ -135,7 +163,7 @@ koji-web is a web UI to the Koji system.
|
|||
|
||||
%install
|
||||
rm -rf $RPM_BUILD_ROOT
|
||||
make DESTDIR=$RPM_BUILD_ROOT install
|
||||
make DESTDIR=$RPM_BUILD_ROOT %{?install_opt} install
|
||||
|
||||
%clean
|
||||
rm -rf $RPM_BUILD_ROOT
|
||||
|
|
@ -168,8 +196,12 @@ rm -rf $RPM_BUILD_ROOT
|
|||
%files utils
|
||||
%defattr(-,root,root)
|
||||
%{_sbindir}/kojira
|
||||
%if %{use_systemd}
|
||||
%{_unitdir}/kojira.service
|
||||
%else
|
||||
%{_initrddir}/kojira
|
||||
%config(noreplace) %{_sysconfdir}/sysconfig/kojira
|
||||
%endif
|
||||
%dir %{_sysconfdir}/kojira
|
||||
%config(noreplace) %{_sysconfdir}/kojira/kojira.conf
|
||||
%{_sbindir}/koji-gc
|
||||
|
|
@ -192,8 +224,12 @@ rm -rf $RPM_BUILD_ROOT
|
|||
%{_sbindir}/kojid
|
||||
%dir %{_libexecdir}/kojid
|
||||
%{_libexecdir}/kojid/mergerepos
|
||||
%if %{use_systemd}
|
||||
%{_unitdir}/kojid.service
|
||||
%else
|
||||
%{_initrddir}/kojid
|
||||
%config(noreplace) %{_sysconfdir}/sysconfig/kojid
|
||||
%endif
|
||||
%dir %{_sysconfdir}/kojid
|
||||
%config(noreplace) %{_sysconfdir}/kojid/kojid.conf
|
||||
%attr(-,kojibuilder,kojibuilder) %{_sysconfdir}/mock/koji
|
||||
|
|
@ -201,6 +237,19 @@ rm -rf $RPM_BUILD_ROOT
|
|||
%pre builder
|
||||
/usr/sbin/useradd -r -s /bin/bash -G mock -d /builddir -M kojibuilder 2>/dev/null ||:
|
||||
|
||||
%if %{use_systemd}
|
||||
|
||||
%post builder
|
||||
%systemd_post kojid.service
|
||||
|
||||
%preun builder
|
||||
%systemd_preun kojid.service
|
||||
|
||||
%postun builder
|
||||
%systemd_postun kojid.service
|
||||
|
||||
%else
|
||||
|
||||
%post builder
|
||||
/sbin/chkconfig --add kojid
|
||||
|
||||
|
|
@ -209,17 +258,35 @@ if [ $1 = 0 ]; then
|
|||
/sbin/service kojid stop &> /dev/null
|
||||
/sbin/chkconfig --del kojid
|
||||
fi
|
||||
%endif
|
||||
|
||||
%files vm
|
||||
%defattr(-,root,root)
|
||||
%{_sbindir}/kojivmd
|
||||
#dir %{_datadir}/kojivmd
|
||||
%{_datadir}/kojivmd/kojikamid
|
||||
%if %{use_systemd}
|
||||
%{_unitdir}/kojivmd.service
|
||||
%else
|
||||
%{_initrddir}/kojivmd
|
||||
%config(noreplace) %{_sysconfdir}/sysconfig/kojivmd
|
||||
%endif
|
||||
%dir %{_sysconfdir}/kojivmd
|
||||
%config(noreplace) %{_sysconfdir}/kojivmd/kojivmd.conf
|
||||
|
||||
%if %{use_systemd}
|
||||
|
||||
%post vm
|
||||
%systemd_post kojivmd.service
|
||||
|
||||
%preun vm
|
||||
%systemd_preun kojivmd.service
|
||||
|
||||
%postun vm
|
||||
%systemd_postun kojivmd.service
|
||||
|
||||
%else
|
||||
|
||||
%post vm
|
||||
/sbin/chkconfig --add kojivmd
|
||||
|
||||
|
|
@ -228,7 +295,20 @@ if [ $1 = 0 ]; then
|
|||
/sbin/service kojivmd stop &> /dev/null
|
||||
/sbin/chkconfig --del kojivmd
|
||||
fi
|
||||
%endif
|
||||
|
||||
%if %{use_systemd}
|
||||
|
||||
%post utils
|
||||
%systemd_post kojira.service
|
||||
|
||||
%preun utils
|
||||
%systemd_preun kojira.service
|
||||
|
||||
%postun utils
|
||||
%systemd_postun kojira.service
|
||||
|
||||
%else
|
||||
%post utils
|
||||
/sbin/chkconfig --add kojira
|
||||
/sbin/service kojira condrestart &> /dev/null || :
|
||||
|
|
@ -237,6 +317,7 @@ if [ $1 = 0 ]; then
|
|||
/sbin/service kojira stop &> /dev/null || :
|
||||
/sbin/chkconfig --del kojira
|
||||
fi
|
||||
%endif
|
||||
|
||||
%changelog
|
||||
* Mon Mar 24 2014 Mike McLean <mikem at redhat.com> - 1.9.0-1
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
#
|
||||
# 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;
|
||||
# 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,
|
||||
|
|
@ -328,6 +328,9 @@ class ParameterError(GenericError):
|
|||
"""Raised when an rpc call receives incorrect arguments"""
|
||||
faultCode = 1019
|
||||
|
||||
class ImportError(GenericError):
|
||||
"""Raised when an import fails"""
|
||||
faultCode = 1020
|
||||
|
||||
class MultiCallInProgress(object):
|
||||
"""
|
||||
|
|
@ -992,7 +995,7 @@ def parse_pom(path=None, contents=None):
|
|||
raise GenericError, 'either a path to a pom file or the contents of a pom file must be specified'
|
||||
|
||||
# A common problem is non-UTF8 characters in XML files, so we'll convert the string first
|
||||
|
||||
|
||||
contents = fixEncoding(contents)
|
||||
|
||||
try:
|
||||
|
|
@ -1953,7 +1956,7 @@ class ClientSession(object):
|
|||
if self.logger.isEnabledFor(logging.DEBUG):
|
||||
tb_str = ''.join(traceback.format_exception(*sys.exc_info()))
|
||||
self.logger.debug(tb_str)
|
||||
self.logger.info("Try #%d for call %d (%s) failed: %s", tries, self.callnum, name, e)
|
||||
self.logger.info("Try #%s for call %s (%s) failed: %s", tries, self.callnum, name, e)
|
||||
if tries > 1:
|
||||
# first retry is immediate, after that we honor retry_interval
|
||||
time.sleep(interval)
|
||||
|
|
@ -2039,10 +2042,10 @@ class ClientSession(object):
|
|||
chk_opts['verify'] = 'adler32'
|
||||
result = self._callMethod('checkUpload', (path, name), chk_opts)
|
||||
if int(result['size']) != ofs:
|
||||
raise koji.GenericError, "Uploaded file is wrong length: %s/%s, %s != %s" \
|
||||
raise GenericError, "Uploaded file is wrong length: %s/%s, %s != %s" \
|
||||
% (path, name, result['sumlength'], ofs)
|
||||
if problems and result['hexdigest'] != full_chksum.hexdigest():
|
||||
raise koji.GenericError, "Uploaded file has wrong checksum: %s/%s, %s != %s" \
|
||||
raise GenericError, "Uploaded file has wrong checksum: %s/%s, %s != %s" \
|
||||
% (path, name, result['hexdigest'], full_chksum.hexdigest())
|
||||
self.logger.debug("Fast upload: %s complete. %i bytes in %.1f seconds", localfile, size, t2)
|
||||
|
||||
|
|
|
|||
10
koji/auth.py
10
koji/auth.py
|
|
@ -3,7 +3,7 @@
|
|||
#
|
||||
# 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;
|
||||
# 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,
|
||||
|
|
@ -247,7 +247,7 @@ class Session(object):
|
|||
if not result:
|
||||
raise koji.AuthError, 'invalid user_id: %s' % user_id
|
||||
name, usertype, status = result
|
||||
|
||||
|
||||
if status != koji.USER_STATUS['NORMAL']:
|
||||
raise koji.AuthError, 'logins by %s are not allowed' % name
|
||||
|
||||
|
|
@ -394,7 +394,7 @@ class Session(object):
|
|||
raise koji.AuthError, '%s is not authorized to login other users' % client_dn
|
||||
else:
|
||||
username = client_name
|
||||
|
||||
|
||||
cursor = context.cnx.cursor()
|
||||
query = """SELECT id FROM users
|
||||
WHERE name = %(username)s"""
|
||||
|
|
@ -596,7 +596,7 @@ class Session(object):
|
|||
"""
|
||||
if not name:
|
||||
raise koji.GenericError, 'a user must have a non-empty name'
|
||||
|
||||
|
||||
if usertype == None:
|
||||
usertype = koji.USERTYPES['NORMAL']
|
||||
elif not koji.USERTYPES.get(usertype):
|
||||
|
|
@ -606,7 +606,7 @@ class Session(object):
|
|||
status = koji.USER_STATUS['NORMAL']
|
||||
elif not koji.USER_STATUS.get(status):
|
||||
raise koji.GenericError, 'invalid status: %s' % status
|
||||
|
||||
|
||||
cursor = context.cnx.cursor()
|
||||
select = """SELECT nextval('users_id_seq')"""
|
||||
cursor.execute(select, locals())
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
#
|
||||
# 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;
|
||||
# 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,
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
#
|
||||
# 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;
|
||||
# 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,
|
||||
|
|
@ -266,7 +266,7 @@ class SCM(object):
|
|||
|
||||
# check for validity: params should be empty, query may be empty, everything else should be populated
|
||||
if params :
|
||||
raise koji.GenericError, 'Unable to parse SCM URL: %s . Param element %s should be empty.' % (self.url,param)
|
||||
raise koji.GenericError, 'Unable to parse SCM URL: %s . Params element %s should be empty.' % (self.url, params)
|
||||
if not scheme :
|
||||
raise koji.GenericError, 'Unable to parse SCM URL: %s . Could not find the scheme element.' % self.url
|
||||
if not netloc :
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
#
|
||||
# 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;
|
||||
# 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,
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
#
|
||||
# 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;
|
||||
# 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,
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
#
|
||||
# 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;
|
||||
# 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,
|
||||
|
|
@ -368,4 +368,3 @@ def findSimpleTests(namespace):
|
|||
ret.setdefault(name, value)
|
||||
#...so first test wins in case of name overlap
|
||||
return ret
|
||||
|
||||
|
|
|
|||
|
|
@ -158,7 +158,7 @@ class WSGIWrapper(object):
|
|||
for chunk in result:
|
||||
if chunk and not self.set_headers:
|
||||
raise RuntimeError, "write() called before start_response()"
|
||||
write(data)
|
||||
write(chunk)
|
||||
if not req.bytes_sent:
|
||||
#application sent nothing back
|
||||
req.set_content_length(0)
|
||||
|
|
@ -187,4 +187,3 @@ class InputWrapper(object):
|
|||
while line:
|
||||
yield line
|
||||
line = self.readline()
|
||||
|
||||
|
|
|
|||
|
|
@ -139,4 +139,3 @@ class PlgHTTPS(httplib.HTTP):
|
|||
|
||||
def __init__(self, host='', port=None, ssl_context=None, strict=None, timeout=None):
|
||||
self._setup(self._connection_class(host, port, ssl_context, strict, timeout))
|
||||
|
||||
|
|
|
|||
|
|
@ -156,4 +156,3 @@ class PlgFileObject(socket._fileobject):
|
|||
self._sock.close()
|
||||
finally:
|
||||
self._sock = None
|
||||
|
||||
|
|
|
|||
|
|
@ -176,4 +176,3 @@ if __name__ == '__main__':
|
|||
except KeyboardInterrupt:
|
||||
os._exit(0)
|
||||
print "All done. (%d timed out)" % tm.get()
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
#
|
||||
# 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;
|
||||
# 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,
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
#
|
||||
# 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;
|
||||
# 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,
|
||||
|
|
|
|||
|
|
@ -22,19 +22,19 @@ session = None
|
|||
target = None
|
||||
|
||||
def connect_timeout(host, port, timeout):
|
||||
for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM):
|
||||
af, socktype, proto, canonname, sa = res
|
||||
sock = socket.socket(af, socktype, proto)
|
||||
sock.settimeout(timeout)
|
||||
try:
|
||||
sock.connect(sa)
|
||||
break
|
||||
except socket.error, msg:
|
||||
sock.close()
|
||||
else:
|
||||
# If we got here then we couldn't connect (yet)
|
||||
raise
|
||||
return sock
|
||||
for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM):
|
||||
af, socktype, proto, canonname, sa = res
|
||||
sock = socket.socket(af, socktype, proto)
|
||||
sock.settimeout(timeout)
|
||||
try:
|
||||
sock.connect(sa)
|
||||
break
|
||||
except socket.error, msg:
|
||||
sock.close()
|
||||
else:
|
||||
# If we got here then we couldn't connect (yet)
|
||||
raise
|
||||
return sock
|
||||
|
||||
class tlstimeout(qpid.messaging.transports.tls):
|
||||
def __init__(self, conn, host, port):
|
||||
|
|
@ -168,6 +168,8 @@ def get_message_headers(msgtype, *args, **kws):
|
|||
headers['tag'] = kws['tag']['name']
|
||||
headers['package'] = kws['package']['name']
|
||||
elif msgtype == 'TaskStateChange':
|
||||
headers['id'] = kws['info']['id']
|
||||
headers['parent'] = kws['info']['parent']
|
||||
headers['method'] = kws['info']['method']
|
||||
headers['attribute'] = kws['attribute']
|
||||
headers['old'] = kws['old']
|
||||
|
|
|
|||
|
|
@ -319,5 +319,3 @@ class RunRootTask(tasks.BaseTaskHandler):
|
|||
os.unlink(fn)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
|
|
|
|||
61
plugins/runroot_hub.py
Normal file
61
plugins/runroot_hub.py
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
#koji hub plugin
|
||||
# There is a kojid plugin that goes with this hub plugin. The kojid builder
|
||||
# plugin has a config file. This hub plugin has no config file.
|
||||
|
||||
|
||||
from koji.context import context
|
||||
from koji.plugin import export
|
||||
import koji
|
||||
import random
|
||||
import sys
|
||||
|
||||
#XXX - have to import kojihub for mktask
|
||||
sys.path.insert(0, '/usr/share/koji-hub/')
|
||||
from kojihub import mktask, get_tag, get_all_arches
|
||||
|
||||
__all__ = ('runroot',)
|
||||
|
||||
|
||||
def get_channel_arches(channel):
|
||||
"""determine arches available in channel"""
|
||||
chan = context.handlers.call('getChannel', channel, strict=True)
|
||||
ret = {}
|
||||
for host in context.handlers.call('listHosts', channelID=chan['id'], enabled=True):
|
||||
for a in host['arches'].split():
|
||||
ret[koji.canonArch(a)] = 1
|
||||
return ret
|
||||
|
||||
@export
|
||||
def runroot(tagInfo, arch, command, channel=None, **opts):
|
||||
""" Create a runroot task """
|
||||
context.session.assertPerm('runroot')
|
||||
taskopts = {
|
||||
'priority': 15,
|
||||
'arch': arch,
|
||||
}
|
||||
|
||||
taskopts['channel'] = channel or 'runroot'
|
||||
|
||||
if arch == 'noarch':
|
||||
#not all arches can generate a proper buildroot for all tags
|
||||
tag = get_tag(tagInfo)
|
||||
if not tag['arches']:
|
||||
raise koji.GenericError, 'no arches defined for tag %s' % tag['name']
|
||||
|
||||
#get all known arches for the system
|
||||
fullarches = get_all_arches()
|
||||
|
||||
tagarches = tag['arches'].split()
|
||||
|
||||
# If our tag can't do all arches, then we need to
|
||||
# specify one of the arches it can do.
|
||||
if set(fullarches) - set(tagarches):
|
||||
chanarches = get_channel_arches(taskopts['channel'])
|
||||
choices = [x for x in tagarches if x in chanarches]
|
||||
if not choices:
|
||||
raise koji.GenericError, 'no common arches for tag/channel: %s/%s' \
|
||||
% (tagInfo, taskopts['channel'])
|
||||
taskopts['arch'] = koji.canonArch(random.choice(choices))
|
||||
|
||||
return mktask(taskopts,'runroot', tagInfo, arch, command, **opts)
|
||||
|
||||
|
|
@ -1,4 +1,6 @@
|
|||
BINFILES = kojira koji-gc koji-shadow
|
||||
SYSTEMDSYSTEMUNITDIR = $(shell pkg-config systemd --variable=systemdsystemunitdir)
|
||||
TYPE = systemd
|
||||
|
||||
_default:
|
||||
@echo "nothing to make. try make install"
|
||||
|
|
@ -6,7 +8,7 @@ _default:
|
|||
clean:
|
||||
rm -f *.o *.so *.pyc *~
|
||||
|
||||
install:
|
||||
_install:
|
||||
@if [ "$(DESTDIR)" = "" ]; then \
|
||||
echo " "; \
|
||||
echo "ERROR: A destdir is required"; \
|
||||
|
|
@ -15,12 +17,6 @@ install:
|
|||
mkdir -p $(DESTDIR)/usr/sbin
|
||||
install -p -m 755 $(BINFILES) $(DESTDIR)/usr/sbin
|
||||
|
||||
mkdir -p $(DESTDIR)/etc/rc.d/init.d
|
||||
install -p -m 755 kojira.init $(DESTDIR)/etc/rc.d/init.d/kojira
|
||||
|
||||
mkdir -p $(DESTDIR)/etc/sysconfig
|
||||
install -p -m 644 kojira.sysconfig $(DESTDIR)/etc/sysconfig/kojira
|
||||
|
||||
mkdir -p $(DESTDIR)/etc/kojira
|
||||
install -p -m 644 kojira.conf $(DESTDIR)/etc/kojira/kojira.conf
|
||||
|
||||
|
|
@ -29,3 +25,16 @@ install:
|
|||
|
||||
mkdir -p $(DESTDIR)/etc/koji-shadow
|
||||
install -p -m 644 koji-shadow.conf $(DESTDIR)/etc/koji-shadow/koji-shadow.conf
|
||||
|
||||
install-systemd: _install
|
||||
mkdir -p $(DESTDIR)$(SYSTEMDSYSTEMUNITDIR)
|
||||
install -p -m 644 kojira.service $(DESTDIR)$(SYSTEMDSYSTEMUNITDIR)
|
||||
|
||||
install-sysv: _install
|
||||
mkdir -p $(DESTDIR)/etc/rc.d/init.d
|
||||
install -p -m 755 kojira.init $(DESTDIR)/etc/rc.d/init.d/kojira
|
||||
|
||||
mkdir -p $(DESTDIR)/etc/sysconfig
|
||||
install -p -m 644 kojira.sysconfig $(DESTDIR)/etc/sysconfig/kojira
|
||||
|
||||
install: install-$(TYPE)
|
||||
|
|
|
|||
|
|
@ -957,4 +957,3 @@ if __name__ == "__main__":
|
|||
pass
|
||||
if not options.skip_main:
|
||||
sys.exit(rv)
|
||||
|
||||
|
|
|
|||
|
|
@ -1328,4 +1328,3 @@ if __name__ == "__main__":
|
|||
except:
|
||||
pass
|
||||
sys.exit(rv)
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
#
|
||||
# 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;
|
||||
# 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,
|
||||
|
|
@ -130,7 +130,7 @@ class ManagedRepo(object):
|
|||
if not tag_info:
|
||||
tag_info = getTag(self.session, self.tag_id, self.event_id)
|
||||
if not tag_info:
|
||||
self.logger.warn('Could not get info for tag %i, skipping delete of repo %i' %
|
||||
self.logger.warn('Could not get info for tag %i, skipping delete of repo %i' %
|
||||
(self.tag_id, self.repo_id))
|
||||
return False
|
||||
tag_name = tag_info['name']
|
||||
|
|
|
|||
14
util/kojira.service
Normal file
14
util/kojira.service
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
[Unit]
|
||||
Description=Koji repo administration
|
||||
Documentation=https://fedoraproject.org/wiki/Koji/ServerHowTo
|
||||
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
ExecStart=/usr/sbin/kojira \
|
||||
--fg \
|
||||
--force-lock \
|
||||
--verbose
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
18
vm/Makefile
18
vm/Makefile
|
|
@ -1,6 +1,7 @@
|
|||
|
||||
BINFILES = kojivmd
|
||||
SHAREFILES = kojikamid
|
||||
SYSTEMDSYSTEMUNITDIR = $(shell pkg-config systemd --variable=systemdsystemunitdir)
|
||||
TYPE = systemd
|
||||
|
||||
_default:
|
||||
@echo "nothing to make. try make install"
|
||||
|
|
@ -11,7 +12,7 @@ clean:
|
|||
kojikamid: kojikamid.py
|
||||
bash fix_kojikamid.sh >kojikamid
|
||||
|
||||
install: kojikamid
|
||||
_install: kojikamid
|
||||
@if [ "$(DESTDIR)" = "" ]; then \
|
||||
echo " "; \
|
||||
echo "ERROR: A destdir is required"; \
|
||||
|
|
@ -24,13 +25,18 @@ install: kojikamid
|
|||
mkdir -p $(DESTDIR)/usr/share/kojivmd
|
||||
install -p -m 644 $(SHAREFILES) $(DESTDIR)/usr/share/kojivmd
|
||||
|
||||
mkdir -p $(DESTDIR)/etc/kojivmd
|
||||
install -p -m 644 kojivmd.conf $(DESTDIR)/etc/kojivmd/kojivmd.conf
|
||||
|
||||
install-systemd: _install
|
||||
mkdir -p $(DESTDIR)$(SYSTEMDSYSTEMUNITDIR)
|
||||
install -p -m 644 kojivmd.service $(DESTDIR)$(SYSTEMDSYSTEMUNITDIR)
|
||||
|
||||
install-sysv: _install
|
||||
mkdir -p $(DESTDIR)/etc/rc.d/init.d
|
||||
install -p -m 755 kojivmd.init $(DESTDIR)/etc/rc.d/init.d/kojivmd
|
||||
|
||||
mkdir -p $(DESTDIR)/etc/sysconfig
|
||||
install -p -m 644 kojivmd.sysconfig $(DESTDIR)/etc/sysconfig/kojivmd
|
||||
|
||||
mkdir -p $(DESTDIR)/etc/kojivmd
|
||||
install -p -m 644 kojivmd.conf $(DESTDIR)/etc/kojivmd/kojivmd.conf
|
||||
|
||||
|
||||
install: install-$(TYPE)
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
#
|
||||
# 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;
|
||||
# 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,
|
||||
|
|
@ -276,7 +276,7 @@ class WindowsBuild(object):
|
|||
continue
|
||||
tokens = entry.split(':')
|
||||
filename = tokens[0]
|
||||
for var in ('name', 'version', 'release'):
|
||||
for var in ('name', 'version', 'release'):
|
||||
filename = filename.replace('$' + var, getattr(self, var))
|
||||
metadata = {}
|
||||
metadata['platforms'] = tokens[1].split(',')
|
||||
|
|
@ -589,7 +589,7 @@ def get_mgmt_server():
|
|||
macaddr, gateway, MANAGER_PORT)
|
||||
server = xmlrpclib.ServerProxy('http://%s:%s/' %
|
||||
(gateway, MANAGER_PORT), allow_none=True)
|
||||
# we would set a timeout on the socket here, but that is apparently not
|
||||
# we would set a timeout on the socket here, but that is apparently not
|
||||
# supported by python/cygwin/Windows
|
||||
task_port = server.getPort(macaddr)
|
||||
logger.debug('found task-specific port %s', task_port)
|
||||
|
|
|
|||
14
vm/kojivmd.service
Normal file
14
vm/kojivmd.service
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
[Unit]
|
||||
Description=Koji vm build server
|
||||
Documentation=https://fedoraproject.org/wiki/Koji/ServerHowTo
|
||||
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
ExecStart=/usr/sbin/kojivmd \
|
||||
--fg \
|
||||
--force-lock \
|
||||
--verbose
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
|
@ -24,3 +24,8 @@ LoginTimeout = 72
|
|||
# Secret = CHANGE_ME
|
||||
|
||||
LibPath = /usr/share/koji-web/lib
|
||||
|
||||
# If set to True, then the footer will be included literally.
|
||||
# If False, then the footer will be included as another Kid Template.
|
||||
# Defaults to True
|
||||
LiteralFooter = True
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@
|
|||
</tr>
|
||||
#end if
|
||||
<tr>
|
||||
<th>Built by</th><td><a href="userinfo?userID=$build.owner_id">$build.owner_name</a></td>
|
||||
<th>Built by</th><td class="user-$build.owner_name"><a href="userinfo?userID=$build.owner_id">$build.owner_name</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
#set $stateName = $util.stateName($build.state)
|
||||
|
|
|
|||
|
|
@ -134,7 +134,7 @@
|
|||
#if $tag
|
||||
<td><a href="taginfo?tagID=$build.tag_id">$build.tag_name</a></td>
|
||||
#end if
|
||||
<td><a href="userinfo?userID=$build.owner_id">$build.owner_name</a></td>
|
||||
<td class="user-$build.owner_name"><a href="userinfo?userID=$build.owner_id">$build.owner_name</a></td>
|
||||
<td>$util.formatTime($build.completion_time)</td>
|
||||
#set $stateName = $util.stateName($build.state)
|
||||
<td class="$stateName">$util.stateImage($build.state)</td>
|
||||
|
|
|
|||
|
|
@ -8,8 +8,12 @@
|
|||
|
||||
#set $localfooterpath=$util.themePath("extra-footer.html", local=True)
|
||||
#if os.path.exists($localfooterpath)
|
||||
#if $literalFooter
|
||||
#set $localfooter="".join(open($localfooterpath).readlines())
|
||||
$localfooter
|
||||
#$localfooter
|
||||
#else
|
||||
#include $localfooterpath
|
||||
#end if
|
||||
#end if
|
||||
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@
|
|||
<td>$build.build_id</td>
|
||||
<td><a href="buildinfo?buildID=$build.build_id">$build.nvr</a></td>
|
||||
#if not $user
|
||||
<td><a href="userinfo?userID=$build.owner_id">$build.owner_name</a></td>
|
||||
<td class="user-$build.owner_name"><a href="userinfo?userID=$build.owner_id">$build.owner_name</a></td>
|
||||
#end if
|
||||
<td>$util.formatTime($build.completion_time)</td>
|
||||
<td class="$stateName">$util.stateImage($build.state)</td>
|
||||
|
|
@ -50,12 +50,13 @@
|
|||
<th>State</th>
|
||||
</tr>
|
||||
#for $task in $tasks
|
||||
<tr class="$util.rowToggle($self)">
|
||||
#set $scratch = $util.taskScratchClass($task)
|
||||
<tr class="$util.rowToggle($self) $scratch">
|
||||
#set $state = $util.taskState($task.state)
|
||||
<td>$task.id</td>
|
||||
<td><a href="taskinfo?taskID=$task.id" class="task$state" title="$state">$koji.taskLabel($task)</a></td>
|
||||
#if not $user
|
||||
<td>
|
||||
<td class="user-$task.owner_name">
|
||||
#if $task.owner_type == $koji.USERTYPES['HOST']
|
||||
<a href="hostinfo?userID=$task.owner">$task.owner_name</a>
|
||||
#else
|
||||
|
|
|
|||
|
|
@ -286,7 +286,7 @@ def index(environ, packageOrder='package_name', packageStart=None):
|
|||
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
|
||||
|
|
@ -294,21 +294,21 @@ def index(environ, packageOrder='package_name', packageStart=None):
|
|||
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:
|
||||
|
|
@ -399,7 +399,7 @@ def notificationcreate(environ):
|
|||
def notificationdelete(environ, notificationID):
|
||||
server = _getServer(environ)
|
||||
_assertLogin(environ)
|
||||
|
||||
|
||||
notificationID = int(notificationID)
|
||||
notification = server.getBuildNotification(notificationID)
|
||||
if not notification:
|
||||
|
|
@ -491,7 +491,7 @@ def tasks(environ, owner=None, state='active', view='tree', method='all', hostID
|
|||
|
||||
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':
|
||||
|
|
@ -531,7 +531,7 @@ def tasks(environ, owner=None, state='active', view='tree', method='all', hostID
|
|||
|
||||
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:
|
||||
|
|
@ -577,7 +577,7 @@ def taskinfo(environ, taskID):
|
|||
values['parent'] = parent
|
||||
else:
|
||||
values['parent'] = None
|
||||
|
||||
|
||||
descendents = server.getTaskDescendents(task['id'], request=True)
|
||||
values['descendents'] = descendents
|
||||
|
||||
|
|
@ -641,7 +641,7 @@ def taskinfo(environ, taskID):
|
|||
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'])
|
||||
|
|
@ -688,7 +688,7 @@ def taskstatus(environ, taskID):
|
|||
def resubmittask(environ, taskID):
|
||||
server = _getServer(environ)
|
||||
_assertLogin(environ)
|
||||
|
||||
|
||||
taskID = int(taskID)
|
||||
newTaskID = server.resubmitTask(taskID)
|
||||
_redirect(environ, 'taskinfo?taskID=%i' % newTaskID)
|
||||
|
|
@ -817,13 +817,13 @@ def packages(environ, tagID=None, userID=None, order='package_name', start=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):
|
||||
|
|
@ -840,7 +840,7 @@ def packageinfo(environ, packageID, tagOrder='name', tagStart=None, buildOrder='
|
|||
|
||||
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']},
|
||||
|
|
@ -966,7 +966,7 @@ def tagedit(environ, tagID):
|
|||
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'])
|
||||
|
|
@ -1016,7 +1016,7 @@ def tagparent(environ, tagID, parentID, action):
|
|||
|
||||
data = server.getInheritanceData(tag['id'])
|
||||
data.append(newDatum)
|
||||
|
||||
|
||||
server.setInheritanceData(tag['id'], data)
|
||||
elif form.has_key('cancel'):
|
||||
pass
|
||||
|
|
@ -1039,7 +1039,7 @@ def tagparent(environ, tagID, parentID, action):
|
|||
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'])
|
||||
|
|
@ -1076,7 +1076,7 @@ def buildinfo(environ, buildID):
|
|||
server = _getServer(environ)
|
||||
|
||||
buildID = int(buildID)
|
||||
|
||||
|
||||
build = server.getBuild(buildID)
|
||||
|
||||
values['title'] = koji.buildLabel(build) + ' | Build Info'
|
||||
|
|
@ -1170,7 +1170,7 @@ def buildinfo(environ, buildID):
|
|||
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'])
|
||||
|
|
@ -1243,7 +1243,7 @@ def builds(environ, userID=None, tagID=None, packageID=None, state=None, order='
|
|||
if prefix not in _PREFIX_CHARS:
|
||||
prefix = None
|
||||
values['prefix'] = prefix
|
||||
|
||||
|
||||
values['order'] = order
|
||||
if type in ('maven', 'win', 'image'):
|
||||
pass
|
||||
|
|
@ -1274,7 +1274,7 @@ def builds(environ, userID=None, tagID=None, packageID=None, state=None, order='
|
|||
'type': type,
|
||||
'state': state, 'prefix': prefix},
|
||||
start=start, dataName='builds', prefix='build', order=order)
|
||||
|
||||
|
||||
values['chars'] = _PREFIX_CHARS
|
||||
|
||||
return _genHTML(environ, 'builds.chtml')
|
||||
|
|
@ -1295,7 +1295,7 @@ def users(environ, order='name', start=None, prefix=None):
|
|||
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):
|
||||
|
|
@ -1314,10 +1314,10 @@ def userinfo(environ, userID, packageOrder='package_name', packageStart=None, bu
|
|||
|
||||
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):
|
||||
|
|
@ -1360,7 +1360,7 @@ def rpminfo(environ, rpmID, fileOrder='name', fileStart=None, buildrootOrder='-i
|
|||
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)
|
||||
|
||||
|
|
@ -1408,7 +1408,7 @@ def fileinfo(environ, filename, rpmID=None, archiveID=None):
|
|||
|
||||
values['rpm'] = None
|
||||
values['archive'] = None
|
||||
|
||||
|
||||
if rpmID:
|
||||
rpmID = int(rpmID)
|
||||
rpm = server.getRPM(rpmID)
|
||||
|
|
@ -1439,7 +1439,7 @@ def fileinfo(environ, filename, rpmID=None, archiveID=None):
|
|||
def cancelbuild(environ, buildID):
|
||||
server = _getServer(environ)
|
||||
_assertLogin(environ)
|
||||
|
||||
|
||||
buildID = int(buildID)
|
||||
build = server.getBuild(buildID)
|
||||
if build == None:
|
||||
|
|
@ -1468,7 +1468,7 @@ def hosts(environ, state='enabled', start=None, order='name'):
|
|||
values['state'] = state
|
||||
|
||||
hosts = server.listHosts(**args)
|
||||
|
||||
|
||||
server.multicall = True
|
||||
for host in hosts:
|
||||
server.getLastHostUpdate(host['id'])
|
||||
|
|
@ -1518,7 +1518,7 @@ def hostinfo(environ, hostID=None, userID=None):
|
|||
values['perms'] = server.getUserPerms(environ['koji.currentUser']['id'])
|
||||
else:
|
||||
values['perms'] = []
|
||||
|
||||
|
||||
return _genHTML(environ, 'hostinfo.chtml')
|
||||
|
||||
def hostedit(environ, hostID):
|
||||
|
|
@ -1630,7 +1630,7 @@ def buildrootinfo(environ, buildrootID, builtStart=None, builtOrder=None, compon
|
|||
|
||||
values['buildroot'] = buildroot
|
||||
values['task'] = task
|
||||
|
||||
|
||||
return _genHTML(environ, 'buildrootinfo.chtml')
|
||||
|
||||
def rpmlist(environ, type, buildrootID=None, imageID=None, start=None, order='nvr'):
|
||||
|
|
@ -1714,13 +1714,13 @@ def buildtargets(environ, start=None, order='name'):
|
|||
|
||||
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):
|
||||
|
|
@ -1733,7 +1733,7 @@ def buildtargetinfo(environ, targetID=None, name=None):
|
|||
target = server.getBuildTarget(targetID)
|
||||
elif name != None:
|
||||
target = server.getBuildTarget(name)
|
||||
|
||||
|
||||
if target == None:
|
||||
raise koji.GenericError, 'invalid build target: %s' % (targetID or name)
|
||||
|
||||
|
|
@ -1785,7 +1785,7 @@ def buildtargetedit(environ, targetID):
|
|||
values = _initValues(environ, 'Edit Build Target', 'buildtargets')
|
||||
tags = server.listTags()
|
||||
tags.sort(_sortbyname)
|
||||
|
||||
|
||||
values['target'] = target
|
||||
values['tags'] = tags
|
||||
|
||||
|
|
@ -1810,7 +1810,7 @@ def buildtargetcreate(environ):
|
|||
|
||||
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')
|
||||
|
|
@ -1860,7 +1860,7 @@ def buildsbyuser(environ, start=None, order='-builds'):
|
|||
user['builds'] = numBuilds
|
||||
if numBuilds > maxBuilds:
|
||||
maxBuilds = numBuilds
|
||||
|
||||
|
||||
values['order'] = order
|
||||
|
||||
graphWidth = 400.0
|
||||
|
|
@ -1893,14 +1893,14 @@ def rpmsbyhost(environ, start=None, order=None, hostArch=None, rpmArch=None):
|
|||
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
|
||||
|
|
@ -1947,11 +1947,11 @@ def tasksbyhost(environ, start=None, order='-tasks', hostArch=None):
|
|||
server = _getServer(environ)
|
||||
|
||||
maxTasks = 1
|
||||
|
||||
|
||||
hostArchFilter = hostArch
|
||||
if hostArchFilter == 'ix86':
|
||||
hostArchFilter = ['i386', 'i486', 'i586', 'i686']
|
||||
|
||||
|
||||
hosts = server.listHosts(arches=hostArchFilter)
|
||||
|
||||
server.multicall = True
|
||||
|
|
@ -1963,12 +1963,12 @@ def tasksbyhost(environ, start=None, order='-tasks', hostArch=None):
|
|||
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
|
||||
|
|
@ -1984,7 +1984,7 @@ def tasksbyuser(environ, start=None, order='-tasks'):
|
|||
server = _getServer(environ)
|
||||
|
||||
maxTasks = 1
|
||||
|
||||
|
||||
users = server.listUsers()
|
||||
|
||||
server.multicall = True
|
||||
|
|
@ -1996,7 +1996,7 @@ def tasksbyuser(environ, start=None, order='-tasks'):
|
|||
user['tasks'] = numTasks
|
||||
if numTasks > maxTasks:
|
||||
maxTasks = numTasks
|
||||
|
||||
|
||||
values['order'] = order
|
||||
|
||||
graphWidth = 400.0
|
||||
|
|
@ -2025,7 +2025,7 @@ def buildsbystatus(environ, days='7'):
|
|||
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
|
||||
|
|
@ -2070,7 +2070,7 @@ def buildsbytarget(environ, days='7', start=None, order='-builds'):
|
|||
if builds > maxBuilds:
|
||||
maxBuilds = builds
|
||||
|
||||
kojiweb.util.paginateList(values, targets.values(), start, 'targets', 'target', order)
|
||||
kojiweb.util.paginateList(values, targets.values(), start, 'targets', 'target', order)
|
||||
|
||||
values['order'] = order
|
||||
|
||||
|
|
@ -2140,7 +2140,7 @@ def recentbuilds(environ, user=None, tag=None, package=None):
|
|||
task = None
|
||||
builds[i]['task'] = task
|
||||
builds[i]['changelog'] = clogs[i][0]
|
||||
|
||||
|
||||
values['tag'] = tagObj
|
||||
values['user'] = userObj
|
||||
values['package'] = packageObj
|
||||
|
|
@ -2197,7 +2197,7 @@ def search(environ, start=None, order='name'):
|
|||
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:
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@
|
|||
#for $build in $builds
|
||||
<tr class="$util.rowToggle($self)">
|
||||
<td><a href="buildinfo?buildID=$build.build_id">$build.nvr</a></td>
|
||||
<td><a href="userinfo?userID=$build.owner_id">$build.owner_name</a></td>
|
||||
<td class="user-$build.owner_name"><a href="userinfo?userID=$build.owner_id">$build.owner_name</a></td>
|
||||
<td>$util.formatTime($build.completion_time)</td>
|
||||
#set $stateName = $util.stateName($build.state)
|
||||
<td class="$stateName">$util.stateImage($build.state)</td>
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@
|
|||
<td><a href="packageinfo?packageID=$package.package_id">$package.package_name</a></td>
|
||||
#if $tag or $user
|
||||
<td><a href="taginfo?tagID=$package.tag_id">$package.tag_name</a></td>
|
||||
<td><a href="userinfo?userID=$package.owner_id">$package.owner_name</a></td>
|
||||
<td class="user-$package.owner_name"><a href="userinfo?userID=$package.owner_id">$package.owner_name</a></td>
|
||||
<td class="$str(not $package.blocked).lower()">#if $package.blocked then $util.imageTag('no') else $util.imageTag('yes')#</td>
|
||||
#end if
|
||||
</tr>
|
||||
|
|
|
|||
|
|
@ -145,7 +145,7 @@ All
|
|||
$printChildren($task.id, $task.descendents)
|
||||
#end if
|
||||
</td>
|
||||
<td>
|
||||
<td class="user-$task.owner_name">
|
||||
#if $task.owner_type == $koji.USERTYPES['HOST']
|
||||
<a href="hostinfo?userID=$task.owner">$task.owner_name</a>
|
||||
#else
|
||||
|
|
|
|||
|
|
@ -69,6 +69,7 @@ class Dispatcher(object):
|
|||
['KojiFilesURL', 'string', 'http://localhost/kojifiles'],
|
||||
['KojiTheme', 'string', None],
|
||||
['KojiGreeting', 'string', 'Welcome to Koji Web'],
|
||||
['LiteralFooter', 'boolean', True],
|
||||
|
||||
['WebPrincipal', 'string', None],
|
||||
['WebKeytab', 'string', '/etc/httpd.keytab'],
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ def _initValues(environ, title='Build System Info', pageID='summary'):
|
|||
values['title'] = title
|
||||
values['pageID'] = pageID
|
||||
values['currentDate'] = str(datetime.datetime.now())
|
||||
values['literalFooter'] = environ['koji.options'].get('LiteralFooter', True)
|
||||
themeCache.clear()
|
||||
themeInfo.clear()
|
||||
themeInfo['name'] = environ['koji.options'].get('KojiTheme', None)
|
||||
|
|
@ -225,7 +226,7 @@ def passthrough_except(template, *exclude):
|
|||
previously used
|
||||
#attr _PASSTHROUGH = ...
|
||||
to define the list of variable names to be passed-through.
|
||||
Any variables names passed in will be excluded from the
|
||||
Any variables names passed in will be excluded from the
|
||||
list of variables in the output string.
|
||||
"""
|
||||
passvars = []
|
||||
|
|
@ -245,7 +246,7 @@ def sortByKeyFunc(key, noneGreatest=False):
|
|||
cmpFunc = lambda a, b: (a is None or b is None) and -(cmp(a, b)) or cmp(a, b)
|
||||
else:
|
||||
cmpFunc = cmp
|
||||
|
||||
|
||||
if key.startswith('-'):
|
||||
key = key[1:]
|
||||
sortFunc = lambda a, b: cmpFunc(b[key], a[key])
|
||||
|
|
@ -265,7 +266,7 @@ def paginateList(values, data, start, dataName, prefix=None, order=None, noneGre
|
|||
"""
|
||||
if order != None:
|
||||
data.sort(sortByKeyFunc(order, noneGreatest))
|
||||
|
||||
|
||||
totalRows = len(data)
|
||||
|
||||
if start:
|
||||
|
|
@ -277,7 +278,7 @@ def paginateList(values, data, start, dataName, prefix=None, order=None, noneGre
|
|||
count = len(data)
|
||||
|
||||
_populateValues(values, dataName, prefix, data, totalRows, start, count, pageSize, order)
|
||||
|
||||
|
||||
return data
|
||||
|
||||
def paginateMethod(server, values, methodName, args=None, kw=None,
|
||||
|
|
@ -294,10 +295,10 @@ def paginateMethod(server, values, methodName, args=None, kw=None,
|
|||
start = 0
|
||||
if not dataName:
|
||||
raise StandardError, 'dataName must be specified'
|
||||
|
||||
|
||||
kw['queryOpts'] = {'countOnly': True}
|
||||
totalRows = getattr(server, methodName)(*args, **kw)
|
||||
|
||||
|
||||
kw['queryOpts'] = {'order': order,
|
||||
'offset': start,
|
||||
'limit': pageSize}
|
||||
|
|
@ -402,7 +403,7 @@ def formatDep(name, version, flags):
|
|||
a human-readable format. Copied from
|
||||
rpmUtils/miscutils.py:formatRequires()"""
|
||||
s = name
|
||||
|
||||
|
||||
if flags:
|
||||
if flags & (koji.RPMSENSE_LESS | koji.RPMSENSE_GREATER |
|
||||
koji.RPMSENSE_EQUAL):
|
||||
|
|
@ -458,6 +459,19 @@ def rowToggle(template):
|
|||
else:
|
||||
return 'row-even'
|
||||
|
||||
|
||||
def taskScratchClass(task_object):
|
||||
""" Return a css class indicating whether or not this task is a scratch
|
||||
build.
|
||||
"""
|
||||
request = task_object['request']
|
||||
if len(request) >= 3:
|
||||
opts = request[2]
|
||||
if opts.get('scratch'):
|
||||
return "scratch"
|
||||
return ""
|
||||
|
||||
|
||||
_fileFlags = {1: 'configuration',
|
||||
2: 'documentation',
|
||||
4: 'icon',
|
||||
|
|
@ -567,4 +581,3 @@ a network issue or load issues on the server."""
|
|||
else:
|
||||
str = "An error has occurred while processing your request."
|
||||
return str, level
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue