Add image-build support
Signed-off-by: Lubos Kocman <lkocman@redhat.com>
This commit is contained in:
		
							parent
							
								
									4fb20198db
								
							
						
					
					
						commit
						8e90a2a32a
					
				| @ -211,6 +211,7 @@ def run_compose(compose): | |||||||
|     extrafiles_phase = pungi.phases.ExtraFilesPhase(compose, pkgset_phase) |     extrafiles_phase = pungi.phases.ExtraFilesPhase(compose, pkgset_phase) | ||||||
|     createiso_phase = pungi.phases.CreateisoPhase(compose) |     createiso_phase = pungi.phases.CreateisoPhase(compose) | ||||||
|     liveimages_phase = pungi.phases.LiveImagesPhase(compose) |     liveimages_phase = pungi.phases.LiveImagesPhase(compose) | ||||||
|  |     image_build_phase = pungi.phases.ImageBuildPhase(compose) | ||||||
|     test_phase = pungi.phases.TestPhase(compose) |     test_phase = pungi.phases.TestPhase(compose) | ||||||
| 
 | 
 | ||||||
|     # check if all config options are set |     # check if all config options are set | ||||||
| @ -279,9 +280,11 @@ def run_compose(compose): | |||||||
|     # CREATEISO and LIVEIMAGES phases |     # CREATEISO and LIVEIMAGES phases | ||||||
|     createiso_phase.start() |     createiso_phase.start() | ||||||
|     liveimages_phase.start() |     liveimages_phase.start() | ||||||
|  |     image_build_phase.start() | ||||||
| 
 | 
 | ||||||
|     createiso_phase.stop() |     createiso_phase.stop() | ||||||
|     liveimages_phase.stop() |     liveimages_phase.stop() | ||||||
|  |     image_build_phase.stop() | ||||||
| 
 | 
 | ||||||
|     # merge checksum files |     # merge checksum files | ||||||
|     for variant in compose.get_variants(types=["variant", "layered-product"]): |     for variant in compose.get_variants(types=["variant", "layered-product"]): | ||||||
|  | |||||||
| @ -467,3 +467,80 @@ Example | |||||||
|             'src': True |             'src': True | ||||||
|         }), |         }), | ||||||
|     ] |     ] | ||||||
|  | 
 | ||||||
|  | Image Build Settings | ||||||
|  | ==================== | ||||||
|  | 
 | ||||||
