diff --git a/README.livemedia-creator b/README.livemedia-creator index dee51b25..424bd249 100644 --- a/README.livemedia-creator +++ b/README.livemedia-creator @@ -2,7 +2,9 @@ INTRO ----- livemedia-creator uses Anaconda, kickstart and Lorax to create bootable media -such as live iso's that use the same install path as a normal system install. +that use the same install path as a normal system install. It can be used to +make live isos, bootable (partitioned) disk images and filesystem images for +use with virtualization. The general idea is to use virt-install to install into a disk image and then use the disk image to create the bootable media. @@ -18,12 +20,12 @@ minimum you need: QUICKSTART ---------- sudo livemedia-creator --make-iso \ ---iso=/extra/iso/Fedora-16-x86_64-netinst.iso --ks=./fedora-livemedia.ks +--iso=/extra/iso/Fedora-18-x86_64-netinst.iso --ks=./docs/fedora-livemedia.ks If you are using the lorax git repo you can run it like so: sudo PATH=./src/sbin/:$PATH PYTHONPATH=./src/ ./src/sbin/livemedia-creator \ ---make-iso --iso=/extra/iso/Fedora-16-x86_64-netinst.iso \ +--make-iso --iso=/extra/iso/Fedora-18-x86_64-netinst.iso \ --ks=./docs/fedora-livemedia.ks --lorax-templates=./share/ If you want to watch the install you can pass '--vnc vnc' and use a vnc @@ -35,16 +37,17 @@ to monitor the logs for fatal errors, but may not catch everything. HOW IT WORKS ------------ -The --make-* switches define the final output. +There are 2 stages, the install stage which produces a disk or filesystem +image as its output, and the boot media creation which uses the image as +its input. Normally you would have it run both stages, but it is possible +to have it stop after the install stage, using --image-only, or to have it +skip the install stage and use a previously created disk image by passing +--disk-image or --fs-image -You then need to either pass --iso and --ks in order to create a disk image -using virt-install, or --disk-image to use a disk image from a previous run -to create the .iso - -virt-install boots using the passed Anaconda installer iso and installs the -system based on the kickstart. The %post section of the kickstart is used to -customize the installed system in the same way that current spin-kickstarts -do. +When creating an iso virt-install boots using the passed Anaconda installer iso +and installs the system based on the kickstart. The %post section of the +kickstart is used to customize the installed system in the same way that +current spin-kickstarts do. livemedia-creator monitors the install process for problems by watching the install logs. They are written to the current directory or to the base @@ -129,7 +132,7 @@ it as well. ANACONDA IMAGE INSTALL ---------------------- You can create images without using virt-install by passing --no-virt on the -cmdline. This will use Anaconda's image install feature to handle the install. +cmdline. This will use Anaconda's directory install feature to handle the install. There are a couple of things to keep in mind when doing this: 1. It will be most reliable when building images for the same release that the @@ -168,10 +171,11 @@ This will produce an ami-root.img file in the working directory. At this time I have not tested the image with EC2. Feedback would we welcome. -APPLIANCE CREATION ------------------ livemedia-creator can now replace -appliance-tools by using the --make-appliance switch. This will create the -partitioned disk image and an XML file that can be used with virt-image to -setup a virtual system. +APPLIANCE CREATION +------------------ +livemedia-creator can now replace appliance-tools by using the --make-appliance +switch. This will create the partitioned disk image and an XML file that can be +used with virt-image to setup a virtual system. The XML is generated using the Mako template from /usr/share/lorax/appliance/virt-image.xml You can use a different template by @@ -218,7 +222,9 @@ disk image. Or you can pass --keep-image to keep it around after lorax is run. Cleaning up aborted --no-virt installs can sometimes be accomplished by running -the anaconda-cleanup script. +the anaconda-cleanup script. As of f18 anaconda is multi-threaded and it can +sometimes become stuck and refuse to exit. When this happens you can usually +clean up by first killing the anaconda process then running anaconda-cleanup. HACKING diff --git a/src/sbin/livemedia-creator b/src/sbin/livemedia-creator index f80ad000..0b40e61c 100755 --- a/src/sbin/livemedia-creator +++ b/src/sbin/livemedia-creator @@ -2,7 +2,7 @@ # # Live Media Creator # -# Copyright (C) 2011-2012 Red Hat, Inc. +# Copyright (C) 2011-2013 Red Hat, Inc. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -34,7 +34,6 @@ import threading import SocketServer from time import sleep import shutil -import traceback import argparse import hashlib @@ -49,9 +48,10 @@ from mako.exceptions import text_error_template # Use the Lorax treebuilder branch for iso creation from pylorax.base import DataHolder from pylorax.treebuilder import TreeBuilder, RuntimeBuilder, udev_escape -from pylorax.sysutils import joinpaths, remove, linktree +from pylorax.sysutils import joinpaths, remove from pylorax.imgutils import PartitionMount, mksparse, mkext4img, loop_detach -from pylorax.imgutils import get_loop_name, dm_detach +from pylorax.imgutils import get_loop_name, dm_detach, mount, umount, Mount +from pylorax.imgutils import mksquashfs from pylorax.executils import execWithRedirect, execWithCapture # no-virt mode doesn't need libvirt, so make it optional @@ -64,6 +64,12 @@ except ImportError: DRACUT_DEFAULT = ["--xz", "--add", "livenet dmsquash-live convertfs pollcdrom", "--omit", "plymouth"] +ROOT_PATH = "/mnt/sysimage/" +RUNTIME = "images/install.img" + +class InstallError(Exception): + pass + class LogRequestHandler(SocketServer.BaseRequestHandler): """ @@ -184,11 +190,7 @@ class IsoMountpoint(object): self.initrd_path = initrd_path if not self.initrd_path: - self.mount_dir = tempfile.mkdtemp() - if not self.mount_dir: - raise Exception("Error creating temporary directory") - - execWithRedirect("mount", ["-o", "loop", self.iso_path, self.mount_dir]) + self.mount_dir = mount(self.iso_path, opts="loop") else: self.mount_dir = self.initrd_path @@ -213,8 +215,7 @@ class IsoMountpoint(object): def umount( self ): if not self.initrd_path: - execWithRedirect("umount", [self.mount_dir]) - os.rmdir( self.mount_dir ) + umount(self.mount_dir) def get_iso_label( self ): """ @@ -252,55 +253,55 @@ class VirtualInstall( object ): self.virt_name = "LiveOS-"+str(uuid.uuid4()) # add --graphics none later # add whatever serial cmds are needed later - cmd = [ "virt-install", "-n", self.virt_name, - "-r", str(memory), - "--noreboot", - "--noautoconsole" ] + args = ["-n", self.virt_name, + "-r", str(memory), + "--noreboot", + "--noautoconsole"] - cmd.append("--graphics") + args.append("--graphics") if vnc: - cmd.append(vnc) + args.append(vnc) else: - cmd.append("none") + args.append("none") for ks in ks_paths: - cmd.append("--initrd-inject") - cmd.append(ks) + args.append("--initrd-inject") + args.append(ks) disk_opts = "path={0}".format(disk_img) disk_opts += ",format=raw" if not os.path.isfile(disk_img): disk_opts += ",size={0}".format(img_size) - cmd.append("--disk") - cmd.append(disk_opts) + args.append("--disk") + args.append(disk_opts) if iso.liveos: disk_opts = "path={0},device=cdrom".format(iso.iso_path) - cmd.append("--disk") - cmd.append(disk_opts) + args.append("--disk") + args.append(disk_opts) extra_args = "ks=file:/{0}".format(os.path.basename(ks_paths[0])) if kernel_args: extra_args += " "+kernel_args if iso.liveos: extra_args += " stage2=live:CDLABEL={0}".format(udev_escape(iso.label)) - cmd.append("--extra-args") - cmd.append(extra_args) + args.append("--extra-args") + args.append(extra_args) - cmd.append("--location") - cmd.append(iso.mount_dir) + args.append("--location") + args.append(iso.mount_dir) channel_args = "tcp,host={0}:{1},mode=connect,target_type=virtio" \ ",name=org.fedoraproject.anaconda.log.0".format( virtio_host, virtio_port) - cmd.append("--channel") - cmd.append(channel_args) + args.append("--channel") + args.append(channel_args) if arch: - cmd.append("--arch") - cmd.append(arch) + args.append("--arch") + args.append(arch) - rc = execWithRedirect( cmd[0], cmd[1:] ) + rc = execWithRedirect("virt-install", args) if rc: raise Exception("Problem starting virtual install") @@ -328,8 +329,8 @@ class VirtualInstall( object ): Could use libvirt for this instead. """ log.info( "Shutting down {0}".format(self.virt_name) ) - subprocess.call(["virsh","destroy",self.virt_name]) - subprocess.call(["virsh","undefine",self.virt_name]) + subprocess.call(["virsh", "destroy", self.virt_name]) + subprocess.call(["virsh", "undefine", self.virt_name]) def is_image_mounted(disk_img): """ @@ -343,42 +344,37 @@ def is_image_mounted(disk_img): return False -def anaconda_install( disk_img, disk_size, kickstart, repo, args ): +class KernelInfo(object): """ - disk_img Full path of the disk image - disk_size Disk size in GB - kickstart Full path to kickstart file - repo URL of repository - args Extra args to pass to anaconda --image install + Info about the kernels in boot_dir """ - import selinux + def __init__(self, boot_dir): + self.boot_dir = boot_dir + self.list = self.get_kernels() + self.arch = self.get_kernel_arch() + log.debug("kernel_list for {0.boot_dir} = {0.list}".format(self)) + log.debug("kernel_arch is {0.arch}".format(self)) - # Set selinux to Permissive if it is Enforcing - selinux_enforcing = False - if selinux.is_selinux_enabled() and selinux.security_getenforce(): - selinux_enforcing = True - selinux.security_setenforce(0) + def get_kernels(self): + """ + Get a list of the kernels in the boot_dir - # Create the sparse image - mksparse( disk_img, disk_size * 1024**3 ) + Examine the vmlinuz-* versions and return a list of them + """ + files = os.listdir(self.boot_dir) + return [f[8:] for f in files if f.startswith("vmlinuz-")] - cmd = [ "anaconda", "--image", disk_img, "--kickstart", kickstart, - "--cmdline", "--repo", repo_url ] - cmd += args + def get_kernel_arch(self): + """ + Get the arch of the first kernel in boot_dir - rc = execWithRedirect( cmd[0], cmd[1:] ) - - if selinux_enforcing: - selinux.security_setenforce(1) - return rc - - -def get_kernels( boot_dir ): - """ - Examine the vmlinuz-* versions and return a list of them - """ - files = os.listdir(boot_dir) - return [f[8:] for f in files if f.startswith("vmlinuz-")] + Defaults to i386 + """ + if self.list: + kernel_arch = self.list[0].split(".")[-1] + else: + kernel_arch = "i386" + return kernel_arch def make_appliance(disk_img, name, template, outfile, networks=None, ram=1024, @@ -450,8 +446,32 @@ def make_ami( disk_img, ami_img="ami-root.img", ami_label="AMI" ): return work_dir -def make_livecd( disk_img, squashfs_args="", templatedir=None, - title="Linux", project="Linux", releasever=16, isolabel=None ): +def make_runtime(opts, mount_dir, work_dir): + """ + Make the squashfs image from a directory + + Result is in work_dir+RUNTIME + """ + kernels = KernelInfo(joinpaths(mount_dir, "boot" )) + + # Fake yum object + fake_yum = DataHolder(conf=DataHolder(installroot=mount_dir)) + # Fake arch with only basearch set + arch = DataHolder(basearch=kernels.arch, libdir=None, buildarch=None) + # TODO: Need to get release info from someplace... + product = DataHolder(name=opts.project, version=opts.releasever, release="", + variant="", bugurl="", isfinal=False) + + # This is a mounted image partition, cannot hardlink to it, so just use it + # symlink mount_dir/images to work_dir/images so we don't run out of space + os.makedirs(joinpaths(work_dir, "images")) + + rb = RuntimeBuilder(product, arch, fake_yum) + log.info("Creating runtime") + rb.create_runtime(joinpaths(work_dir, RUNTIME), size=None) + + +def make_livecd(opts, mount_dir, work_dir): """ Take the content from the disk image and make a livecd out of it @@ -466,76 +486,195 @@ def make_livecd( disk_img, squashfs_args="", templatedir=None, mounts that and then mounts LiveOS/rootfs.img as / """ - with PartitionMount( disk_img ) as img_mount: - if not img_mount or not img_mount.mount_dir: - return None + kernels = KernelInfo(joinpaths(mount_dir, "boot" )) - kernel_list = get_kernels( joinpaths( img_mount.mount_dir, "boot" ) ) - log.debug( "kernel_list = {0}".format(kernel_list) ) - if kernel_list: - kernel_arch = kernel_list[0].split(".")[-1] + arch = DataHolder(basearch=kernels.arch, libdir=None, buildarch=None) + # TODO: Need to get release info from someplace... + product = DataHolder(name=opts.project, version=opts.releasever, release="", + variant="", bugurl="", isfinal=False) + + # Link /images to work_dir/images to make the templates happy + if os.path.islink(joinpaths(mount_dir, "images")): + os.unlink(joinpaths(mount_dir, "images")) + execWithRedirect("/bin/ln", ["-s", joinpaths(work_dir, "images"), + joinpaths(mount_dir, "images")]) + + # The templates expect the config files to be in /tmp/config_files + # I think these should be release specific, not from lorax, but for now + configdir = joinpaths(opts.lorax_templates,"live/config_files/") + configdir_path = "tmp/config_files" + fullpath = joinpaths(mount_dir, configdir_path) + if os.path.exists(fullpath): + remove(fullpath) + shutil.copytree(configdir, fullpath) + + isolabel = opts.volid or "{0.name} {0.version} {1.basearch}".format(product, arch) + if len(isolabel) > 32: + isolabel = isolabel[:32] + log.warn("Truncating isolabel to 32 chars: %s" % (isolabel,)) + + tb = TreeBuilder(product=product, arch=arch, + inroot=mount_dir, outroot=work_dir, + runtime=RUNTIME, isolabel=isolabel, + templatedir=joinpaths(opts.lorax_templates,"live/")) + log.info( "Rebuilding initrds" ) + if not opts.dracut_args: + dracut_args = DRACUT_DEFAULT + else: + dracut_args = [] + for arg in opts.dracut_args: + dracut_args += arg.split(" ", 1) + log.info("dracut args = {0}".format(dracut_args)) + tb.rebuild_initrds(add_args=dracut_args) + log.info("Building boot.iso") + tb.build() + + return work_dir + + +def novirt_install(opts, disk_img, disk_size, repo_url): + """ + Use Anaconda to install to a disk image + """ + import selinux + + # Set selinux to Permissive if it is Enforcing + selinux_enforcing = False + if selinux.is_selinux_enabled() and selinux.security_getenforce(): + selinux_enforcing = True + selinux.security_setenforce(0) + + args = ["--kickstart", opts.ks[0], "--cmdline", "--repo", repo_url] + 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: + # Make a blank fs image + args += ["--dirinstall"] + + mkext4img(None, disk_img, label="Anaconda", size=disk_size * 1024**3) + if not os.path.isdir(ROOT_PATH): + os.mkdir(ROOT_PATH) + mount(disk_img, opts="loop", mnt=ROOT_PATH) + else: + args += ["--image", disk_img] + + # Create the sparse image + mksparse(disk_img, disk_size * 1024**3) + + rc = execWithRedirect("anaconda", args) + + # 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 ["anaconda.log", "ifcfg.log", "program.log", "storage.log", + "packaging.log", "yum.log"]: + if os.path.exists("/tmp/"+l): + shutil.copy2("/tmp/"+l, log_anaconda) + os.unlink("/tmp/"+l) + + if opts.make_iso or opts.make_fsimage: + umount(ROOT_PATH) + else: + # If anaconda failed the disk image may still be in use by dm + execWithRedirect("anaconda-cleanup", []) + dm_name = os.path.splitext(os.path.basename(disk_img))[0] + dm_path = "/dev/mapper/"+dm_name + if os.path.exists(dm_path): + dm_detach(dm_path) + loop_detach(get_loop_name(disk_img)) + + if selinux_enforcing: + selinux.security_setenforce(1) + + if rc: + raise InstallError("novirt_install failed") + + +def virt_install(opts, install_log, disk_img, disk_size): + """ + Use virt-install to install to a disk image + """ + iso_mount = IsoMountpoint(opts.iso, opts.location) + log_monitor = LogMonitor(install_log) + + kernel_args = "" + if opts.kernel_args: + kernel_args += opts.kernel_args + if opts.proxy: + kernel_args += " proxy="+opts.proxy + + virt = VirtualInstall(iso_mount, opts.ks, disk_img, disk_size, + kernel_args, opts.ram, opts.vnc, opts.arch, + log_check = log_monitor.server.log_check, + virtio_host = log_monitor.host, + virtio_port = log_monitor.port) + virt.destroy() + log_monitor.shutdown() + iso_mount.umount() + + if log_monitor.server.log_check(): + raise InstallError("virt_install failed") + + +def make_squashfs(disk_img, work_dir, compression="xz"): + """ + Take disk_img and put it into LiveOS/rootfs.img and squashfs this + tree into work_dir+RUNTIME + """ + liveos_dir = joinpaths(work_dir, "runtime/LiveOS") + os.makedirs(liveos_dir) + os.makedirs(os.path.dirname(joinpaths(work_dir, RUNTIME))) + execWithRedirect("/bin/ln", [disk_img, + joinpaths(liveos_dir, "rootfs.img")]) + mksquashfs(joinpaths(work_dir, "runtime"), + joinpaths(work_dir, RUNTIME), compression) + remove(joinpaths(work_dir, "runtime")) + + +def make_image(opts, ks): + """ + Install to an image + + Use virt or anaconda to install to an image. + + Returns the name of the image created. + """ + disk_size = 1 + (sum([p.size for p in ks.handler.partition.partitions]) / 1024) + log.info("disk_size = {0}GB".format(disk_size)) + + repo_url = ks.handler.method.url + + if opts.image_name: + disk_img = joinpaths(opts.tmp, opts.image_name) + else: + disk_img = tempfile.mktemp(prefix="disk", suffix=".img", dir=opts.tmp) + install_log = os.path.abspath(os.path.dirname(opts.logfile))+"/virt-install.log" + + log.info("disk_img = {0}".format(disk_img)) + log.info("install_log = {0}".format(install_log)) + + try: + if opts.no_virt: + novirt_install(opts, disk_img, disk_size, repo_url) else: - kernel_arch = "i386" - log.debug( "kernel_arch = {0}".format(kernel_arch) ) + virt_install(opts, install_log, disk_img, disk_size) + except InstallError as e: + log.error("Install failed: {0}".format(e)) + if not opts.keep_image: + log.info("Removing bad disk image") + os.unlink(disk_img) + raise - work_dir = tempfile.mkdtemp() - log.info( "working dir is {0}".format(work_dir) ) - - runtime = "images/install.img" - # This is a mounted image partition, cannot hardlink to it, so just use it - installroot = img_mount.mount_dir - # symlink installroot/images to work_dir/images so we don't run out of space - os.makedirs( joinpaths( work_dir, "images" ) ) - - # TreeBuilder expects the config files to be in /tmp/config_files - # I think these should be release specific, not from lorax, but for now - configdir = joinpaths(templatedir,"live/config_files/") - configdir_path = "tmp/config_files" - fullpath = joinpaths(installroot, configdir_path) - if os.path.exists(fullpath): - remove(fullpath) - shutil.copytree(configdir, fullpath) - - # Fake yum object - fake_yum = DataHolder( conf=DataHolder( installroot=installroot ) ) - # Fake arch with only basearch set - arch = DataHolder( basearch=kernel_arch, libdir=None, buildarch=None ) - # TODO: Need to get release info from someplace... - product = DataHolder( name=project, version=releasever, release="", - variant="", bugurl="", isfinal=False ) - - rb = RuntimeBuilder( product, arch, fake_yum ) - log.info( "Creating runtime" ) - rb.create_runtime( joinpaths( work_dir, runtime ), size=None ) - - # Link /images to work_dir/images to make the templates happy - if os.path.islink( joinpaths( installroot, "images" ) ): - os.unlink( joinpaths( installroot, "images" ) ) - execWithRedirect("/bin/ln", ["-s", joinpaths(work_dir, "images"), - joinpaths(installroot, "images")]) - - isolabel = isolabel or "{0.name} {0.version} {1.basearch}".format(product, arch) - if len(isolabel) > 32: - isolabel = isolabel[:32] - log.error("Truncating isolabel to 32 chars: %s" % (isolabel,)) - - tb = TreeBuilder( product=product, arch=arch, - inroot=installroot, outroot=work_dir, - runtime=runtime, isolabel=isolabel, - templatedir=joinpaths(templatedir,"live/")) - log.info( "Rebuilding initrds" ) - if not opts.dracut_args: - dracut_args = DRACUT_DEFAULT - else: - dracut_args = [] - for arg in opts.dracut_args: - dracut_args += arg.split(" ", 1) - log.info( "dracut args = {0}".format( dracut_args ) ) - tb.rebuild_initrds( add_args=dracut_args ) - log.info( "Building boot.iso" ) - tb.build() - - return work_dir + log.info("Disk Image install successful") + return disk_img def setup_logging(opts): @@ -574,7 +713,9 @@ if __name__ == '__main__': action.add_argument( "--make-iso", action="store_true", help="Build a live iso" ) action.add_argument( "--make-disk", action="store_true", - help="Build a disk image" ) + help="Build a partitioned disk image" ) + action.add_argument( "--make-fsimage", action="store_true", + help="Build a filesystem image" ) action.add_argument( "--make-appliance", action="store_true", help="Build an appliance image and XML description" ) action.add_argument( "--make-ami", action="store_true", @@ -584,13 +725,15 @@ if __name__ == '__main__': help="Anaconda installation .iso path to use for virt-install" ) parser.add_argument( "--disk-image", type=os.path.abspath, help="Path to disk image to use for creating final image" ) + parser.add_argument( "--fs-image", type=os.path.abspath, + help="Path to filesystem image to use for creating final image" ) parser.add_argument( "--ks", action="append", type=os.path.abspath, help="Kickstart file defining the install." ) parser.add_argument( "--image-name", default=None, - help="Name of disk image to create. Default is a random name." ) + help="Name of fs/disk image to create. Default is a random name." ) parser.add_argument( "--image-only", action="store_true", - help="Exit after creating disk image." ) + help="Exit after creating fs/disk image." ) parser.add_argument( "--keep-image", action="store_true", help="Keep raw disk image after .iso creation" ) parser.add_argument( "--no-virt", action="store_true", @@ -692,23 +835,30 @@ if __name__ == '__main__': log.error( "The disk image {0} is missing.".format( opts.disk_image ) ) sys.exit( 1 ) - if not opts.no_virt and not opts.iso and not opts.disk_image: + if opts.fs_image and not os.path.exists( opts.fs_image ): + log.error( "The filesystem image {0} is missing.".format( opts.fs_image ) ) + sys.exit( 1 ) + + is_install = not (opts.disk_image or opts.fs_image) + if is_install and not opts.no_virt and not opts.iso: log.error( "virt-install needs an install iso." ) sys.exit( 1 ) if opts.volid and len(opts.volid) > 32: - logger.fatal("the volume id cannot be longer than 32 characters") + log.fatal("the volume id cannot be longer than 32 characters") sys.exit(1) - if not opts.no_virt and not libvirt: + if is_install and not opts.no_virt and not libvirt: log.error("virt-install requires libvirt-python to be installed.") sys.exit(1) - if not opts.no_virt and not os.path.exists("/usr/bin/virt-install"): + if is_install and not opts.no_virt \ + and not os.path.exists("/usr/bin/virt-install"): log.error("virt-install requires python-virtinst to be installed.") sys.exit(1) - if opts.no_virt and not os.path.exists("/usr/sbin/anaconda"): + if not is_install and opts.no_virt \ + and not os.path.exists("/usr/sbin/anaconda"): log.error("no-virt requires anaconda to be installed.") sys.exit(1) @@ -721,7 +871,7 @@ if __name__ == '__main__': "exist".format(opts.app_template)) sys.exit(1) - if opts.image_name and os.path.exists(joinpaths(opts.tmp,opts.image_name)): + if opts.image_name and os.path.exists(joinpaths(opts.tmp, opts.image_name)): log.error("The disk image to be created should not exist.") sys.exit(1) @@ -737,123 +887,74 @@ if __name__ == '__main__': ks = KickstartParser( ks_version, errorsAreFatal=False, missingIncludeIsFatal=False ) ks.readKickstart( opts.ks[0] ) - # Make the disk image - if not opts.disk_image: + # Make the disk or filesystem image + if not opts.disk_image and not opts.fs_image: if not opts.ks: log.error("Image creation requires a kickstart file") sys.exit(1) - disk_size = 1 + (sum( [p.size for p in ks.handler.partition.partitions] ) / 1024) - log.info( "disk_size = {0}GB".format(disk_size) ) - + errors = [] if ks.handler.method.method != "url": - log.error( "Only url install method is currently supported. Please " - "fix your kickstart file." ) - sys.exit( 1 ) - repo_url = ks.handler.method.url + errors.append("Only url install method is currently supported. Please " + "fix your kickstart file." ) + if ks.handler.displaymode.displayMode is not None: - log.error("The kickstart must not set a display mode (text, cmdline, " - "graphical), this will interfere with livemedia-creator.") + errors.append("The kickstart must not set a display mode (text, cmdline, " + "graphical), this will interfere with livemedia-creator.") + + if errors: + for e in errors: + log.error(e) sys.exit(1) - if opts.image_name: - disk_img = joinpaths(opts.tmp, opts.image_name) - else: - disk_img = tempfile.mktemp( prefix="disk", suffix=".img", dir=opts.tmp ) - install_log = os.path.abspath(os.path.dirname(opts.logfile))+"/virt-install.log" + # 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)) + sys.exit(1) - log.info( "disk_img = {0}".format(disk_img) ) - log.info( "install_log = {0}".format(install_log) ) + if not opts.image_only: + result_dir = None + if opts.make_iso: + work_dir = tempfile.mkdtemp() + log.info("working dir is {0}".format(work_dir)) - if opts.no_virt: - anaconda_args = [] - if opts.anaconda_args: - for arg in opts.anaconda_args: - anaconda_args += arg.split(" ", 1) - if opts.proxy: - anaconda_args += [ "--proxy", opts.proxy ] - if opts.armplatform: - anaconda_args += [ "--armplatform", opts.armplatform ] + 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 - # Use anaconda's image install - install_error = anaconda_install( disk_img, disk_size, opts.ks[0], - repo_url, anaconda_args ) + make_squashfs(disk_img, work_dir) + 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) + result_dir = make_livecd(opts, img_mount.mount_dir, work_dir) - # 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 ["anaconda.log", "ifcfg.log", "program.log", "storage.log", - "packaging.log", "yum.log"]: - if os.path.exists( "/tmp/"+l ): - shutil.copy2( "/tmp/"+l, log_anaconda ) - os.unlink( "/tmp/"+l ) + # 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_ami: + result_dir = make_ami(opts.disk_image or disk_img) + 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, opts.arch, opts.title, opts.project, opts.releasever) - # If anaconda failed the disk image may still be in use by dm - execWithRedirect("anaconda-cleanup", []) - dm_name = os.path.splitext(os.path.basename(disk_img))[0] - dm_path = "/dev/mapper/"+dm_name - if os.path.exists(dm_path): - dm_detach(dm_path) - loop_detach(get_loop_name(disk_img)) - - else: - iso_mount = IsoMountpoint( opts.iso, opts.location ) - log_monitor = LogMonitor( install_log ) - - kernel_args = "" - if opts.kernel_args: - kernel_args += opts.kernel_args - if opts.proxy: - kernel_args += " proxy="+opts.proxy - - virt = VirtualInstall( iso_mount, opts.ks, disk_img, disk_size, - kernel_args, opts.ram, opts.vnc, opts.arch, - log_check = log_monitor.server.log_check, - virtio_host = log_monitor.host, - virtio_port = log_monitor.port ) - virt.destroy() - log_monitor.shutdown() - iso_mount.umount() - - install_error = log_monitor.server.log_check() - - if install_error: - log.error( "Install failed" ) - if not opts.keep_image: - log.info( "Removing bad disk image" ) - os.unlink( disk_img ) - sys.exit( 1 ) - else: - log.info( "Disk Image install successful" ) - - - result_dir = None - if opts.make_iso and not opts.image_only: - result_dir = make_livecd( opts.disk_image or disk_img, opts.squashfs_args, - opts.lorax_templates, - opts.title, opts.project, opts.releasever, - opts.volid ) - # cleanup the mess - if disk_img and not opts.keep_image and not opts.disk_image: - os.unlink( disk_img ) - log.info("Disk image erased") - disk_img = None - elif opts.make_ami and not opts.image_only: - result_dir = make_ami(opts.disk_image or disk_img) - elif opts.make_appliance and not opts.image_only: - 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, opts.arch, opts.title, opts.project, opts.releasever) - - if opts.result_dir and result_dir: - shutil.copytree( result_dir, opts.result_dir ) - shutil.rmtree( result_dir ) + if opts.result_dir and result_dir: + shutil.copytree( result_dir, opts.result_dir ) + shutil.rmtree( result_dir ) log.info("SUMMARY") log.info("-------")