lorax/src/sbin/livemedia-creator
Brian C. Lane 89050f068d livemedia-creator: Move core functions into pylorax modules
This reduces the amount of code in livemedia-creator to the cmdline
parsing and calling of the installer functions. Moving them into other
modules will allow them to be used by other projects, like the
lorax-composer API server.
2018-05-14 13:00:14 -07:00

345 lines
14 KiB
Python
Executable File

#!/usr/bin/python3
#
# Live Media Creator
#
# Copyright (C) 2011-2018 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
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import logging
log = logging.getLogger("livemedia-creator")
import os
import sys
import tempfile
import shutil
import glob
import selinux
# Use pykickstart to calculate disk image size
from pykickstart.parser import KickstartParser
from pykickstart.version import makeVersion
from pykickstart.constants import KS_SHUTDOWN
# Use the Lorax treebuilder branch for iso creation
from pylorax import setup_logging, find_templates, vernum
from pylorax.cmdline import lmc_parser
from pylorax.creator import make_image, make_squashfs, make_livecd, make_runtime, make_appliance, make_live_images
from pylorax.creator import calculate_disk_size
from pylorax.imgutils import PartitionMount
from pylorax.imgutils import Mount
from pylorax.imgutils import copytree
from pylorax.installer import InstallError
from pylorax.sysutils import joinpaths
def default_image_name(compression, basename):
""" Return a default image name with the correct suffix for the compression type.
:param str compression: Compression type
:param str basename: Base filename
:returns: basename with compression suffix
If the compression is unknown it defaults to xz
"""
SUFFIXES = {"xz": ".xz", "gzip": ".gz", "bzip2": ".bz2", "lzma": ".lzma"}
return basename + SUFFIXES.get(compression, ".xz")
def main():
parser = lmc_parser()
opts = parser.parse_args()
setup_logging(opts.logfile, log)
log.debug( opts )
log.info("livemedia-creator v%s", vernum)
# Find the lorax templates
opts.lorax_templates = find_templates(opts.lorax_templates or "/usr/share/lorax")
# Check for invalid combinations of options, print all the errors and exit.
errors = []
if not opts.disk_image and not opts.fs_image and not opts.ks:
errors.append("Image creation requires a kickstart file")
if opts.ks and not os.path.exists(opts.ks[0]):
errors.append("kickstart file (%s) is missing." % opts.ks[0])
if opts.make_iso and not os.path.exists(opts.lorax_templates):
errors.append("The lorax templates directory (%s) doesn't "
"exist." % opts.lorax_templates)
if opts.result_dir and os.path.exists(opts.result_dir):
errors.append("The results_dir (%s) should not exist, please delete or "
"move its contents" % opts.result_dir)
# Default to putting results under tmp
if not opts.result_dir:
opts.result_dir = opts.tmp
if opts.iso and not os.path.exists(opts.iso):
errors.append("The iso %s is missing." % opts.iso)
if opts.disk_image and not os.path.exists(opts.disk_image):
errors.append("The disk image %s is missing." % opts.disk_image)
if opts.fs_image and not os.path.exists(opts.fs_image):
errors.append("The filesystem image %s is missing." % opts.fs_image)
is_install = not (opts.disk_image or opts.fs_image)
if is_install and not opts.no_virt and not opts.iso:
errors.append("virt install needs an install iso.")
if opts.volid and len(opts.volid) > 32:
errors.append("the volume id cannot be longer than 32 characters")
if is_install and not opts.no_virt \
and not any(glob.glob("/usr/bin/qemu-system-*")):
errors.append("qemu needs to be installed.")
if is_install and opts.no_virt \
and not os.path.exists("/usr/sbin/anaconda"):
errors.append("no-virt requires anaconda to be installed.")
if is_install and opts.no_virt:
if selinux.is_selinux_enabled() and selinux.security_getenforce():
errors.append("selinux must be disabled or in Permissive mode.")
if opts.make_appliance and not opts.app_template:
opts.app_template = joinpaths(opts.lorax_templates,
"appliance/libvirt.tmpl")
if opts.make_appliance and not os.path.exists(opts.app_template):
errors.append("The appliance template (%s) doesn't "
"exist" % opts.app_template)
if opts.image_name and os.path.exists(joinpaths(opts.result_dir, opts.image_name)):
errors.append("The disk image to be created should not exist.")
# Vagrant creates a qcow2 inside a tar, turn on qcow2
if opts.make_vagrant:
opts.image_type = "qcow2"
# Alias --qcow2 to --image-type=qcow2
if opts.qcow2:
opts.image_type = "qcow2"
if opts.image_type and not os.path.exists("/usr/bin/qemu-img"):
errors.append("image-type requires the qemu-img utility to be installed." % opts.image_type)
if opts.image_type and opts.make_iso:
errors.append("image-type cannot be used to make a bootable iso.")
if opts.image_type and opts.make_fsimage:
errors.append("image-type cannot be used to make filesystem images.")
if opts.image_type and opts.make_tar:
errors.append("image-type cannot be used to make a tar.")
if opts.make_oci and not (opts.oci_config and opts.oci_runtime):
errors.append("--make-oci requires --oci-config and --oci-runtime")
if opts.make_oci and not os.path.exists(opts.oci_config):
errors.append("oci % file is missing" % opts.oci_config)
if opts.make_oci and not os.path.exists(opts.oci_runtime):
errors.append("oci % file is missing" % opts.oci_runtime)
if opts.make_vagrant and opts.vagrant_metadata and not os.path.exists(opts.vagrant_metadata):
errors.append("Vagrant metadata file %s is missing" % opts.vagrant_metadata)
if opts.virt_uefi and not os.path.isdir(opts.ovmf_path):
errors.append("The OVMF firmware is missing from %s" % opts.ovmf_path)
elif opts.virt_uefi and os.path.isdir(opts.ovmf_path):
for f in ["OVMF_CODE.fd", "OVMF_VARS.fd"]:
if not os.path.exists(joinpaths(opts.ovmf_path, f)):
errors.append("OVMF firmware file %s is missing from %s" % (f, opts.ovmf_path))
if os.getuid() != 0:
errors.append("You need to run this as root")
if errors:
list(log.error(e) for e in errors)
sys.exit(1)
if not os.path.exists(opts.result_dir):
os.makedirs(opts.result_dir)
# AMI image is just a fsimage with an AMI label
if opts.make_ami:
opts.make_fsimage = True
if not opts.image_name:
opts.image_name = "ami-root.img"
if opts.fs_label == "Anaconda":
opts.fs_label = "AMI"
elif opts.make_tar:
if not opts.image_name:
opts.image_name = default_image_name(opts.compression, "root.tar")
if opts.compression == "xz" and not opts.compress_args:
opts.compress_args = ["-9"]
elif opts.make_oci:
if not opts.image_name:
opts.image_name = default_image_name(opts.compression, "bundle.tar")
if opts.compression == "xz" and not opts.compress_args:
opts.compress_args = ["-9"]
elif opts.make_vagrant:
if not opts.image_name:
opts.image_name = default_image_name(opts.compression, "vagrant.tar")
if opts.compression == "xz" and not opts.compress_args:
opts.compress_args = ["-9"]
if opts.app_file:
opts.app_file = joinpaths(opts.result_dir, opts.app_file)
if opts.make_ostree_live:
opts.make_pxe_live = True
opts.ostree = True
else:
opts.ostree = False
tempfile.tempdir = opts.tmp
disk_img = 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.")
sys.exit(1)
# Make the disk or filesystem image
if not opts.disk_image and not opts.fs_image:
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)
sys.exit(1)
# 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)
result_dir = None
if not opts.image_only:
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")
sys.exit(1)
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.")
sys.exit(1)
if opts.result_dir != opts.tmp and result_dir:
copytree(result_dir, opts.result_dir, preserve=False)
shutil.rmtree(result_dir)
result_dir = None
log.info("SUMMARY")
log.info("-------")
log.info("Logs are in %s", os.path.abspath(os.path.dirname(opts.logfile)))
if disk_img:
log.info("Disk image is at %s", disk_img)
if opts.make_appliance:
log.info("Appliance description is in %s", opts.app_file)
log.info("Results are in %s", result_dir or opts.result_dir)
sys.exit( 0 )
if __name__ == '__main__':
main()