From 52a01883d6bbae5bdba3d3009242c3d4b94d5b5b Mon Sep 17 00:00:00 2001 From: Ian McLeod Date: Wed, 8 Apr 2015 22:37:16 -0500 Subject: [PATCH 01/33] Add "real" Vagrant format options to image building. We originally added Vagrant support by tweaking options sent to the existing RHEV-M and vSphere OVA generation code. This was a bit of a hack and resulted in confusing filenames. This adds "vagrant-libvirt" and "vagrant-virtualbox" image types, passes in the correct options to Image Factory and produces output files with the conventional ".box" extension and more descriptive and less confusing vagrant name suffixes. --- builder/kojid | 20 +++++++++++++++++++- cli/koji | 2 +- docs/schema.sql | 1 + 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/builder/kojid b/builder/kojid index 3df05884..e460ccc3 100755 --- a/builder/kojid +++ b/builder/kojid @@ -3051,7 +3051,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') for f in formats: if f not in supported: raise koji.ApplianceError('Invalid format: %s' % f) @@ -3083,6 +3083,8 @@ class BaseImageTask(OzImageTask): 'qcow2': 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 +3223,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', @@ -3381,6 +3394,11 @@ 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' else: diff --git a/cli/koji b/cli/koji index bfb0955c..473bcb84 100755 --- a/cli/koji +++ b/cli/koji @@ -5243,7 +5243,7 @@ def _build_image_indirection(options, task_opts, session, args): def handle_image_build(options, session, args): """Create a disk image given an install tree""" formats = ('vmdk', 'qcow', 'qcow2', 'vdi', 'rhevm-ova', 'vsphere-ova', - 'docker', 'raw-xz') + 'vagrant-virtualbox', 'vagrant-libvirt', 'docker', 'raw-xz') usage = _("usage: %prog image-build [options] " + " [...]") usage += _("\n %prog image-build --config FILE") diff --git a/docs/schema.sql b/docs/schema.sql index c087d566..ffe9b222 100644 --- a/docs/schema.sql +++ b/docs/schema.sql @@ -736,6 +736,7 @@ 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'); -- Do we want to enforce a constraint that a build can only generate one From 91bae287e00faf669496d5c3e38caaaf79860d69 Mon Sep 17 00:00:00 2001 From: Mike McLean Date: Mon, 20 Apr 2015 18:05:01 -0400 Subject: [PATCH 02/33] handle callnum=None (anon_retry) in retry error messages --- koji/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/koji/__init__.py b/koji/__init__.py index d133ed19..51713499 100644 --- a/koji/__init__.py +++ b/koji/__init__.py @@ -1948,7 +1948,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 #%d 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) From 3283df7f2cf275927631e2d9b76729c6d622e567 Mon Sep 17 00:00:00 2001 From: Mike Bonnet Date: Mon, 20 Apr 2015 18:33:45 -0400 Subject: [PATCH 03/33] add id and parent to the headers of TaskStateChange messages --- plugins/messagebus.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/messagebus.py b/plugins/messagebus.py index a23bc949..2b23fe5b 100644 --- a/plugins/messagebus.py +++ b/plugins/messagebus.py @@ -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'] From 381645a4532ad44eb8a348ca4d92c2fbb4fb7d2e Mon Sep 17 00:00:00 2001 From: Mike Bonnet Date: Wed, 22 Apr 2015 16:06:48 -0400 Subject: [PATCH 04/33] mergeScratch(): import rpms from a scratch build into an existing build The mergeScratch() method allows importing rpms built by a scratch build into an existing build, if that build did not produce rpms matching the arch of the scratch build. This is useful for bootstrapping a new arch into existing builds, and avoiding a mass-rebuild to add arch support. --- hub/kojihub.py | 141 +++++++++++++++++++++++++++++++++++++++++++++++ koji/__init__.py | 3 + 2 files changed, 144 insertions(+) diff --git a/hub/kojihub.py b/hub/kojihub.py index 9decf7cf..d11ea7de 100644 --- a/hub/kojihub.py +++ b/hub/kojihub.py @@ -4720,6 +4720,117 @@ 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']: + raise koji.ImportError, 'no arch-specific rpms produced by task %s' % child['id'] + 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 + + # 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 @@ -7647,6 +7758,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 diff --git a/koji/__init__.py b/koji/__init__.py index 51713499..b73af71d 100644 --- a/koji/__init__.py +++ b/koji/__init__.py @@ -323,6 +323,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): """ From 6e480eed1c272c634d8ab814b5e1ccb30b2e326f Mon Sep 17 00:00:00 2001 From: Mike Bonnet Date: Mon, 20 Apr 2015 19:12:59 -0400 Subject: [PATCH 05/33] use the same name for repository and pluginRepository entries in settings.xml This is required because of http://jira.codehaus.org/browse/MNG-5475 --- builder/kojid | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/builder/kojid b/builder/kojid index e460ccc3..9fca6627 100755 --- a/builder/kojid +++ b/builder/kojid @@ -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 From 108475b6a3101d1d812b9638c307aa6be1b5cfdc Mon Sep 17 00:00:00 2001 From: Dennis Gilmore Date: Fri, 24 Apr 2015 09:42:22 -0500 Subject: [PATCH 06/33] schema: add missiing raw.xz archive type Signed-off-by: Dennis Gilmore --- docs/schema.sql | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/schema.sql b/docs/schema.sql index ffe9b222..3582b876 100644 --- a/docs/schema.sql +++ b/docs/schema.sql @@ -737,6 +737,7 @@ insert into archivetypes (name, description, extensions) values ('txt', 'Text fi 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 From 865f93c1e25eed469f9f795986a449c92982bfb7 Mon Sep 17 00:00:00 2001 From: Jay Greguske Date: Tue, 5 May 2015 13:04:15 -0400 Subject: [PATCH 07/33] add vhd/vpc support --- builder/kojid | 11 +++++++++-- cli/koji | 7 +++---- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/builder/kojid b/builder/kojid index 9fca6627..3a92ec32 100755 --- a/builder/kojid +++ b/builder/kojid @@ -3049,7 +3049,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', 'vagrant-virtualbox', 'vagrant-libvirt') + 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) @@ -3079,6 +3079,7 @@ 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, @@ -3289,7 +3290,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'): @@ -3399,6 +3404,8 @@ class BaseImageTask(OzImageTask): 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': diff --git a/cli/koji b/cli/koji index 473bcb84..b08f0781 100755 --- a/cli/koji +++ b/cli/koji @@ -5238,12 +5238,11 @@ 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', - 'vagrant-virtualbox', 'vagrant-libvirt', 'docker', 'raw-xz') + formats = ('vmdk', 'qcow', 'qcow2', 'vdi', 'vpc', 'rhevm-ova', + 'vsphere-ova', 'vagrant-virtualbox', 'vagrant-libvirt', + 'docker', 'raw-xz') usage = _("usage: %prog image-build [options] " + " [...]") usage += _("\n %prog image-build --config FILE") From e5466860ed5fc9629d1042e65647bc9fb5e3e501 Mon Sep 17 00:00:00 2001 From: Ralph Bean Date: Mon, 27 Apr 2015 14:20:30 -0400 Subject: [PATCH 08/33] (koji-web) Add user-specific css classes. This will allow us in Fedora to style system account names differently. For instance, we can markup the name 'koschei' (our continuous integration bot). --- www/kojiweb/buildinfo.chtml | 2 +- www/kojiweb/builds.chtml | 2 +- www/kojiweb/index.chtml | 4 ++-- www/kojiweb/packageinfo.chtml | 2 +- www/kojiweb/packages.chtml | 2 +- www/kojiweb/tasks.chtml | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/www/kojiweb/buildinfo.chtml b/www/kojiweb/buildinfo.chtml index 65fc3bad..07c62fdf 100644 --- a/www/kojiweb/buildinfo.chtml +++ b/www/kojiweb/buildinfo.chtml @@ -45,7 +45,7 @@ #end if - Built by$build.owner_name + Built by$build.owner_name #set $stateName = $util.stateName($build.state) diff --git a/www/kojiweb/builds.chtml b/www/kojiweb/builds.chtml index f221ffa2..337c466a 100644 --- a/www/kojiweb/builds.chtml +++ b/www/kojiweb/builds.chtml @@ -134,7 +134,7 @@ #if $tag $build.tag_name #end if - $build.owner_name + $build.owner_name $util.formatTime($build.completion_time) #set $stateName = $util.stateName($build.state) $util.stateImage($build.state) diff --git a/www/kojiweb/index.chtml b/www/kojiweb/index.chtml index f6312170..7613fe60 100644 --- a/www/kojiweb/index.chtml +++ b/www/kojiweb/index.chtml @@ -22,7 +22,7 @@ $build.build_id $build.nvr #if not $user - $build.owner_name + $build.owner_name #end if $util.formatTime($build.completion_time) $util.stateImage($build.state) @@ -55,7 +55,7 @@ $task.id $koji.taskLabel($task) #if not $user - + #if $task.owner_type == $koji.USERTYPES['HOST'] $task.owner_name #else diff --git a/www/kojiweb/packageinfo.chtml b/www/kojiweb/packageinfo.chtml index e0fcb716..313bc88e 100644 --- a/www/kojiweb/packageinfo.chtml +++ b/www/kojiweb/packageinfo.chtml @@ -47,7 +47,7 @@ #for $build in $builds $build.nvr - $build.owner_name + $build.owner_name $util.formatTime($build.completion_time) #set $stateName = $util.stateName($build.state) $util.stateImage($build.state) diff --git a/www/kojiweb/packages.chtml b/www/kojiweb/packages.chtml index 6de2cf83..08cae840 100644 --- a/www/kojiweb/packages.chtml +++ b/www/kojiweb/packages.chtml @@ -78,7 +78,7 @@ $package.package_name #if $tag or $user $package.tag_name - $package.owner_name + $package.owner_name #if $package.blocked then $util.imageTag('no') else $util.imageTag('yes')# #end if diff --git a/www/kojiweb/tasks.chtml b/www/kojiweb/tasks.chtml index 897bfcc4..4c1a32fa 100644 --- a/www/kojiweb/tasks.chtml +++ b/www/kojiweb/tasks.chtml @@ -145,7 +145,7 @@ All $printChildren($task.id, $task.descendents) #end if - + #if $task.owner_type == $koji.USERTYPES['HOST'] $task.owner_name #else From c738175d4a76d686596ee01ea84b5756ed83bce2 Mon Sep 17 00:00:00 2001 From: Ralph Bean Date: Mon, 27 Apr 2015 14:20:31 -0400 Subject: [PATCH 09/33] (koji-web) Markup scratch builds. With this we can markup scratch builds, deemphasize them, etc. --- www/kojiweb/index.chtml | 3 ++- www/lib/kojiweb/util.py | 13 +++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/www/kojiweb/index.chtml b/www/kojiweb/index.chtml index 7613fe60..738256c3 100644 --- a/www/kojiweb/index.chtml +++ b/www/kojiweb/index.chtml @@ -50,7 +50,8 @@ State #for $task in $tasks - + #set $scratch = $util.taskScratchClass($task) + #set $state = $util.taskState($task.state) $task.id $koji.taskLabel($task) diff --git a/www/lib/kojiweb/util.py b/www/lib/kojiweb/util.py index ae007577..89a5ddbc 100644 --- a/www/lib/kojiweb/util.py +++ b/www/lib/kojiweb/util.py @@ -458,6 +458,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', From 837c070127d30b6b82834c90e424b8fc8185ef2d Mon Sep 17 00:00:00 2001 From: Ralph Bean Date: Fri, 24 Apr 2015 15:24:43 -0400 Subject: [PATCH 10/33] (koji-web) Treat extra-footer as a template. The code previously would look for a file called extra-footer.html that a koji admin could override to put some nice extra footer on their koji-web page. If that file existed, koji-web would read it in verbatim and write it to the bottom of each served page. This change enhances that behavior and treats the extra-footer.html file as a Cheetah template. This way, a koji admin could specify some conditional logic in the extra-footer file to display this or that thing depending on the dynamic contents of the page being served. Note that this is backwards compatible. Static html extra-footer files already out there in the wild will be interpreted as Cheetah templates and should be included seamlessly. --- www/kojiweb/includes/footer.chtml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/www/kojiweb/includes/footer.chtml b/www/kojiweb/includes/footer.chtml index 8dad3102..684e5a4c 100644 --- a/www/kojiweb/includes/footer.chtml +++ b/www/kojiweb/includes/footer.chtml @@ -8,8 +8,7 @@ #set $localfooterpath=$util.themePath("extra-footer.html", local=True) #if os.path.exists($localfooterpath) -#set $localfooter="".join(open($localfooterpath).readlines()) -$localfooter +#include $localfooterpath #end if From 9e89ebb9a4988fb498e2bcc840d1e0a82b774371 Mon Sep 17 00:00:00 2001 From: Pavol Babincak Date: Mon, 6 Apr 2015 12:10:05 -0400 Subject: [PATCH 11/33] Ran reindent.py from cpython tools on all python scripts Citing from reindent docs: Change Python (.py) files to use 4-space indents and no hard tab characters. Also trim excess spaces and tabs from ends of lines, and remove empty lines at the end of files. Also ensure the last line ends with a newline. Citing from PEP 8: Use 4 spaces per indentation level. Python 2 code indented with a mixture of tabs and spaces should be converted to using spaces exclusively. Don't write string literals that rely on significant trailing whitespace. Such trailing whitespace is visually indistinguishable and some editors (or more recently, reindent.py) will trim them. Also PyLint recommends not to have trailing whitespace on any line. --- builder/kojid | 138 +++++++++++++++++----------------- cli/koji | 70 ++++++++--------- hub/kojihub.py | 28 +++---- hub/kojixmlrpc.py | 6 +- koji/__init__.py | 4 +- koji/auth.py | 10 +-- koji/context.py | 2 +- koji/daemon.py | 2 +- koji/db.py | 2 +- koji/plugin.py | 2 +- koji/policy.py | 3 +- koji/server.py | 1 - koji/ssl/SSLCommon.py | 1 - koji/ssl/SSLConnection.py | 1 - koji/ssl/XMLRPCServerProxy.py | 1 - koji/tasks.py | 2 +- koji/util.py | 2 +- plugins/messagebus.py | 26 +++---- plugins/runroot.py | 2 - util/koji-gc | 1 - util/koji-shadow | 1 - util/kojira | 4 +- vm/kojikamid.py | 6 +- www/kojiweb/index.py | 98 ++++++++++++------------ www/lib/kojiweb/util.py | 15 ++-- 25 files changed, 209 insertions(+), 219 deletions(-) diff --git a/builder/kojid b/builder/kojid index 3a92ec32..dcd08407 100755 --- a/builder/kojid +++ b/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, @@ -1030,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. @@ -2387,7 +2387,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 @@ -2486,7 +2486,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') @@ -2501,7 +2501,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 @@ -2522,8 +2522,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): @@ -2546,7 +2546,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) @@ -2559,11 +2559,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]) @@ -2580,7 +2580,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: @@ -2608,7 +2608,7 @@ class ApplianceTask(ImageTask): cachedir[1:])) broot.markExternalRPMs(hdrlist) imgdata['rpmlist'] = hdrlist - + broot.expire() return imgdata @@ -2704,7 +2704,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]]) @@ -3421,7 +3421,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 @@ -3450,7 +3450,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. @@ -3489,7 +3489,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)" % @@ -3498,21 +3498,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)) @@ -3531,7 +3531,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 @@ -3541,10 +3541,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 ] @@ -3562,7 +3562,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 @@ -3611,12 +3611,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')) @@ -3624,24 +3624,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, @@ -3690,9 +3690,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: @@ -3720,7 +3720,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 @@ -3848,7 +3848,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] @@ -3867,7 +3867,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, } @@ -3992,16 +3992,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) @@ -4017,17 +4017,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'] @@ -4041,7 +4041,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) @@ -4080,7 +4080,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) @@ -4089,14 +4089,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: @@ -4587,7 +4587,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 diff --git a/cli/koji b/cli/koji index b08f0781..8e0f7b2f 100755 --- a/cli/koji +++ b/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) @@ -5908,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) @@ -5922,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) @@ -6044,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] @@ -6059,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: @@ -6073,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 @@ -6133,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) diff --git a/hub/kojihub.py b/hub/kojihub.py index d11ea7de..bea724dc 100644 --- a/hub/kojihub.py +++ b/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, @@ -3442,8 +3442,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('buildroot ON rpminfo.buildroot_id = buildroot.id') @@ -3553,7 +3553,7 @@ def list_archives(buildID=None, buildrootID=None, componentBuildrootID=None, hos 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 @@ -3590,7 +3590,7 @@ def list_archives(buildID=None, buildrootID=None, componentBuildrootID=None, hos an empty list is returned. """ values = {} - + tables = ['archiveinfo'] joins = ['archivetypes on archiveinfo.type_id = archivetypes.id'] fields = [('archiveinfo.id', 'id'), @@ -6060,7 +6060,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: @@ -7653,7 +7653,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) @@ -8698,7 +8698,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): @@ -8707,14 +8707,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) @@ -9364,11 +9364,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) @@ -10217,7 +10217,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]) @@ -10458,7 +10458,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(): diff --git a/hub/kojixmlrpc.py b/hub/kojixmlrpc.py index 67cb8eea..0e510122 100644 --- a/hub/kojixmlrpc.py +++ b/hub/kojixmlrpc.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, @@ -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 diff --git a/koji/__init__.py b/koji/__init__.py index b73af71d..c3a0990e 100644 --- a/koji/__init__.py +++ b/koji/__init__.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, @@ -990,7 +990,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: diff --git a/koji/auth.py b/koji/auth.py index 8e51ec7b..d419d770 100644 --- a/koji/auth.py +++ b/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()) diff --git a/koji/context.py b/koji/context.py index f904bed7..b05e3a3c 100755 --- a/koji/context.py +++ b/koji/context.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, diff --git a/koji/daemon.py b/koji/daemon.py index b9f3070b..d76355ba 100644 --- a/koji/daemon.py +++ b/koji/daemon.py @@ -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, diff --git a/koji/db.py b/koji/db.py index f5f61043..dcd24610 100644 --- a/koji/db.py +++ b/koji/db.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, diff --git a/koji/plugin.py b/koji/plugin.py index e189d1f3..cbb245e8 100644 --- a/koji/plugin.py +++ b/koji/plugin.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, diff --git a/koji/policy.py b/koji/policy.py index 47e72371..653f4143 100644 --- a/koji/policy.py +++ b/koji/policy.py @@ -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 - diff --git a/koji/server.py b/koji/server.py index 7d9ed3ef..7cc8be85 100644 --- a/koji/server.py +++ b/koji/server.py @@ -187,4 +187,3 @@ class InputWrapper(object): while line: yield line line = self.readline() - diff --git a/koji/ssl/SSLCommon.py b/koji/ssl/SSLCommon.py index 5cb722d8..0d3fb947 100644 --- a/koji/ssl/SSLCommon.py +++ b/koji/ssl/SSLCommon.py @@ -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)) - diff --git a/koji/ssl/SSLConnection.py b/koji/ssl/SSLConnection.py index 1bb9e762..5a45095b 100644 --- a/koji/ssl/SSLConnection.py +++ b/koji/ssl/SSLConnection.py @@ -156,4 +156,3 @@ class PlgFileObject(socket._fileobject): self._sock.close() finally: self._sock = None - diff --git a/koji/ssl/XMLRPCServerProxy.py b/koji/ssl/XMLRPCServerProxy.py index 40b174d6..16de6195 100644 --- a/koji/ssl/XMLRPCServerProxy.py +++ b/koji/ssl/XMLRPCServerProxy.py @@ -176,4 +176,3 @@ if __name__ == '__main__': except KeyboardInterrupt: os._exit(0) print "All done. (%d timed out)" % tm.get() - diff --git a/koji/tasks.py b/koji/tasks.py index 46a70994..0d9a0030 100644 --- a/koji/tasks.py +++ b/koji/tasks.py @@ -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, diff --git a/koji/util.py b/koji/util.py index e7c95a7e..80e511f3 100644 --- a/koji/util.py +++ b/koji/util.py @@ -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, diff --git a/plugins/messagebus.py b/plugins/messagebus.py index 2b23fe5b..3f3dc6f7 100644 --- a/plugins/messagebus.py +++ b/plugins/messagebus.py @@ -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): diff --git a/plugins/runroot.py b/plugins/runroot.py index 427d483d..86b6892b 100644 --- a/plugins/runroot.py +++ b/plugins/runroot.py @@ -319,5 +319,3 @@ class RunRootTask(tasks.BaseTaskHandler): os.unlink(fn) except OSError: pass - - diff --git a/util/koji-gc b/util/koji-gc index 871c7f89..2d61aa49 100755 --- a/util/koji-gc +++ b/util/koji-gc @@ -957,4 +957,3 @@ if __name__ == "__main__": pass if not options.skip_main: sys.exit(rv) - diff --git a/util/koji-shadow b/util/koji-shadow index cdeef8cd..3b62776a 100755 --- a/util/koji-shadow +++ b/util/koji-shadow @@ -1328,4 +1328,3 @@ if __name__ == "__main__": except: pass sys.exit(rv) - diff --git a/util/kojira b/util/kojira index fe827bee..c18f63b6 100755 --- a/util/kojira +++ b/util/kojira @@ -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'] diff --git a/vm/kojikamid.py b/vm/kojikamid.py index c142267a..15c05700 100755 --- a/vm/kojikamid.py +++ b/vm/kojikamid.py @@ -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) diff --git a/www/kojiweb/index.py b/www/kojiweb/index.py index ff15a5f5..4be6131f 100644 --- a/www/kojiweb/index.py +++ b/www/kojiweb/index.py @@ -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: diff --git a/www/lib/kojiweb/util.py b/www/lib/kojiweb/util.py index 89a5ddbc..2168e0ea 100644 --- a/www/lib/kojiweb/util.py +++ b/www/lib/kojiweb/util.py @@ -225,7 +225,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 +245,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 +265,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 +277,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 +294,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 +402,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): @@ -580,4 +580,3 @@ a network issue or load issues on the server.""" else: str = "An error has occurred while processing your request." return str, level - From 815098643d10ddc835b798a02dc592b568d1ace0 Mon Sep 17 00:00:00 2001 From: Mike McLean Date: Mon, 11 May 2015 17:09:29 -0400 Subject: [PATCH 12/33] fix a couple old typos found by pylint --- hub/kojihub.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hub/kojihub.py b/hub/kojihub.py index bea724dc..0fa33e04 100644 --- a/hub/kojihub.py +++ b/hub/kojihub.py @@ -4951,7 +4951,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): @@ -7481,7 +7481,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: From 5dc14ced0b3020bf23e600bb3b7034d267b571c1 Mon Sep 17 00:00:00 2001 From: Mike McLean Date: Mon, 11 May 2015 17:35:06 -0400 Subject: [PATCH 13/33] fix useless-else-on-loop warnings #pylint --- hub/kojihub.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/hub/kojihub.py b/hub/kojihub.py index 0fa33e04..3da3dc83 100644 --- a/hub/kojihub.py +++ b/hub/kojihub.py @@ -3896,8 +3896,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 @@ -4875,11 +4875,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): """ From 23630cade831a8c63b5d0524d7cf6358a1a0792f Mon Sep 17 00:00:00 2001 From: Mike McLean Date: Mon, 11 May 2015 17:37:05 -0400 Subject: [PATCH 14/33] actually raise intended exceptions #pylint --- hub/kojihub.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hub/kojihub.py b/hub/kojihub.py index 3da3dc83..cb99196a 100644 --- a/hub/kojihub.py +++ b/hub/kojihub.py @@ -7671,7 +7671,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) @@ -10282,7 +10282,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): From a6019a92d7bbba52323bcf03ec857632bc24cc26 Mon Sep 17 00:00:00 2001 From: Mike McLean Date: Mon, 11 May 2015 17:41:17 -0400 Subject: [PATCH 15/33] import no longer used #pylint --- hub/kojihub.py | 1 - 1 file changed, 1 deletion(-) diff --git a/hub/kojihub.py b/hub/kojihub.py index cb99196a..aeae2556 100644 --- a/hub/kojihub.py +++ b/hub/kojihub.py @@ -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 From 5e09ebab4842413628916904d9c5c14283eddd93 Mon Sep 17 00:00:00 2001 From: Mike McLean Date: Mon, 11 May 2015 17:46:18 -0400 Subject: [PATCH 16/33] fix comment #pylint --- hub/kojihub.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hub/kojihub.py b/hub/kojihub.py index aeae2556..c3157399 100644 --- a/hub/kojihub.py +++ b/hub/kojihub.py @@ -252,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) From 82f6390957e5157a68f50c01fb5e232c2a440a06 Mon Sep 17 00:00:00 2001 From: Mike McLean Date: Mon, 11 May 2015 17:50:29 -0400 Subject: [PATCH 17/33] fix docstring #pylint --- hub/kojihub.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hub/kojihub.py b/hub/kojihub.py index c3157399..a9a44dd3 100644 --- a/hub/kojihub.py +++ b/hub/kojihub.py @@ -9394,7 +9394,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 From 28dc38a750a0bb4a75d5b0e00493b2ea5053b522 Mon Sep 17 00:00:00 2001 From: Mike McLean Date: Mon, 11 May 2015 17:57:46 -0400 Subject: [PATCH 18/33] more #pylint -- dangerous-default-value --- hub/kojihub.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/hub/kojihub.py b/hub/kojihub.py index a9a44dd3..fb0acf45 100644 --- a/hub/kojihub.py +++ b/hub/kojihub.py @@ -708,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 @@ -8369,7 +8373,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) From 27aa2f51e3ccbb7b5236daf8e8cb42f53daae071 Mon Sep 17 00:00:00 2001 From: Mathieu Bridon Date: Fri, 15 May 2015 16:56:27 +0200 Subject: [PATCH 19/33] files: Tell Apache to let clients follow symlinks This is necessary for builds to succeed, as they will try to access URLs like this: http://koji.fr/kojifiles/repos/$tag/$repoid/$arch/toplink/packages/... And on the filesystem, that "toplink" folder is in fact a symlink to the kojifiles root. --- hub/httpd.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hub/httpd.conf b/hub/httpd.conf index 5f480ea1..38e6b93b 100644 --- a/hub/httpd.conf +++ b/hub/httpd.conf @@ -29,7 +29,7 @@ Alias /kojihub /usr/share/koji-hub/kojixmlrpc.py Alias /kojifiles "/mnt/koji/" - Options Indexes + Options Indexes SymLinksIfOwnerMatch AllowOverride None Require all granted #If you have httpd <= 2.2, you'll want the following two lines instead From 1bf69b7c7eb58ed05debd89c395495064b491c8b Mon Sep 17 00:00:00 2001 From: Mike McLean Date: Fri, 15 May 2015 13:03:20 -0400 Subject: [PATCH 20/33] add a note about FollowSymLinks in hub httpd conf --- hub/httpd.conf | 3 +++ 1 file changed, 3 insertions(+) diff --git a/hub/httpd.conf b/hub/httpd.conf index 38e6b93b..de3f19f0 100644 --- a/hub/httpd.conf +++ b/hub/httpd.conf @@ -30,6 +30,9 @@ Alias /kojifiles "/mnt/koji/" 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 From d0c8088e5977367274e3507d3974b5708dafff7b Mon Sep 17 00:00:00 2001 From: Mike Bonnet Date: Fri, 5 Jun 2015 10:00:24 -0400 Subject: [PATCH 21/33] handle tasks with arch-specific and noarch subtasks (e.g. kernel) --- hub/kojihub.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/hub/kojihub.py b/hub/kojihub.py index fb0acf45..9cc8ff22 100644 --- a/hub/kojihub.py +++ b/hub/kojihub.py @@ -4768,7 +4768,7 @@ def merge_scratch(task_id): elif output.endswith('.log'): info['logs'].append(output) if not info['rpms']: - raise koji.ImportError, 'no arch-specific rpms produced by task %s' % child['id'] + continue if not info['logs']: raise koji.ImportError, 'task %s is missing logs' % child['id'] buildroots = query_buildroots(taskID=child['id'], @@ -4777,6 +4777,8 @@ def merge_scratch(task_id): 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) From e8fdc88a0b92c251c72754d47e484366d4697945 Mon Sep 17 00:00:00 2001 From: Ralph Bean Date: Sat, 6 Jun 2015 11:57:10 -0400 Subject: [PATCH 22/33] Add the hub plugin that accompanies the kojid runroot plugin. --- plugins/runroot_hub.py | 61 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 plugins/runroot_hub.py diff --git a/plugins/runroot_hub.py b/plugins/runroot_hub.py new file mode 100644 index 00000000..a666919d --- /dev/null +++ b/plugins/runroot_hub.py @@ -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) + From a76a125e9aa9e93da5e9162bc5867a0d93ad55cc Mon Sep 17 00:00:00 2001 From: Daniel Mach Date: Sat, 6 Jun 2015 15:36:02 -0400 Subject: [PATCH 23/33] Add CLI for runroot plugin. --- cli/koji | 65 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/cli/koji b/cli/koji index 8e0f7b2f..6cdecaae 100755 --- a/cli/koji +++ b/cli/koji @@ -6442,6 +6442,71 @@ 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] ") + 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:] + 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) + 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]") From cf96a3f0372c04f066f924e193ce97d4a9d3a025 Mon Sep 17 00:00:00 2001 From: Ralph Bean Date: Sat, 6 Jun 2015 15:36:03 -0400 Subject: [PATCH 24/33] Catch the case where the runroot plugin is not installed. --- cli/koji | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/cli/koji b/cli/koji index 6cdecaae..908d829e 100755 --- a/cli/koji +++ b/cli/koji @@ -6473,12 +6473,18 @@ def handle_runroot(options, session, args): command = ' '.join(args[2:]) else: command = args[2:] - 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) + 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 From 8ec72226d44fc7213c2154869bce8d95931d8406 Mon Sep 17 00:00:00 2001 From: Ralph Bean Date: Sat, 6 Jun 2015 15:53:50 -0400 Subject: [PATCH 25/33] Make the templated footer configurable. We ran into a problem[1] where old footers would cause syntax errors when interpreted as templates. This makes that behavior configurable and defaults to the old literal interpretation. [1] https://lists.fedoraproject.org/pipermail/buildsys/2015-May/004751.html --- www/conf/web.conf | 5 +++++ www/kojiweb/includes/footer.chtml | 5 +++++ www/kojiweb/wsgi_publisher.py | 1 + www/lib/kojiweb/util.py | 1 + 4 files changed, 12 insertions(+) diff --git a/www/conf/web.conf b/www/conf/web.conf index 171349b0..38f0b619 100644 --- a/www/conf/web.conf +++ b/www/conf/web.conf @@ -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 diff --git a/www/kojiweb/includes/footer.chtml b/www/kojiweb/includes/footer.chtml index 684e5a4c..8e220220 100644 --- a/www/kojiweb/includes/footer.chtml +++ b/www/kojiweb/includes/footer.chtml @@ -8,7 +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 +#else #include $localfooterpath +#end if #end if diff --git a/www/kojiweb/wsgi_publisher.py b/www/kojiweb/wsgi_publisher.py index 4f58b8e7..e7908152 100644 --- a/www/kojiweb/wsgi_publisher.py +++ b/www/kojiweb/wsgi_publisher.py @@ -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'], diff --git a/www/lib/kojiweb/util.py b/www/lib/kojiweb/util.py index 2168e0ea..8156d799 100644 --- a/www/lib/kojiweb/util.py +++ b/www/lib/kojiweb/util.py @@ -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) From 1929eb320fb9fc9cf438a45795b7899f065a54d6 Mon Sep 17 00:00:00 2001 From: Mike McLean Date: Mon, 18 May 2015 16:49:51 -0400 Subject: [PATCH 26/33] handle symlinks in BuildMavenTask._zip_dir() --- builder/kojid | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/builder/kojid b/builder/kojid index dcd08407..ad2cf1fe 100755 --- a/builder/kojid +++ b/builder/kojid @@ -1281,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): From f68099f584e149f3e8f8f26bf99436d248a0564c Mon Sep 17 00:00:00 2001 From: Mike McLean Date: Fri, 22 May 2015 12:45:22 -0400 Subject: [PATCH 27/33] fix more #pylint issues --- hub/kojixmlrpc.py | 2 +- koji/__init__.py | 4 ++-- koji/daemon.py | 2 +- koji/server.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/hub/kojixmlrpc.py b/hub/kojixmlrpc.py index 0e510122..efb99a60 100644 --- a/hub/kojixmlrpc.py +++ b/hub/kojixmlrpc.py @@ -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): diff --git a/koji/__init__.py b/koji/__init__.py index c3a0990e..e38a2088 100644 --- a/koji/__init__.py +++ b/koji/__init__.py @@ -2037,10 +2037,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) diff --git a/koji/daemon.py b/koji/daemon.py index d76355ba..0570faa5 100644 --- a/koji/daemon.py +++ b/koji/daemon.py @@ -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 : diff --git a/koji/server.py b/koji/server.py index 7cc8be85..52f13f53 100644 --- a/koji/server.py +++ b/koji/server.py @@ -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) From 554a2448761e7be5533c53786d1e370177b9e38e Mon Sep 17 00:00:00 2001 From: Mike McLean Date: Thu, 28 May 2015 17:35:21 -0400 Subject: [PATCH 28/33] Avoid errors logging anonymous retries (ticket 317) see: https://fedorahosted.org/koji/ticket/317 --- koji/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/koji/__init__.py b/koji/__init__.py index e38a2088..58971dab 100644 --- a/koji/__init__.py +++ b/koji/__init__.py @@ -1951,7 +1951,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 %s (%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) From d3eee76ce76bf989e00acff36b7788d9facfcc58 Mon Sep 17 00:00:00 2001 From: Brian Stinson Date: Sat, 6 Jun 2015 16:43:40 -0500 Subject: [PATCH 29/33] inject the url of the install tree into the kickstart used for generating images --- builder/kojid | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/builder/kojid b/builder/kojid index ad2cf1fe..b6b79c16 100755 --- a/builder/kojid +++ b/builder/kojid @@ -2841,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 @@ -2878,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): @@ -3327,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))) From 3260dbebdb9eeabcf95af01edd255d814d588a23 Mon Sep 17 00:00:00 2001 From: Dennis Gilmore Date: Sun, 10 May 2015 22:05:44 -0500 Subject: [PATCH 30/33] schema: add missing image channel from the default schema Signed-off-by: Dennis Gilmore --- docs/schema.sql | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/schema.sql b/docs/schema.sql index 3582b876..aad24a9c 100644 --- a/docs/schema.sql +++ b/docs/schema.sql @@ -183,6 +183,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 From 10a75fc22f6fd71b6f4c55212b2a3b4eaba84bf5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Sat, 10 Aug 2013 15:17:27 -0400 Subject: [PATCH 31/33] Add systemd unit file for kojid Signed-off-by: Dennis Gilmore --- Makefile | 3 ++- builder/Makefile | 19 +++++++++++++------ builder/kojid.service | 14 ++++++++++++++ koji.spec | 39 +++++++++++++++++++++++++++++++++++++-- 4 files changed, 66 insertions(+), 9 deletions(-) create mode 100644 builder/kojid.service diff --git a/Makefile b/Makefile index b9c23bf2..d5e9b776 100644 --- a/Makefile +++ b/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 diff --git a/builder/Makefile b/builder/Makefile index 02bae119..1de0a9cd 100644 --- a/builder/Makefile +++ b/builder/Makefile @@ -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) diff --git a/builder/kojid.service b/builder/kojid.service new file mode 100644 index 00000000..1886a440 --- /dev/null +++ b/builder/kojid.service @@ -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 diff --git a/koji.spec b/koji.spec index c2b1af40..82df76ed 100644 --- a/koji.spec +++ b/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 @@ -135,7 +152,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 @@ -192,8 +209,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 +222,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,6 +243,7 @@ if [ $1 = 0 ]; then /sbin/service kojid stop &> /dev/null /sbin/chkconfig --del kojid fi +%endif %files vm %defattr(-,root,root) From 7c435492cfd3786467403ca3b0fa90221dae9944 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Mon, 21 Apr 2014 14:45:40 -0400 Subject: [PATCH 32/33] Add systemd unit file for kojivmd Signed-off-by: Dennis Gilmore --- koji.spec | 24 ++++++++++++++++++++++++ vm/Makefile | 18 ++++++++++++------ vm/kojivmd.service | 14 ++++++++++++++ 3 files changed, 50 insertions(+), 6 deletions(-) create mode 100644 vm/kojivmd.service diff --git a/koji.spec b/koji.spec index 82df76ed..fbe04b7f 100644 --- a/koji.spec +++ b/koji.spec @@ -107,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 @@ -250,11 +256,28 @@ fi %{_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 @@ -263,6 +286,7 @@ if [ $1 = 0 ]; then /sbin/service kojivmd stop &> /dev/null /sbin/chkconfig --del kojivmd fi +%endif %post utils /sbin/chkconfig --add kojira diff --git a/vm/Makefile b/vm/Makefile index 1dcab17a..13e340cb 100644 --- a/vm/Makefile +++ b/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) diff --git a/vm/kojivmd.service b/vm/kojivmd.service new file mode 100644 index 00000000..a417fdbe --- /dev/null +++ b/vm/kojivmd.service @@ -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 From 9c69602a65441b574bb55b1b561cdeec96d1cfbd Mon Sep 17 00:00:00 2001 From: Dennis Gilmore Date: Sat, 6 Jun 2015 14:17:28 -0500 Subject: [PATCH 33/33] Add systemd unit file for kojira Signed-off-by: Dennis Gilmore --- koji.spec | 22 ++++++++++++++++++++++ util/Makefile | 23 ++++++++++++++++------- util/kojira.service | 14 ++++++++++++++ 3 files changed, 52 insertions(+), 7 deletions(-) create mode 100644 util/kojira.service diff --git a/koji.spec b/koji.spec index fbe04b7f..d1e41817 100644 --- a/koji.spec +++ b/koji.spec @@ -132,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 @@ -191,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 @@ -288,6 +297,18 @@ if [ $1 = 0 ]; then 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 || : @@ -296,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 - 1.9.0-1 diff --git a/util/Makefile b/util/Makefile index 79ac64fa..5ea8bded 100644 --- a/util/Makefile +++ b/util/Makefile @@ -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) diff --git a/util/kojira.service b/util/kojira.service new file mode 100644 index 00000000..beaea18a --- /dev/null +++ b/util/kojira.service @@ -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