Add filesystem image install support

This adds support for installing to a filesystem image instead of a
partitioned disk image. It requires Anaconda's --dirinstall support.
Also re-organized the code to break it up into smaller methods.

This speeds up iso creation in no-virt mode by removing the need to copy
the filesystem from the partitioned disk image to the filesystem image
that is used to make the squashfs image.
This commit is contained in:
Brian C. Lane 2013-01-30 17:16:25 -05:00
parent 894d8b4738
commit 8e84ef10c1
2 changed files with 372 additions and 265 deletions

View File

@ -2,7 +2,9 @@
INTRO INTRO
----- -----
livemedia-creator uses Anaconda, kickstart and Lorax to create bootable media 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 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. use the disk image to create the bootable media.
@ -18,12 +20,12 @@ minimum you need:
QUICKSTART QUICKSTART
---------- ----------
sudo livemedia-creator --make-iso \ 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: 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 \ 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/ --ks=./docs/fedora-livemedia.ks --lorax-templates=./share/
If you want to watch the install you can pass '--vnc vnc' and use a vnc 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 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 When creating an iso virt-install boots using the passed Anaconda installer iso
using virt-install, or --disk-image to use a disk image from a previous run and installs the system based on the kickstart. The %post section of the
to create the .iso kickstart is used to customize the installed system in the same way that
current spin-kickstarts do.
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 livemedia-creator monitors the install process for problems by watching the
install logs. They are written to the current directory or to the base install logs. They are written to the current directory or to the base
@ -129,7 +132,7 @@ it as well.
ANACONDA IMAGE INSTALL ANACONDA IMAGE INSTALL
---------------------- ----------------------
You can create images without using virt-install by passing --no-virt on the 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: 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 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. At this time I have not tested the image with EC2. Feedback would we welcome.
APPLIANCE CREATION ------------------ livemedia-creator can now replace APPLIANCE CREATION
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 livemedia-creator can now replace appliance-tools by using the --make-appliance
setup a virtual system. 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 The XML is generated using the Mako template from
/usr/share/lorax/appliance/virt-image.xml You can use a different template by /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. run.
Cleaning up aborted --no-virt installs can sometimes be accomplished by running 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 HACKING

View File