|  | **image_build** | ||||||
|  |     (*list*) -- config for koji image-build; format: [(variant_uid_regex, {arch|*: [{opt: value}])] | ||||||
|  | 
 | ||||||
|  | .. note:: | ||||||
|  |     Config can contain anything what is accepted by | ||||||
|  |     koji image-build --config configfile.ini | ||||||
|  |     Repo is currently the only option which is being automatically transformed | ||||||
|  |     into a string. | ||||||
|  | 
 | ||||||
|  |     Please don't set install_tree as it would get overriden by pungi. | ||||||
|  |     The 'format' attr is [('image_type', 'image_suffix'), ...]. | ||||||
|  |     productmd should ideally contain all of image types and suffixes. | ||||||
|  | 
 | ||||||
|  | Example | ||||||
|  | ------- | ||||||
|  | :: | ||||||
|  | 
 | ||||||
|  |     image_build = [ | ||||||
|  |         ('^Server$', { | ||||||
|  |             'x86_64': [ | ||||||
|  |                 { | ||||||
|  |                     'format': [('docker', 'tar.gz'), ('qcow2', 'qcow2')] | ||||||
|  |                     'name': 'fedora-qcow-and-docker-base', | ||||||
|  |                     'target': 'koji-target-name', | ||||||
|  |                     'ksversion': 'F23', # value from pykickstart | ||||||
|  |                     'version': '23', | ||||||
|  |                     'ksurl': 'https://git.fedorahosted.org/git/spin-kickstarts.git?somedirectoryifany#HEAD', | ||||||
|  |                     'kickstart': "fedora-docker-base.ks", | ||||||
|  |                     'repo': ["http://someextrarepos.org/repo", "ftp://rekcod.oi/repo]. | ||||||
|  |     #               'install_tree': 'http://sometpath',  # this is set automatically by pungi to os_dir for given variant/$arch  | ||||||
|  |                     'distro': 'Fedora-20', | ||||||
|  |                     'disk_size': 3 | ||||||
|  |                 }, | ||||||
|  |                 { | ||||||
|  |                     'format': [('qcow2','qcow2')] | ||||||
|  |                     'name': 'fedora-qcow-base', | ||||||
|  |                     'target': 'koji-target-name', | ||||||
|  |                     'ksversion': 'F23', # value from pykickstart | ||||||
|  |                     'version': '23', | ||||||
|  |                     'ksurl': 'https://git.fedorahosted.org/git/spin-kickstarts.git?somedirectoryifany#HEAD', | ||||||
|  |                     'kickstart': "fedora-docker-base.ks", | ||||||
|  |                     'distro': 'Fedora-23' | ||||||
|  |                 } | ||||||
|  |             ] | ||||||
|  |        }), | ||||||
|  |     ] | ||||||
|  | 
 | ||||||
|  | Translate Paths Settings | ||||||
|  | ======================== | ||||||
|  | 
 | ||||||
|  | **translate_paths** | ||||||
|  |     (*list*) -- list of paths to translate; format: [(path,translated_path)] | ||||||
|  | 
 | ||||||
|  | .. note:: | ||||||
|  |     This feature becomes useful when you need to transform compose location | ||||||
|  |     into e.g. a http repo which is can be passed to koji image-build. | ||||||
|  |     Translation needs to be invoked by a function call in pungi. | ||||||
|  |     os.path.normpath() is applied on both path and translated_path | ||||||
|  |      | ||||||
|  | 
 | ||||||
|  | Example config | ||||||
|  | -------------- | ||||||
|  | :: | ||||||
|  |     translate_paths = [ | ||||||
|  |         ("/mnt/a", "http://b/dir"), | ||||||
|  |     ] | ||||||
|  | 
 | ||||||
|  | Example usage | ||||||
|  | ------------- | ||||||
|  | :: | ||||||
|  |     >>> from pungi.paths import translate_paths | ||||||
|  |     >>> print translate_paths(compose_object_with_mapping, "/mnt/a/c/somefile") | ||||||
|  |     http://b/dir/c/somefile | ||||||
|  | |||||||
| @ -307,6 +307,34 @@ class WorkPaths(object): | |||||||
|         path = os.path.join(path, file_name) |         path = os.path.join(path, file_name) | ||||||
|         return path |         return path | ||||||
| 
 | 
 | ||||||
|  |     def image_build_dir(self, arch, variant, create_dir=True): | ||||||
|  |         """ | ||||||
|  |         @param arch | ||||||
|  |         @param variant | ||||||
|  |         @param create_dir=True | ||||||
|  | 
 | ||||||
|  |         Examples: | ||||||
|  |             work/x86_64/Server/image-build | ||||||
|  |         """ | ||||||
|  |         path = os.path.join(self.topdir(arch, create_dir=create_dir), variant.uid, "image-build") | ||||||
|  |         if create_dir: | ||||||
|  |             makedirs(path) | ||||||
|  |         return path | ||||||
|  | 
 | ||||||
|  |     def image_build_conf(self, arch, variant, image_name, image_type, create_dir=True): | ||||||
|  |         """ | ||||||
|  |         @param arch | ||||||
|  |         @param variant | ||||||
|  |         @param image-name | ||||||
|  |         @param image-type (e.g docker) | ||||||
|  |         @param create_dir=True | ||||||
|  | 
 | ||||||
|  |         Examples: | ||||||
|  |             work/x86_64/Server/image-build/docker_rhel-server-docker.cfg | ||||||
|  |         """ | ||||||
|  |         path = os.path.join(self.image_build_dir(arch, variant), "%s_%s.cfg" % (image_type, image_name)) | ||||||
|  |         return path | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| class ComposePaths(object): | class ComposePaths(object): | ||||||
|     def __init__(self, compose): |     def __init__(self, compose): | ||||||
| @ -511,6 +539,45 @@ class ComposePaths(object): | |||||||
|         result = os.path.join(path, file_name) |         result = os.path.join(path, file_name) | ||||||
|         return result |         return result | ||||||
| 
 | 
 | ||||||
|  |     def image_dir(self, arch, variant, symlink_to=None, create_dir=True, relative=False): | ||||||
|  |         """ | ||||||
|  |         Examples: | ||||||
|  |             compose/Server/x86_64/images | ||||||
|  |             None | ||||||
|  |         @param arch | ||||||
|  |         @param variant | ||||||
|  |         @param symlink_to=None | ||||||
|  |         @param create_dir=True | ||||||
|  |         @param relative=False | ||||||
|  |         """ | ||||||
|  |         # skip optional, addons and src architecture | ||||||
|  |         if variant.type != "variant": | ||||||
|  |             return None | ||||||
|  |         if arch == "src": | ||||||
|  |             return None | ||||||
|  | 
 | ||||||
|  |         path = os.path.join(self.topdir(arch, variant, create_dir=create_dir, relative=relative), "images") | ||||||
|  |         if symlink_to: | ||||||
|  |             topdir = self.compose.topdir.rstrip("/") + "/" | ||||||
|  |             relative_dir = path[len(topdir):] | ||||||
|  |             target_dir = os.path.join(symlink_to, self.compose.compose_id, relative_dir) | ||||||
|  |             if create_dir and not relative: | ||||||
|  |                 makedirs(target_dir) | ||||||
|  |             try: | ||||||
|  |                 os.symlink(target_dir, path) | ||||||
|  |             except OSError as ex: | ||||||
|  |                 if ex.errno != errno.EEXIST: | ||||||
|  |                     raise | ||||||
|  |                 msg = "Symlink pointing to '%s' expected: %s" % (target_dir, path) | ||||||
|  |                 if not os.path.islink(path): | ||||||
|  |                     raise RuntimeError(msg) | ||||||
|  |                 if os.path.abspath(os.readlink(path)) != target_dir: | ||||||
|  |                     raise RuntimeError(msg) | ||||||
|  |         else: | ||||||
|  |             if create_dir and not relative: | ||||||
|  |                 makedirs(path) | ||||||
|  |         return path | ||||||
|  | 
 | ||||||
|     def jigdo_dir(self, arch, variant, create_dir=True, relative=False): |     def jigdo_dir(self, arch, variant, create_dir=True, relative=False): | ||||||
|         """ |         """ | ||||||
|         Examples: |         Examples: | ||||||
|  | |||||||
| @ -25,4 +25,5 @@ from buildinstall import BuildinstallPhase  # noqa | |||||||
| from extra_files import ExtraFilesPhase  # noqa | from extra_files import ExtraFilesPhase  # noqa | ||||||
| from createiso import CreateisoPhase  # noqa | from createiso import CreateisoPhase  # noqa | ||||||
| from live_images import LiveImagesPhase  # noqa | from live_images import LiveImagesPhase  # noqa | ||||||
|  | from image_build import ImageBuildPhase  # noqa | ||||||
| from test import TestPhase  # noqa | from test import TestPhase  # noqa | ||||||
|  | |||||||
							
								
								
									
										166
									
								
								pungi/phases/image_build.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										166
									
								
								pungi/phases/image_build.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,166 @@ | |||||||
|  | # -*- coding: utf-8 -*- | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | import os | ||||||
|  | import time | ||||||
|  | import pipes | ||||||
|  | 
 | ||||||
|  | from pungi.util import get_arch_variant_data | ||||||
|  | from pungi.phases.base import PhaseBase | ||||||
|  | from pungi.linker import Linker | ||||||
|  | from pungi.paths import translate_path | ||||||
|  | from pungi.wrappers.kojiwrapper import KojiWrapper | ||||||
|  | from pungi.wrappers.iso import IsoWrapper | ||||||
|  | from kobo.shortcuts import run, read_checksum_file | ||||||
|  | from kobo.threads import ThreadPool, WorkerThread | ||||||
|  | from productmd.images import Image | ||||||
|  | 
 | ||||||
|  | class ImageBuildPhase(PhaseBase): | ||||||
|  |     """class for wrapping up koji image-build""" | ||||||
|  |     name = "image_build" | ||||||
|  | 
 | ||||||
|  |     def __init__(self, compose): | ||||||
|  |         PhaseBase.__init__(self, compose) | ||||||
|  |         self.pool = ThreadPool(logger=self.compose._logger) | ||||||
|  | 
 | ||||||
|  |     def skip(self): | ||||||
|  |         if PhaseBase.skip(self): | ||||||
|  |             return True | ||||||
|  |         if not self.compose.conf.get(self.name): | ||||||
|  |             self.log_info("Section '%s' was not found. Skipping" % self.name) | ||||||
|  |             return True | ||||||
|  |         return False | ||||||
|  | 
 | ||||||
|  |     def run(self): | ||||||
|  |         for arch in self.compose.get_arches(): # src will be skipped | ||||||
|  |             for variant in self.compose.get_variants(arch=arch): | ||||||
|  |                 image_build_data = get_arch_variant_data(self.compose.conf, self.name, arch, variant) | ||||||
|  |                 for image_conf in image_build_data: | ||||||
|  |                     image_conf["arches"] = arch # passed to get_image_build_cmd as dict | ||||||
|  |                     image_conf["variant"] = variant # ^ | ||||||
|  |                     image_conf["install_tree"] = translate_path(self.compose, self.compose.paths.compose.os_tree(arch, variant)) # ^ | ||||||
|  |                     format = image_conf["format"] # transform format into right 'format' for image-build | ||||||
|  |                     image_conf["format"] = ",".join([x[0] for x in image_conf["format"]]) # 'docker,qcow2' | ||||||
|  |                     if image_conf.has_key("repos") and not isinstance(image_conf["repos"], str): | ||||||
|  |                         image_conf["repos"] = ",".join(image_conf["repos"]) # supply repos as str separated by , instead of list | ||||||
|  |                     cmd = { | ||||||
|  |                         "format": format, | ||||||
|  |                         "image_conf": image_conf, | ||||||
|  |                         "conf_file": self.compose.paths.work.image_build_conf(image_conf["arches"], image_conf['variant'], image_name=image_conf['name'], image_type=image_conf['format'].replace(",", "-")), | ||||||
|  |                         "image_dir": self.compose.paths.compose.image_dir(arch, variant), | ||||||
|  |                         "relative_image_dir": self.compose.paths.compose.image_dir(arch, variant, create_dir=False, relative=True), | ||||||
|  |                         "link_type": self.compose.conf.get("link_type", "hardlink-or-copy") | ||||||
|  |                     } | ||||||
|  |                     self.pool.add(CreateImageBuildThread(self.pool)) | ||||||
|  |                     self.pool.queue_put((self.compose, cmd)) | ||||||
|  |         self.pool.start() | ||||||
|  | 
 | ||||||
|  |     def stop(self, *args, **kwargs): | ||||||
|  |         PhaseBase.stop(self, *args, **kwargs) | ||||||
|  |         if self.skip(): | ||||||
|  |             return | ||||||
|  | 
 | ||||||
|  | class CreateImageBuildThread(WorkerThread): | ||||||
|  |     def fail(self, compose, cmd): | ||||||
|  |         compose.log_error("CreateImageBuild failed.") | ||||||
|  | 
 | ||||||
|  |     def process(self, item, num): | ||||||
|  |         compose, cmd = item | ||||||
|  |         mounts = [compose.topdir] | ||||||
|  |         if "mount" in cmd: | ||||||
|  |             mounts.append(cmd["mount"]) | ||||||
|  |         runroot = compose.conf.get("runroot", False) | ||||||
|  |         log_file = compose.paths.log.log_file(cmd["image_conf"]["arches"], "imagebuild-%s-%s-%s" % (cmd["image_conf"]["arches"], cmd["image_conf"]["variant"], cmd['image_conf']['format'].replace(",","-"))) | ||||||
|  |         msg = "Creating %s image (arch: %s, variant: %s)" % (cmd["image_conf"]["format"].replace(",","-"), cmd["image_conf"]["arches"], cmd["image_conf"]["variant"]) | ||||||
|  |         self.pool.log_info("[BEGIN] %s" % msg) | ||||||
|  | 
 | ||||||
|  |         koji_wrapper = KojiWrapper(compose.conf["koji_profile"]) | ||||||
|  |         # paths module doesn't hold compose object, so we have to generate path here | ||||||
|  | 
 | ||||||
|  |         # writes conf file for koji image-build | ||||||
|  |         self.pool.log_info("Writing image-build config for %s.%s into %s" % (cmd["image_conf"]["variant"], cmd["image_conf"]["arches"], cmd["conf_file"])) | ||||||
|  |         koji_cmd = koji_wrapper.get_image_build_cmd(cmd['image_conf'], conf_file_dest=cmd["conf_file"], wait=True, scratch=False) | ||||||
|  | 
 | ||||||
|  |         # avoid race conditions? | ||||||
|  |         # Kerberos authentication failed: Permission denied in replay cache code (-1765328215) | ||||||
|  |         time.sleep(num * 3) | ||||||
|  |         output = koji_wrapper.run_create_image_cmd(koji_cmd, log_file=log_file) | ||||||
|  |         self.pool.log_debug("build-image outputs: %s" % (output)) | ||||||
|  |         if output["retcode"] != 0: | ||||||
|  |             self.fail(compose, cmd) | ||||||
|  |             raise RuntimeError("ImageBuild task failed: %s. See %s for more details." % (output["task_id"], log_file)) | ||||||
|  | 
 | ||||||
|  |         # copy image to images/ | ||||||
|  |         image_infos = [] | ||||||
|  | 
 | ||||||
|  |         for filename in koji_wrapper.get_image_path(output["task_id"]): | ||||||
|  |             # format is list of tuples [('qcow2', '.qcow2'), ('raw-xz', 'raw.xz'),] | ||||||
|  |             for format, suffix in cmd['format']: | ||||||
|  |                 if filename.endswith(suffix): | ||||||
|  |                     image_infos.append({'filename': filename, 'suffix': suffix, 'type': format}) # the type/format ... image-build has it wrong | ||||||
|  | 
 | ||||||
|  |         if len(image_infos) != len(cmd['format']): | ||||||
|  |             self.pool.log_error("Error in koji task %s. Expected to find same amount of images as in suffixes attr in image-build (%s). Got '%s'." % | ||||||
|  |                 (output["task_id"], len(cmd['image_conf']['format']), len(image_infos))) | ||||||
|  |             self.fail(compose, cmd) | ||||||
|  | 
 | ||||||
|  |         # The usecase here is that you can run koji image-build with multiple --format | ||||||
|  |         # It's ok to do it serialized since we're talking about max 2 images per single | ||||||
|  |         # image_build record | ||||||
|  |         linker = Linker(logger=compose._logger) | ||||||
|  |         for image_info in image_infos: | ||||||
|  |             # let's not change filename of koji outputs | ||||||
|  |             image_dest = os.path.join(cmd["image_dir"], os.path.basename(image_info['filename'])) | ||||||
|  |             linker.link(image_info['filename'], image_dest, link_type=cmd["link_type"]) | ||||||
|  | 
 | ||||||
|  |             iso = IsoWrapper(logger=compose._logger) # required for checksums only | ||||||
|  |             checksum_cmd = ["cd %s" % pipes.quote(os.path.dirname(image_dest))] | ||||||
|  |             checksum_cmd.extend(iso.get_checksum_cmds(os.path.basename(image_dest))) | ||||||
|  |             checksum_cmd = " && ".join(checksum_cmd) | ||||||
|  | 
 | ||||||
|  |             if runroot: | ||||||
|  |                 packages = ["coreutils", "genisoimage", "isomd5sum", "jigdo", "strace", "lsof"] | ||||||
|  |                 runroot_channel = compose.conf.get("runroot_channel", None) | ||||||
|  |                 runroot_tag = compose.conf["runroot_tag"] | ||||||
|  |                 koji_cmd = koji_wrapper.get_runroot_cmd(runroot_tag, cmd["image_conf"]["arches"], checksum_cmd, channel=runroot_channel, use_shell=True, task_id=True, packages=packages, mounts=mounts) | ||||||
|  | 
 | ||||||
|  |                 # avoid race conditions? | ||||||
|  |                 # Kerberos authentication failed: Permission denied in replay cache code (-1765328215) | ||||||
|  |                 time.sleep(num * 3) | ||||||
|  | 
 | ||||||
|  |                 output = koji_wrapper.run_runroot_cmd(koji_cmd, log_file=log_file) | ||||||
|  |                 if output["retcode"] != 0: | ||||||
|  |                     self.fail(compose, cmd) | ||||||
|  |                     raise RuntimeError("Runroot task failed: %s. See %s for more details." % (output["task_id"], log_file)) | ||||||
|  | 
 | ||||||
|  |             else: | ||||||
|  |                 # run locally | ||||||
|  |                 try: | ||||||
|  |                     run(checksum_cmd, show_cmd=True, logfile=log_file) | ||||||
|  |                 except: | ||||||
|  |                     self.fail(compose, cmd) | ||||||
|  |                     raise | ||||||
|  | 
 | ||||||
|  |             # Update image manifest | ||||||
|  |             img = Image(compose.im) | ||||||
|  |             img.type = image_info['type'] | ||||||
|  |             img.format = image_info['suffix'] | ||||||
|  |             img.path = os.path.join(cmd["relative_image_dir"], os.path.basename(image_dest)) | ||||||
|  |             img.mtime = int(os.stat(image_dest).st_mtime) | ||||||
|  |             img.size = os.path.getsize(image_dest) | ||||||
|  |             img.arch = cmd["image_conf"]["arches"] # arches should be always single arch | ||||||
|  |             img.disc_number = 1 # We don't expect multiple disks | ||||||
|  |             img.disc_count = 1 | ||||||
|  |             for checksum_type in ("md5", "sha1", "sha256"): | ||||||
|  |                 checksum_path = image_dest + ".%sSUM" % checksum_type.upper() | ||||||
|  |                 checksum_value = None | ||||||
|  |                 if os.path.isfile(checksum_path): | ||||||
|  |                     checksum_value, image_name = read_checksum_file(checksum_path)[0] | ||||||
|  |                     if image_name != os.path.basename(img.path): | ||||||
|  |                         raise ValueError("Image name doesn't match checksum: %s" % checksum_path) | ||||||
|  |                 img.add_checksum(compose.paths.compose.topdir(), checksum_type=checksum_type, checksum_value=checksum_value) | ||||||
|  |             img.bootable = False | ||||||
|  |             # named keywords due portability (old productmd used arch, variant ... while new one uses variant, arch | ||||||
|  |             compose.im.add(variant=cmd["image_conf"]["variant"].uid, arch=cmd["image_conf"]["arches"], image=img) | ||||||
|  | 
 | ||||||
|  |         self.pool.log_info("[DONE ] %s" % msg) | ||||||
| @ -22,6 +22,7 @@ import re | |||||||
| import koji | import koji | ||||||
| import rpmUtils.arch | import rpmUtils.arch | ||||||
| from kobo.shortcuts import run | from kobo.shortcuts import run | ||||||
|  | from ConfigParser import ConfigParser | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class KojiWrapper(object): | class KojiWrapper(object): | ||||||
| @ -97,6 +98,35 @@ class KojiWrapper(object): | |||||||
|         } |         } | ||||||
|         return result |         return result | ||||||
| 
 | 
 | ||||||
