def partition_mount_test(self): """Test PartitionMount context manager (requires loop)""" with tempfile.NamedTemporaryFile(prefix="lorax.test.disk.") as disk_img: self.assertTrue(mkfakediskimg(disk_img.name)) # Make sure it can mount the / with /etc/passwd with PartitionMount(disk_img.name) as img_mount: self.assertTrue(img_mount is not None) self.assertTrue(os.path.isdir(img_mount.mount_dir)) self.assertTrue(os.path.exists(joinpaths(img_mount.mount_dir, "/etc/passwd"))) # Make sure submount works with PartitionMount(disk_img.name, submount="/a-sub-mount/") as img_mount: self.assertTrue(img_mount is not None) self.assertTrue(os.path.isdir(img_mount.mount_dir)) self.assertTrue(os.path.exists(joinpaths(img_mount.mount_dir, "/etc/passwd"))) # Make sure it can mount the /boot partition with a custom mount_ok function def mount_ok(mount_dir): kernels = glob.glob(joinpaths(mount_dir, "vmlinuz-*")) return len(kernels) > 0 with PartitionMount(disk_img.name, mount_ok=mount_ok) as img_mount: self.assertTrue(img_mount is not None) self.assertTrue(os.path.isdir(img_mount.mount_dir)) self.assertFalse(os.path.exists(joinpaths(img_mount.mount_dir, "/etc/passwd"))) self.assertTrue(os.path.exists(joinpaths(img_mount.mount_dir, "vmlinuz-4.18.13-200.fc28.x86_64"))) self.assertTrue(os.path.exists(joinpaths(img_mount.mount_dir, "initramfs-4.18.13-200.fc28.x86_64.img")))
def virt_install(opts, install_log, disk_img, disk_size, cancel_func=None): """ Use qemu to install to a disk image :param opts: options passed to livemedia-creator :type opts: argparse options :param str install_log: The path to write the log from qemu :param str disk_img: The full path to the disk image to be created :param int disk_size: The size of the disk_img in MiB :param cancel_func: Function that returns True to cancel build :type cancel_func: function This uses qemu with a boot.iso and a kickstart to create a disk image and then optionally, based on the opts passed, creates tarfile. """ iso_mount = IsoMountpoint(opts.iso, opts.location) if not iso_mount.stage2: iso_mount.umount() raise InstallError("ISO is missing stage2, cannot continue") log_monitor = LogMonitor(install_log, timeout=opts.timeout) cancel_funcs = [log_monitor.server.log_check] if cancel_func is not None: cancel_funcs.append(cancel_func) kernel_args = "" if opts.kernel_args: kernel_args += opts.kernel_args if opts.proxy: kernel_args += " proxy=" + opts.proxy if opts.image_type and not opts.make_fsimage: qemu_args = [] for arg in opts.qemu_args: qemu_args += arg.split(" ", 1) if "-f" not in qemu_args: qemu_args += ["-f", opts.image_type] mkqemu_img(disk_img, disk_size * 1024**2, qemu_args) if opts.make_fsimage or opts.make_tar or opts.make_oci: diskimg_path = tempfile.mktemp(prefix="lmc-disk-", suffix=".img") else: diskimg_path = disk_img try: QEMUInstall(opts, iso_mount, opts.ks, diskimg_path, disk_size, kernel_args, opts.ram, opts.vcpus, opts.vnc, opts.arch, cancel_func=lambda: any(f() for f in cancel_funcs), virtio_host=log_monitor.host, virtio_port=log_monitor.port, image_type=opts.image_type, boot_uefi=opts.virt_uefi, ovmf_path=opts.ovmf_path) log_monitor.shutdown() except InstallError as e: log.error("VirtualInstall failed: %s", e) raise finally: log.info("unmounting the iso") iso_mount.umount() if log_monitor.server.log_check(): if not log_monitor.server.error_line and opts.timeout: msg = "virt_install failed due to timeout" else: msg = "virt_install failed on line: %s" % log_monitor.server.error_line raise InstallError(msg) elif cancel_func and cancel_func(): raise InstallError("virt_install canceled by cancel_func") if opts.make_fsimage: mkfsimage_from_disk(diskimg_path, disk_img, disk_size, label=opts.fs_label) os.unlink(diskimg_path) elif opts.make_tar: compress_args = [] for arg in opts.compress_args: compress_args += arg.split(" ", 1) with PartitionMount(diskimg_path) as img_mount: if img_mount and img_mount.mount_dir: rc = mktar(img_mount.mount_dir, disk_img, opts.compression, compress_args) else: rc = 1 os.unlink(diskimg_path) if rc: raise InstallError("virt_install failed") elif opts.make_oci: # An OCI image places the filesystem under /rootfs/ and adds the json files at the top # And then creates a tar of the whole thing. compress_args = [] for arg in opts.compress_args: compress_args += arg.split(" ", 1) with PartitionMount(diskimg_path, submount="rootfs") as img_mount: if img_mount and img_mount.temp_dir: shutil.copy2(opts.oci_config, img_mount.temp_dir) shutil.copy2(opts.oci_runtime, img_mount.temp_dir) rc = mktar(img_mount.temp_dir, disk_img, opts.compression, compress_args) else: rc = 1 os.unlink(diskimg_path) if rc: raise InstallError("virt_install failed") elif opts.make_vagrant: compress_args = [] for arg in opts.compress_args: compress_args += arg.split(" ", 1) vagrant_dir = tempfile.mkdtemp(prefix="lmc-tmpdir-") metadata_path = joinpaths(vagrant_dir, "metadata.json") execWithRedirect( "mv", ["-f", disk_img, joinpaths(vagrant_dir, "box.img")], raise_err=True) if opts.vagrant_metadata: shutil.copy2(opts.vagrant_metadata, metadata_path) else: create_vagrant_metadata(metadata_path) update_vagrant_metadata(metadata_path, disk_size) if opts.vagrantfile: shutil.copy2(opts.vagrantfile, joinpaths(vagrant_dir, "vagrantfile")) rc = mktar(vagrant_dir, disk_img, opts.compression, compress_args, selinux=False) if rc: raise InstallError("virt_install failed") shutil.rmtree(vagrant_dir)
def novirt_install(opts, disk_img, disk_size, cancel_func=None): """ Use Anaconda to install to a disk image :param opts: options passed to livemedia-creator :type opts: argparse options :param str disk_img: The full path to the disk image to be created :param int disk_size: The size of the disk_img in MiB :param cancel_func: Function that returns True to cancel build :type cancel_func: function This method runs anaconda to create the image and then based on the opts passed creates a qemu disk image or tarfile. """ dirinstall_path = ROOT_PATH # Clean up /tmp/ from previous runs to prevent stale info from being used for path in ["/tmp/yum.repos.d/", "/tmp/yum.cache/"]: if os.path.isdir(path): shutil.rmtree(path) args = ["--kickstart", opts.ks[0], "--cmdline", "--loglevel", "debug"] if opts.anaconda_args: for arg in opts.anaconda_args: args += arg.split(" ", 1) if opts.proxy: args += ["--proxy", opts.proxy] if opts.armplatform: args += ["--armplatform", opts.armplatform] if opts.make_iso or opts.make_fsimage or opts.make_pxe_live: # Make a blank fs image args += ["--dirinstall"] mkext4img(None, disk_img, label=opts.fs_label, size=disk_size * 1024**2) if not os.path.isdir(dirinstall_path): os.mkdir(dirinstall_path) mount(disk_img, opts="loop", mnt=dirinstall_path) elif opts.make_tar or opts.make_oci: # Install under dirinstall_path, make sure it starts clean if os.path.exists(dirinstall_path): shutil.rmtree(dirinstall_path) if opts.make_oci: # OCI installs under /rootfs/ dirinstall_path = joinpaths(dirinstall_path, "rootfs") args += ["--dirinstall", dirinstall_path] else: args += ["--dirinstall"] os.makedirs(dirinstall_path) else: args += ["--image", disk_img] # Create the sparse image mksparse(disk_img, disk_size * 1024**2) log_monitor = LogMonitor(timeout=opts.timeout) args += ["--remotelog", "%s:%s" % (log_monitor.host, log_monitor.port)] cancel_funcs = [log_monitor.server.log_check] if cancel_func is not None: cancel_funcs.append(cancel_func) # Make sure anaconda has the right product and release log.info("Running anaconda.") try: unshare_args = [ "--pid", "--kill-child", "--mount", "--propagation", "unchanged", "anaconda" ] + args for line in execReadlines( "unshare", unshare_args, reset_lang=False, env_add={ "ANACONDA_PRODUCTNAME": opts.project, "ANACONDA_PRODUCTVERSION": opts.releasever }, callback=lambda p: not novirt_cancel_check(cancel_funcs, p)): log.info(line) # Make sure the new filesystem is correctly labeled setfiles_args = [ "-e", "/proc", "-e", "/sys", "/etc/selinux/targeted/contexts/files/file_contexts", "/" ] if "--dirinstall" in args: # setfiles may not be available, warn instead of fail try: execWithRedirect("setfiles", setfiles_args, root=dirinstall_path) except (subprocess.CalledProcessError, OSError) as e: log.warning("Running setfiles on install tree failed: %s", str(e)) else: with PartitionMount(disk_img) as img_mount: if img_mount and img_mount.mount_dir: try: execWithRedirect("setfiles", setfiles_args, root=img_mount.mount_dir) except (subprocess.CalledProcessError, OSError) as e: log.warning( "Running setfiles on install tree failed: %s", str(e)) # For image installs, run fstrim to discard unused blocks. This way # unused blocks do not need to be allocated for sparse image types execWithRedirect("fstrim", [img_mount.mount_dir]) except (subprocess.CalledProcessError, OSError) as e: log.error("Running anaconda failed: %s", e) raise InstallError("novirt_install failed") finally: log_monitor.shutdown() # Move the anaconda logs over to a log directory log_dir = os.path.abspath(os.path.dirname(opts.logfile)) log_anaconda = joinpaths(log_dir, "anaconda") if not os.path.isdir(log_anaconda): os.mkdir(log_anaconda) for l in glob.glob("/tmp/*log") + glob.glob("/tmp/anaconda-tb-*"): shutil.copy2(l, log_anaconda) os.unlink(l) # Make sure any leftover anaconda mounts have been cleaned up if not anaconda_cleanup(dirinstall_path): raise InstallError( "novirt_install cleanup of anaconda mounts failed.") if not opts.make_iso and not opts.make_fsimage and not opts.make_pxe_live: dm_name = os.path.splitext(os.path.basename(disk_img))[0] # Remove device-mapper for partitions and disk log.debug("Removing device-mapper setup on %s", dm_name) for d in sorted(glob.glob("/dev/mapper/" + dm_name + "*"), reverse=True): dm_detach(d) log.debug("Removing loop device for %s", disk_img) loop_detach("/dev/" + get_loop_name(disk_img)) # qemu disk image is used by bare qcow2 images and by Vagrant if opts.image_type: log.info("Converting %s to %s", disk_img, opts.image_type) qemu_args = [] for arg in opts.qemu_args: qemu_args += arg.split(" ", 1) # convert the image to the selected format if "-O" not in qemu_args: qemu_args.extend(["-O", opts.image_type]) qemu_img = tempfile.mktemp(prefix="lmc-disk-", suffix=".img") execWithRedirect("qemu-img", ["convert"] + qemu_args + [disk_img, qemu_img], raise_err=True) if not opts.make_vagrant: execWithRedirect("mv", ["-f", qemu_img, disk_img], raise_err=True) else: # Take the new qcow2 image and package it up for Vagrant compress_args = [] for arg in opts.compress_args: compress_args += arg.split(" ", 1) vagrant_dir = tempfile.mkdtemp(prefix="lmc-tmpdir-") metadata_path = joinpaths(vagrant_dir, "metadata.json") execWithRedirect( "mv", ["-f", qemu_img, joinpaths(vagrant_dir, "box.img")], raise_err=True) if opts.vagrant_metadata: shutil.copy2(opts.vagrant_metadata, metadata_path) else: create_vagrant_metadata(metadata_path) update_vagrant_metadata(metadata_path, disk_size) if opts.vagrantfile: shutil.copy2(opts.vagrantfile, joinpaths(vagrant_dir, "vagrantfile")) log.info("Creating Vagrant image") rc = mktar(vagrant_dir, disk_img, opts.compression, compress_args, selinux=False) if rc: raise InstallError("novirt_install mktar failed: rc=%s" % rc) shutil.rmtree(vagrant_dir) elif opts.make_tar: compress_args = [] for arg in opts.compress_args: compress_args += arg.split(" ", 1) rc = mktar(dirinstall_path, disk_img, opts.compression, compress_args) shutil.rmtree(dirinstall_path) if rc: raise InstallError("novirt_install mktar failed: rc=%s" % rc) elif opts.make_oci: # An OCI image places the filesystem under /rootfs/ and adds the json files at the top # And then creates a tar of the whole thing. compress_args = [] for arg in opts.compress_args: compress_args += arg.split(" ", 1) shutil.copy2(opts.oci_config, ROOT_PATH) shutil.copy2(opts.oci_runtime, ROOT_PATH) rc = mktar(ROOT_PATH, disk_img, opts.compression, compress_args) if rc: raise InstallError("novirt_install mktar failed: rc=%s" % rc) else: # For raw disk images, use fallocate to deallocate unused space execWithRedirect("fallocate", ["--dig-holes", disk_img], raise_err=True)
def run_creator(opts, cancel_func=None): """Run the image creator process :param opts: Commandline options to control the process :type opts: Either a DataHolder or ArgumentParser :param cancel_func: Function that returns True to cancel build :type cancel_func: function :returns: The result directory and the disk image path. :rtype: Tuple of str This function takes the opts arguments and creates the selected output image. See the cmdline --help for livemedia-creator for the possible options (Yes, this is not ideal, but we can fix that later) """ result_dir = None # Parse the kickstart if opts.ks: ks_version = makeVersion() ks = KickstartParser(ks_version, errorsAreFatal=False, missingIncludeIsFatal=False) ks.readKickstart(opts.ks[0]) # live iso usually needs dracut-live so warn the user if it is missing if opts.ks and opts.make_iso: if "dracut-live" not in ks.handler.packages.packageList: log.error("dracut-live package is missing from the kickstart.") raise RuntimeError( "dracut-live package is missing from the kickstart.") # Make the disk or filesystem image if not opts.disk_image and not opts.fs_image: if not opts.ks: raise RuntimeError("Image creation requires a kickstart file") # Check the kickstart for problems errors = check_kickstart(ks, opts) if errors: list(log.error(e) for e in errors) raise RuntimeError("\n".join(errors)) # Make the image. Output of this is either a partitioned disk image or a fsimage try: disk_img = make_image(opts, ks, cancel_func=cancel_func) except InstallError as e: log.error("ERROR: Image creation failed: %s", e) raise RuntimeError("Image creation failed: %s" % e) if opts.image_only: return (result_dir, disk_img) if opts.make_iso: work_dir = tempfile.mkdtemp(prefix="lmc-work-") log.info("working dir is %s", work_dir) if (opts.fs_image or opts.no_virt) and not opts.disk_image: # Create iso from a filesystem image disk_img = opts.fs_image or disk_img if not make_squashfs(opts, disk_img, work_dir): log.error("squashfs.img creation failed") raise RuntimeError("squashfs.img creation failed") if cancel_func and cancel_func(): raise RuntimeError("ISO creation canceled") with Mount(disk_img, opts="loop") as mount_dir: result_dir = make_livecd(opts, mount_dir, work_dir) else: # Create iso from a partitioned disk image disk_img = opts.disk_image or disk_img with PartitionMount(disk_img) as img_mount: if img_mount and img_mount.mount_dir: make_runtime(opts, img_mount.mount_dir, work_dir, calculate_disk_size(opts, ks) / 1024.0) result_dir = make_livecd(opts, img_mount.mount_dir, work_dir) # --iso-only removes the extra build artifacts, keeping only the boot.iso if opts.iso_only and result_dir: boot_iso = joinpaths(result_dir, "images/boot.iso") if not os.path.exists(boot_iso): log.error("%s is missing, skipping --iso-only.", boot_iso) else: iso_dir = tempfile.mkdtemp(prefix="lmc-result-") dest_file = joinpaths(iso_dir, opts.iso_name or "boot.iso") shutil.move(boot_iso, dest_file) shutil.rmtree(result_dir) result_dir = iso_dir # cleanup the mess # cleanup work_dir? if disk_img and not (opts.keep_image or opts.disk_image or opts.fs_image): os.unlink(disk_img) log.info("Disk image erased") disk_img = None elif opts.make_appliance: if not opts.ks: networks = [] else: networks = ks.handler.network.network make_appliance(opts.disk_image or disk_img, opts.app_name, opts.app_template, opts.app_file, networks, opts.ram, opts.vcpus or 1, opts.arch, opts.title, opts.project, opts.releasever) elif opts.make_pxe_live: work_dir = tempfile.mkdtemp(prefix="lmc-work-") log.info("working dir is %s", work_dir) disk_img = opts.fs_image or opts.disk_image or disk_img log.debug("disk image is %s", disk_img) result_dir = make_live_images(opts, work_dir, disk_img) if result_dir is None: log.error("Creating PXE live image failed.") raise RuntimeError("Creating PXE live image failed.") if opts.result_dir != opts.tmp and result_dir: copytree(result_dir, opts.result_dir, preserve=False) shutil.rmtree(result_dir) result_dir = None return (result_dir, disk_img)
def make_live_images(opts, work_dir, disk_img): """ Create live images from direcory or rootfs image :param opts: options passed to livemedia-creator :type opts: argparse options :param str work_dir: Directory for storing results :param str disk_img: Path to disk image (fsimage or partitioned) :returns: Path of directory with created images or None :rtype: str fsck.ext4 is run on the rootfs_image to make sure there are no errors and to zero out any deleted blocks to make it compress better. If this fails for any reason it will return None and log the error. """ sys_root = "" squashfs_root_dir = joinpaths(work_dir, "squashfs_root") liveos_dir = joinpaths(squashfs_root_dir, "LiveOS") os.makedirs(liveos_dir) rootfs_img = joinpaths(liveos_dir, "rootfs.img") if opts.fs_image or opts.no_virt: # Find the ostree root in the fsimage if opts.ostree: with Mount(disk_img, opts="loop") as mnt_dir: sys_root = find_ostree_root(mnt_dir) # Try to hardlink the image, if that fails, copy it rc = execWithRedirect("/bin/ln", [disk_img, rootfs_img]) if rc != 0: shutil.copy2(disk_img, rootfs_img) else: is_root_part = None if opts.ostree: is_root_part = lambda dir: os.path.exists(dir + "/ostree/deploy") with PartitionMount(disk_img, mount_ok=is_root_part) as img_mount: if img_mount and img_mount.mount_dir: try: mounted_sysroot_boot_dir = None if opts.ostree: sys_root = find_ostree_root(img_mount.mount_dir) mounted_sysroot_boot_dir = mount_boot_part_over_root( img_mount) if opts.live_rootfs_keep_size: size = img_mount.mount_size / 1024**3 else: size = opts.live_rootfs_size or None log.info("Creating live rootfs image") mkrootfsimg(img_mount.mount_dir, rootfs_img, "LiveOS", size=size, sysroot=sys_root) finally: if mounted_sysroot_boot_dir: umount(mounted_sysroot_boot_dir) log.debug("sys_root = %s", sys_root) # Make sure free blocks are actually zeroed so it will compress rc = execWithRedirect("/usr/sbin/fsck.ext4", ["-y", "-f", "-E", "discard", rootfs_img]) if rc != 0: log.error("Problem zeroing free blocks of %s", disk_img) return None log.info("Packing live rootfs image") add_pxe_args = [] live_image_name = "live-rootfs.squashfs.img" compression, compressargs = squashfs_args(opts) mksquashfs(squashfs_root_dir, joinpaths(work_dir, live_image_name), compression, compressargs) log.info("Rebuilding initramfs for live") with Mount(rootfs_img, opts="loop") as mnt_dir: try: mount(joinpaths(mnt_dir, "boot"), opts="bind", mnt=joinpaths(mnt_dir, sys_root, "boot")) rebuild_initrds_for_live(opts, joinpaths(mnt_dir, sys_root), work_dir) finally: umount(joinpaths(mnt_dir, sys_root, "boot"), delete=False) remove(squashfs_root_dir) if opts.ostree: add_pxe_args.append("ostree=/%s" % sys_root) template = joinpaths(opts.lorax_templates, "pxe-live/pxe-config.tmpl") create_pxe_config(template, work_dir, live_image_name, add_pxe_args) return work_dir
def run_creator(opts, callback_func=None): """Run the image creator process :param opts: Commandline options to control the process :type opts: Either a DataHolder or ArgumentParser :returns: The result directory and the disk image path. :rtype: Tuple of str This function takes the opts arguments and creates the selected output image. See the cmdline --help for livemedia-creator for the possible options (Yes, this is not ideal, but we can fix that later) """ result_dir = None # Parse the kickstart if opts.ks: ks_version = makeVersion() ks = KickstartParser(ks_version, errorsAreFatal=False, missingIncludeIsFatal=False) ks.readKickstart(opts.ks[0]) # live iso usually needs dracut-live so warn the user if it is missing if opts.ks and opts.make_iso: if "dracut-live" not in ks.handler.packages.packageList: log.error("dracut-live package is missing from the kickstart.") raise RuntimeError("dracut-live package is missing from the kickstart.") # Make the disk or filesystem image if not opts.disk_image and not opts.fs_image: if not opts.ks: raise RuntimeError("Image creation requires a kickstart file") errors = [] if opts.no_virt and ks.handler.method.method not in ("url", "nfs") \ and not ks.handler.ostreesetup.seen: errors.append("Only url, nfs and ostreesetup install methods are currently supported." "Please fix your kickstart file." ) if ks.handler.method.method in ("url", "nfs") and not ks.handler.network.seen: errors.append("The kickstart must activate networking if " "the url or nfs install method is used.") if ks.handler.displaymode.displayMode is not None: errors.append("The kickstart must not set a display mode (text, cmdline, " "graphical), this will interfere with livemedia-creator.") if opts.make_fsimage or (opts.make_pxe_live and opts.no_virt): # Make sure the kickstart isn't using autopart and only has a / mountpoint part_ok = not any(p for p in ks.handler.partition.partitions if p.mountpoint not in ["/", "swap"]) if not part_ok or ks.handler.autopart.seen: errors.append("Filesystem images must use a single / part, not autopart or " "multiple partitions. swap is allowed but not used.") if not opts.no_virt and ks.handler.reboot.action != KS_SHUTDOWN: errors.append("The kickstart must include shutdown when using virt installation.") if errors: list(log.error(e) for e in errors) raise RuntimeError("\n".join(errors)) # Make the image. Output of this is either a partitioned disk image or a fsimage try: disk_img = make_image(opts, ks) except InstallError as e: log.error("ERROR: Image creation failed: %s", e) raise RuntimeError("Image creation failed: %s" % e) if opts.image_only: return (result_dir, disk_img) if opts.make_iso: work_dir = tempfile.mkdtemp(prefix="lmc-work-") log.info("working dir is %s", work_dir) if (opts.fs_image or opts.no_virt) and not opts.disk_image: # Create iso from a filesystem image disk_img = opts.fs_image or disk_img if not make_squashfs(opts, disk_img, work_dir): log.error("squashfs.img creation failed") raise RuntimeError("squashfs.img creation failed") with Mount(disk_img, opts="loop") as mount_dir: result_dir = make_livecd(opts, mount_dir, work_dir) else: # Create iso from a partitioned disk image disk_img = opts.disk_image or disk_img with PartitionMount(disk_img) as img_mount: if img_mount and img_mount.mount_dir: make_runtime(opts, img_mount.mount_dir, work_dir, calculate_disk_size(opts, ks)/1024.0) result_dir = make_livecd(opts, img_mount.mount_dir, work_dir) # --iso-only removes the extra build artifacts, keeping only the boot.iso if opts.iso_only and result_dir: boot_iso = joinpaths(result_dir, "images/boot.iso") if not os.path.exists(boot_iso): log.error("%s is missing, skipping --iso-only.", boot_iso) else: iso_dir = tempfile.mkdtemp(prefix="lmc-result-") dest_file = joinpaths(iso_dir, opts.iso_name or "boot.iso") shutil.move(boot_iso, dest_file) shutil.rmtree(result_dir) result_dir = iso_dir # cleanup the mess # cleanup work_dir? if disk_img and not (opts.keep_image or opts.disk_image or opts.fs_image): os.unlink(disk_img) log.info("Disk image erased") disk_img = None elif opts.make_appliance: if not opts.ks: networks = [] else: networks = ks.handler.network.network make_appliance(opts.disk_image or disk_img, opts.app_name, opts.app_template, opts.app_file, networks, opts.ram, opts.vcpus or 1, opts.arch, opts.title, opts.project, opts.releasever) elif opts.make_pxe_live: work_dir = tempfile.mkdtemp(prefix="lmc-work-") log.info("working dir is %s", work_dir) disk_img = opts.fs_image or opts.disk_image or disk_img log.debug("disk image is %s", disk_img) result_dir = make_live_images(opts, work_dir, disk_img) if result_dir is None: log.error("Creating PXE live image failed.") raise RuntimeError("Creating PXE live image failed.") if opts.result_dir != opts.tmp and result_dir: copytree(result_dir, opts.result_dir, preserve=False) shutil.rmtree(result_dir) result_dir = None return (result_dir, disk_img)