@ -2,7 +2,7 @@
# #
# Live Media Creator # 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 # 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 # it under the terms of the GNU General Public License as published by
@ -34,7 +34,6 @@ import threading
import SocketServer import SocketServer
from time import sleep from time import sleep
import shutil import shutil
import traceback
import argparse import argparse
import hashlib import hashlib
@ -49,9 +48,10 @@ from mako.exceptions import text_error_template
# Use the Lorax treebuilder branch for iso creation # Use the Lorax treebuilder branch for iso creation
from pylorax.base import DataHolder from pylorax.base import DataHolder
from pylorax.treebuilder import TreeBuilder, RuntimeBuilder, udev_escape 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 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 from pylorax.executils import execWithRedirect, execWithCapture
# no-virt mode doesn't need libvirt, so make it optional # 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", DRACUT_DEFAULT = ["--xz", "--add", "livenet dmsquash-live convertfs pollcdrom",
"--omit", "plymouth"] "--omit", "plymouth"]
ROOT_PATH = "/mnt/sysimage/"
RUNTIME = "images/install.img"
class InstallError(Exception):
pass
class LogRequestHandler(SocketServer.BaseRequestHandler): class LogRequestHandler(SocketServer.BaseRequestHandler):
""" """
@ -184,11 +190,7 @@ class IsoMountpoint(object):
self.initrd_path = initrd_path self.initrd_path = initrd_path
if not self.initrd_path: if not self.initrd_path:
self.mount_dir = tempfile.mkdtemp() self.mount_dir = mount(self.iso_path, opts="loop")
if not self.mount_dir:
raise Exception("Error creating temporary directory")
execWithRedirect("mount", ["-o", "loop", self.iso_path, self.mount_dir])
else: else:
self.mount_dir = self.initrd_path self.mount_dir = self.initrd_path
@ -213,8 +215,7 @@ class IsoMountpoint(object):
def umount( self ): def umount( self ):
if not self.initrd_path: if not self.initrd_path:
execWithRedirect("umount", [self.mount_dir]) umount(self.mount_dir)
os.rmdir( self.mount_dir )
def get_iso_label( self ): def get_iso_label( self ):
""" """
@ -252,55 +253,55 @@ class VirtualInstall( object ):
self.virt_name = "LiveOS-"+str(uuid.uuid4()) self.virt_name = "LiveOS-"+str(uuid.uuid4())
# add --graphics none later # add --graphics none later
# add whatever serial cmds are needed later # add whatever serial cmds are needed later
cmd = [ "virt-install", "-n", self.virt_name, args = ["-n", self.virt_name,
"-r", str(memory), "-r", str(memory),
"--noreboot", "--noreboot",
"--noautoconsole" ] "--noautoconsole"]
cmd.append("--graphics") args.append("--graphics")
if vnc: if vnc:
cmd.append(vnc) args.append(vnc)
else: else:
cmd.append("none") args.append("none")
for ks in ks_paths: for ks in ks_paths:
cmd.append("--initrd-inject") args.append("--initrd-inject")
cmd.append(ks) args.append(ks)
disk_opts = "path={0}".format(disk_img) disk_opts = "path={0}".format(disk_img)
disk_opts += ",format=raw" disk_opts += ",format=raw"
if not os.path.isfile(disk_img): if not os.path.isfile(disk_img):
disk_opts += ",size={0}".format(img_size) disk_opts += ",size={0}".format(img_size)
cmd.append("--disk") args.append("--disk")
cmd.append(disk_opts) args.append(disk_opts)
if iso.liveos: if iso.liveos:
disk_opts = "path={0},device=cdrom".format(iso.iso_path) disk_opts = "path={0},device=cdrom".format(iso.iso_path)
cmd.append("--disk") args.append("--disk")
cmd.append(disk_opts) args.append(disk_opts)
extra_args = "ks=file:/{0}".format(os.path.basename(ks_paths[0])) extra_args = "ks=file:/{0}".format(os.path.basename(ks_paths[0]))
if kernel_args: if kernel_args:
extra_args += " "+kernel_args extra_args += " "+kernel_args
if iso.liveos: if iso.liveos:
extra_args += " stage2=live:CDLABEL={0}".format(udev_escape(iso.label)) extra_args += " stage2=live:CDLABEL={0}".format(udev_escape(iso.label))
cmd.append("--extra-args") args.append("--extra-args")
cmd.append(extra_args) args.append(extra_args)
cmd.append("--location") args.append("--location")
cmd.append(iso.mount_dir) args.append(iso.mount_dir)
channel_args = "tcp,host={0}:{1},mode=connect,target_type=virtio" \ channel_args = "tcp,host={0}:{1},mode=connect,target_type=virtio" \
",name=org.fedoraproject.anaconda.log.0".format( ",name=org.fedoraproject.anaconda.log.0".format(
virtio_host, virtio_port) virtio_host, virtio_port)
cmd.append("--channel") args.append("--channel")
cmd.append(channel_args) args.append(channel_args)
if arch: if arch:
cmd.append("--arch") args.append("--arch")
cmd.append(arch) args.append(arch)
rc = execWithRedirect( cmd[0], cmd[1:] ) rc = execWithRedirect("virt-install", args)
if rc: if rc:
raise Exception("Problem starting virtual install") raise Exception("Problem starting virtual install")
@ -328,8 +329,8 @@ class VirtualInstall( object ):
Could use libvirt for this instead. Could use libvirt for this instead.
""" """
log.info( "Shutting down {0}".format(self.virt_name) ) log.info( "Shutting down {0}".format(self.virt_name) )
subprocess.call(["virsh","destroy",self.virt_name]) subprocess.call(["virsh", "destroy", self.virt_name])
subprocess.call(["virsh","undefine",self.virt_name]) subprocess.call(["virsh", "undefine", self.virt_name])
def is_image_mounted(disk_img): def is_image_mounted(disk_img):
""" """
@ -343,42 +344,37 @@ def is_image_mounted(disk_img):
return False return False
def anaconda_install( disk_img, disk_size, kickstart, repo, args ): class KernelInfo(object):
""" """
disk_img Full path of the disk image Info about the kernels in boot_dir
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
""" """
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 def get_kernels(self):
selinux_enforcing = False """
if selinux.is_selinux_enabled() and selinux.security_getenforce(): Get a list of the kernels in the boot_dir
selinux_enforcing = True
selinux.security_setenforce(0)
# Create the sparse image Examine the vmlinuz-* versions and return a list of them
mksparse( disk_img, disk_size * 1024**3 ) """
files = os.listdir(self.boot_dir)
return [f[8:] for f in files if f.startswith("vmlinuz-")]
cmd = [ "anaconda", "--image", disk_img, "--kickstart", kickstart, def get_kernel_arch(self):
"--cmdline", "--repo", repo_url ] """
cmd += args Get the arch of the first kernel in boot_dir
rc = execWithRedirect( cmd[0], cmd[1:] ) Defaults to i386
"""
if selinux_enforcing: if self.list:
selinux.security_setenforce(1) kernel_arch = self.list[0].split(".")[-1]
return rc else:
kernel_arch = "i386"
return kernel_arch
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-")]
def make_appliance(disk_img, name, template, outfile, networks=None, ram=1024, 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 return work_dir
def make_livecd( disk_img, squashfs_args="", templatedir=None, def make_runtime(opts, mount_dir, work_dir):
title="Linux", project="Linux", releasever=16, isolabel=None ): """
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 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 / mounts that and then mounts LiveOS/rootfs.img as /
""" """
with PartitionMount( disk_img ) as img_mount: kernels = KernelInfo(joinpaths(mount_dir, "boot" ))
if not img_mount or not img_mount.mount_dir:
return None
kernel_list = get_kernels( joinpaths( img_mount.mount_dir, "boot" ) ) arch = DataHolder(basearch=kernels.arch, libdir=None, buildarch=None)
log.debug( "kernel_list = {0}".format(kernel_list) ) # TODO: Need to get release info from someplace...
if kernel_list: product = DataHolder(name=opts.project, version=opts.releasever, release="",
kernel_arch = kernel_list[0].split(".")[-1] 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: else:
kernel_arch = "i386" virt_install(opts, install_log, disk_img, disk_size)
log.debug( "kernel_arch = {0}".format(kernel_arch) ) 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("Disk Image install successful")
log.info( "working dir is {0}".format(work_dir) ) return disk_img
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
def setup_logging(opts): def setup_logging(opts):
@ -574,7 +713,9 @@ if __name__ == '__main__':
action.add_argument( "--make-iso", action="store_true", action.add_argument( "--make-iso", action="store_true",
help="Build a live iso" ) help="Build a live iso" )
action.add_argument( "--make-disk", action="store_true", 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", action.add_argument( "--make-appliance", action="store_true",
help="Build an appliance image and XML description" ) help="Build an appliance image and XML description" )
action.add_argument( "--make-ami", action="store_true", 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" ) help="Anaconda installation .iso path to use for virt-install" )
parser.add_argument( "--disk-image", type=os.path.abspath, parser.add_argument( "--disk-image", type=os.path.abspath,
help="Path to disk image to use for creating final image" ) 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, parser.add_argument( "--ks", action="append", type=os.path.abspath,
help="Kickstart file defining the install." ) help="Kickstart file defining the install." )
parser.add_argument( "--image-name", default=None, 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", 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", parser.add_argument( "--keep-image", action="store_true",
help="Keep raw disk image after .iso creation" ) help="Keep raw disk image after .iso creation" )
parser.add_argument( "--no-virt", action="store_true", 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 ) ) log.error( "The disk image {0} is missing.".format( opts.disk_image ) )
sys.exit( 1 ) 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." ) log.error( "virt-install needs an install iso." )
sys.exit( 1 ) sys.exit( 1 )
if opts.volid and len(opts.volid) > 32: 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) 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.") log.error("virt-install requires libvirt-python to be installed.")
sys.exit(1) 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.") log.error("virt-install requires python-virtinst to be installed.")
sys.exit(1) 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.") log.error("no-virt requires anaconda to be installed.")
sys.exit(1) sys.exit(1)
@ -721,7 +871,7 @@ if __name__ == '__main__':
"exist".format(opts.app_template)) "exist".format(opts.app_template))
sys.exit(1) 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.") log.error("The disk image to be created should not exist.")
sys.exit(1) sys.exit(1)
@ -737,123 +887,74 @@ if __name__ == '__main__':
ks = KickstartParser( ks_version, errorsAreFatal=False, missingIncludeIsFatal=False ) ks = KickstartParser( ks_version, errorsAreFatal=False, missingIncludeIsFatal=False )
ks.readKickstart( opts.ks[0] ) ks.readKickstart( opts.ks[0] )
# Make the disk image # Make the disk or filesystem image
if not opts.disk_image: if not opts.disk_image and not opts.fs_image:
if not opts.ks: if not opts.ks:
log.error("Image creation requires a kickstart file") log.error("Image creation requires a kickstart file")
sys.exit(1) sys.exit(1)
disk_size = 1 + (sum( [p.size for p in ks.handler.partition.partitions] ) / 1024) errors = []
log.info( "disk_size = {0}GB".format(disk_size) )
if ks.handler.method.method != "url": if ks.handler.method.method != "url":
log.error( "Only url install method is currently supported. Please " errors.append("Only url install method is currently supported. Please "
"fix your kickstart file." ) "fix your kickstart file." )
sys.exit( 1 )
repo_url = ks.handler.method.url
if ks.handler.displaymode.displayMode is not None: if ks.handler.displaymode.displayMode is not None:
log.error("The kickstart must not set a display mode (text, cmdline, " errors.append("The kickstart must not set a display mode (text, cmdline, "
"graphical), this will interfere with livemedia-creator.") "graphical), this will interfere with livemedia-creator.")
if errors:
for e in errors:
log.error(e)
sys.exit(1) sys.exit(1)
if opts.image_name: # Make the image. Output of this is either a partitioned disk image or a fsimage
disk_img = joinpaths(opts.tmp, opts.image_name) try:
else: disk_img = make_image(opts, ks)
disk_img = tempfile.mktemp( prefix="disk", suffix=".img", dir=opts.tmp ) except InstallError as e:
install_log = os.path.abspath(os.path.dirname(opts.logfile))+"/virt-install.log" log.error("ERROR: Image creation failed: %s" % (e))
sys.exit(1)
log.info( "disk_img = {0}".format(disk_img) ) if not opts.image_only:
log.info( "install_log = {0}".format(install_log) ) result_dir = None
if opts.make_iso:
work_dir = tempfile.mkdtemp()
log.info("working dir is {0}".format(work_dir))
if opts.no_virt: if (opts.fs_image or opts.no_virt) and not opts.disk_image:
anaconda_args = [] # Create iso from a filesystem image
if opts.anaconda_args: disk_img = opts.fs_image or disk_img
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 ]
# Use anaconda's image install make_squashfs(disk_img, work_dir)
install_error = anaconda_install( disk_img, disk_size, opts.ks[0], with Mount(disk_img, opts="loop") as mount_dir:
repo_url, anaconda_args ) 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 # cleanup the mess
log_dir = os.path.abspath(os.path.dirname(opts.logfile)) # cleanup work_dir?
log_anaconda = joinpaths( log_dir, "anaconda" ) if disk_img and not (opts.keep_image or opts.disk_image or opts.fs_image):
if not os.path.isdir( log_anaconda ): os.unlink(disk_img)
os.mkdir( log_anaconda ) log.info("Disk image erased")
for l in ["anaconda.log", "ifcfg.log", "program.log", "storage.log", disk_img = None
"packaging.log", "yum.log"]: elif opts.make_ami:
if os.path.exists( "/tmp/"+l ): result_dir = make_ami(opts.disk_image or disk_img)
shutil.copy2( "/tmp/"+l, log_anaconda ) elif opts.make_appliance:
os.unlink( "/tmp/"+l ) 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 if opts.result_dir and result_dir:
execWithRedirect("anaconda-cleanup", []) shutil.copytree( result_dir, opts.result_dir )
dm_name = os.path.splitext(os.path.basename(disk_img))[0] shutil.rmtree( result_dir )
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 )
log.info("SUMMARY") log.info("SUMMARY")
log.info("-------") log.info("-------")