|  |     def get_image_build_cmd(self, config_options, conf_file_dest, wait=True, scratch=False): | ||||||
|  |         """ | ||||||
|  |         @param config_options | ||||||
|  |         @param conf_file_dest -  a destination in compose workdir for the conf file to be written | ||||||
|  |         @param wait=True | ||||||
|  |         @param scratch=False | ||||||
|  |         """ | ||||||
|  |         # Usage: koji image-build [options] <name> <version> <target> <install-tree-url> <arch> [<arch>...] | ||||||
|  |         sub_command = "image-build" | ||||||
|  |         # The minimum set of options | ||||||
|  |         min_options = ("name", "version", "target", "install_tree", "arches", "format", "kickstart", "ksurl", "distro") | ||||||
|  |         assert set(min_options).issubset(set(config_options.keys())), "image-build requires at least %s got '%s'" % (", ".join(min_options), config_options) | ||||||
|  |         cfg_parser = ConfigParser() | ||||||
|  |         cfg_parser.add_section(sub_command) | ||||||
|  |         for option, value in config_options.iteritems(): | ||||||
|  |             cfg_parser.set(sub_command, option, value) | ||||||
|  | 
 | ||||||
|  |         fd = open(conf_file_dest, "w") | ||||||
|  |         cfg_parser.write(fd) | ||||||
|  |         fd.close() | ||||||
|  | 
 | ||||||
