Merge remote-tracking branch 'origin' into cgen

This commit is contained in:
Mike McLean 2015-06-18 14:26:18 -04:00
commit e8a30591fa
45 changed files with 772 additions and 284 deletions

View file

@ -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

View file

@ -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)

View file

@ -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
View 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
View file

@ -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]")

View file

@ -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

View file

@ -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

View file

@ -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():

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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())

View file

@ -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,

View file

@ -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 :

View file

@ -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,

View file

@ -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,

View file

@ -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

View file

@ -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()

View file

@ -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))

View file

@ -156,4 +156,3 @@ class PlgFileObject(socket._fileobject):
self._sock.close()
finally:
self._sock = None

View file

@ -176,4 +176,3 @@ if __name__ == '__main__':
except KeyboardInterrupt:
os._exit(0)
print "All done. (%d timed out)" % tm.get()

View file

@ -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,

View file

@ -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,

View file

@ -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']

View file

@ -319,5 +319,3 @@ class RunRootTask(tasks.BaseTaskHandler):
os.unlink(fn)
except OSError:
pass

61
plugins/runroot_hub.py Normal file
View 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)

View file

@ -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)

View file

@ -957,4 +957,3 @@ if __name__ == "__main__":
pass
if not options.skip_main:
sys.exit(rv)

View file

@ -1328,4 +1328,3 @@ if __name__ == "__main__":
except:
pass
sys.exit(rv)

View file

@ -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
View 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

View file

@ -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)

View file

@ -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
View 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

View file

@ -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

View file

@ -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)

View file

@ -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>

View file

@ -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>

View file

@ -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

View file

@ -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:

View file

@ -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>

View file

@ -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>

View file

@ -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

View file

@ -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'],

View file

@ -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