|  |         cmd = [self.executable, sub_command, "--config=%s" % conf_file_dest] | ||||||
|  |         if wait: | ||||||
|  |             cmd.append("--wait") | ||||||
|  |         if scratch: | ||||||
|  |             cmd.append("--scratch") | ||||||
|  | 
 | ||||||
|  |         return cmd | ||||||
|  | 
 | ||||||
|     def get_create_image_cmd(self, name, version, target, arch, ks_file, repos, image_type="live", image_format=None, release=None, wait=True, archive=False, specfile=None): |     def get_create_image_cmd(self, name, version, target, arch, ks_file, repos, image_type="live", image_format=None, release=None, wait=True, archive=False, specfile=None): | ||||||
|         # Usage: koji spin-livecd [options] <name> <version> <target> <arch> <kickstart-file> |         # Usage: koji spin-livecd [options] <name> <version> <target> <arch> <kickstart-file> | ||||||
|         # Usage: koji spin-appliance [options] <name> <version> <target> <arch> <kickstart-file> |         # Usage: koji spin-appliance [options] <name> <version> <target> <arch> <kickstart-file> | ||||||
| @ -163,12 +193,14 @@ class KojiWrapper(object): | |||||||
| 
 | 
 | ||||||
|     def run_create_image_cmd(self, command, log_file=None): |     def run_create_image_cmd(self, command, log_file=None): | ||||||
|         # spin-{livecd,appliance} is blocking by default -> you probably want to run it in a thread |         # spin-{livecd,appliance} is blocking by default -> you probably want to run it in a thread | ||||||
| 
 |         try: | ||||||
|             retcode, output = run(command, can_fail=True, logfile=log_file) |             retcode, output = run(command, can_fail=True, logfile=log_file) | ||||||
|  |         except RuntimeError, e: | ||||||
|  |             raise RuntimeError("%s. %s failed with '%s'" % (e, command, output)) | ||||||
| 
 | 
 | ||||||
|         match = re.search(r"Created task: (\d+)", output) |         match = re.search(r"Created task: (\d+)", output) | ||||||
|         if not match: |         if not match: | ||||||
|             raise RuntimeError("Could not find task ID in output") |             raise RuntimeError("Could not find task ID in output. Command '%s' returned '%s'." % (" ".join(command), output)) | ||||||
| 
 | 
 | ||||||
|         result = { |         result = { | ||||||
|             "retcode": retcode, |             "retcode": retcode, | ||||||
| @ -179,7 +211,6 @@ class KojiWrapper(object): | |||||||
| 
 | 
 | ||||||
|     def get_image_path(self, task_id): |     def get_image_path(self, task_id): | ||||||
|         result = [] |         result = [] | ||||||
|         # XXX: hardcoded URL |  | ||||||
|         koji_proxy = self.koji_module.ClientSession(self.koji_module.config.server) |         koji_proxy = self.koji_module.ClientSession(self.koji_module.config.server) | ||||||
|         task_info_list = [] |         task_info_list = [] | ||||||
|         task_info_list.append(koji_proxy.getTaskInfo(task_id, request=True)) |         task_info_list.append(koji_proxy.getTaskInfo(task_id, request=True)) | ||||||
| @ -188,7 +219,7 @@ class KojiWrapper(object): | |||||||
|         # scan parent and child tasks for certain methods |         # scan parent and child tasks for certain methods | ||||||
|         task_info = None |         task_info = None | ||||||
|         for i in task_info_list: |         for i in task_info_list: | ||||||
|             if i["method"] in ("createAppliance", "createLiveCD"): |             if i["method"] in ("createAppliance", "createLiveCD", 'createImage'): | ||||||
|                 task_info = i |                 task_info = i | ||||||
|                 break |                 break | ||||||
| 
 | 
 | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user