2015-05-08 15:39:06 +00:00
|
|
|
#!/usr/bin/python3
|
2011-09-24 00:36:09 +00:00
|
|
|
#
|
|
|
|
# Live Media Creator
|
|
|
|
#
|
2015-05-08 15:39:06 +00:00
|
|
|
# Copyright (C) 2011-2015 Red Hat, Inc.
|
2011-09-24 00:36:09 +00:00
|
|
|
#
|
|
|
|
# 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/>.
|
|
|
|
#
|
|
|
|
# Author(s): Brian C. Lane <bcl@redhat.com>
|
|
|
|
#
|
|
|
|
import logging
|
|
|
|
log = logging.getLogger("livemedia-creator")
|
|
|
|
|
|
|
|
import os
|
|
|
|
import sys
|
|
|
|
import uuid
|
|
|
|
import tempfile
|
|
|
|
import subprocess
|
|
|
|
from time import sleep
|
|
|
|
import shutil
|
|
|
|
import argparse
|
2012-05-24 22:55:46 +00:00
|
|
|
import hashlib
|
2014-07-30 15:59:27 +00:00
|
|
|
import glob
|
2015-10-19 22:30:33 +00:00
|
|
|
import json
|
|
|
|
from math import ceil
|
2011-09-24 00:36:09 +00:00
|
|
|
|
|
|
|
# Use pykickstart to calculate disk image size
|
|
|
|
from pykickstart.parser import KickstartParser
|
|
|
|
from pykickstart.version import makeVersion
|
2015-11-16 22:07:32 +00:00
|
|
|
from pykickstart.constants import KS_SHUTDOWN
|
2011-09-24 00:36:09 +00:00
|
|
|
|
2012-05-24 22:55:46 +00:00
|
|
|
# Use Mako templates for appliance builder descriptions
|
|
|
|
from mako.template import Template
|
|
|
|
from mako.exceptions import text_error_template
|
|
|
|
|
2011-09-24 00:36:09 +00:00
|
|
|
# Use the Lorax treebuilder branch for iso creation
|
2015-05-27 17:36:40 +00:00
|
|
|
from pylorax import ArchData, setup_logging
|
2011-09-24 00:36:09 +00:00
|
|
|
from pylorax.base import DataHolder
|
2012-01-26 01:23:25 +00:00
|
|
|
from pylorax.treebuilder import TreeBuilder, RuntimeBuilder, udev_escape
|
2014-01-15 00:06:33 +00:00
|
|
|
from pylorax.treebuilder import findkernels
|
2013-01-30 22:16:25 +00:00
|
|
|
from pylorax.sysutils import joinpaths, remove
|
2012-05-12 00:35:16 +00:00
|
|
|
from pylorax.imgutils import PartitionMount, mksparse, mkext4img, loop_detach
|
2013-01-30 22:16:25 +00:00
|
|
|
from pylorax.imgutils import get_loop_name, dm_detach, mount, umount, Mount
|
2014-07-30 15:59:27 +00:00
|
|
|
from pylorax.imgutils import mksquashfs, mkqcow2, mktar, mkrootfsimg
|
2015-05-08 15:39:06 +00:00
|
|
|
from pylorax.imgutils import copytree
|
2015-07-29 22:42:57 +00:00
|
|
|
from pylorax.executils import execWithRedirect, execReadlines, runcmd
|
2015-05-27 17:17:38 +00:00
|
|
|
from pylorax.monitor import LogMonitor
|
2015-05-27 17:48:11 +00:00
|
|
|
from pylorax.mount import IsoMountpoint
|
2011-09-24 00:36:09 +00:00
|
|
|
|
2012-05-10 21:21:42 +00:00
|
|
|
# no-virt mode doesn't need libvirt, so make it optional
|
|
|
|
try:
|
|
|
|
import libvirt
|
|
|
|
except ImportError:
|
|
|
|
libvirt = None
|
2011-09-24 00:36:09 +00:00
|
|
|
|
2012-02-07 19:32:14 +00:00
|
|
|
# Default parameters for rebuilding initramfs, override with --dracut-args
|
2015-11-23 17:33:42 +00:00
|
|
|
DRACUT_DEFAULT = ["--xz", "--add", "livenet dmsquash-live convertfs pollcdrom qemu qemu-net",
|
2015-08-31 17:03:56 +00:00
|
|
|
"--omit", "plymouth", "--no-hostonly", "--debug", "--no-early-microcode"]
|
2011-09-24 00:36:09 +00:00
|
|
|
|
2013-01-30 22:16:25 +00:00
|
|
|
ROOT_PATH = "/mnt/sysimage/"
|
|
|
|
RUNTIME = "images/install.img"
|
2015-11-04 02:03:09 +00:00
|
|
|
UEFI_FIRMWARE="loader={0}/OVMF_CODE.fd,loader_ro=yes,loader_type=pflash,nvram_template={0}/OVMF_VARS.fd"
|
|
|
|
|
2013-01-30 22:16:25 +00:00
|
|
|
|
|
|
|
class InstallError(Exception):
|
|
|
|
pass
|
|
|
|
|
2011-09-24 00:36:09 +00:00
|
|
|
|
2015-03-09 16:15:31 +00:00
|
|
|
class FakeDNF(object):
|
|
|
|
"""
|
|
|
|
A minimal DNF object suitable for passing to RuntimeBuilder
|
|
|
|
|
|
|
|
lmc uses RuntimeBuilder to run the arch specific iso creation
|
|
|
|
templates, so the the installroot config value is the important part of
|
|
|
|
this. Everything else should be a nop.
|
|
|
|
"""
|
|
|
|
def __init__(self, conf):
|
|
|
|
self.conf = conf
|
|
|
|
|
|
|
|
def reset(self):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2014-05-09 16:28:01 +00:00
|
|
|
class VirtualInstall(object):
|
2011-09-24 00:36:09 +00:00
|
|
|
"""
|
2014-05-09 18:46:32 +00:00
|
|
|
Run virt-install using an iso and a kickstart
|
2011-09-24 00:36:09 +00:00
|
|
|
"""
|
2014-05-09 16:28:01 +00:00
|
|
|
def __init__(self, iso, ks_paths, disk_img, img_size=2048,
|
2015-11-13 21:46:08 +00:00
|
|
|
kernel_args=None, memory=1024, vcpus=None, vnc=None, arch=None,
|
2014-05-09 16:28:01 +00:00
|
|
|
log_check=None, virtio_host="127.0.0.1", virtio_port=6080,
|
2015-11-04 02:03:09 +00:00
|
|
|
qcow2=False, boot_uefi=False, ovmf_path=None):
|
2011-09-24 00:36:09 +00:00
|
|
|
"""
|
2014-05-09 18:46:32 +00:00
|
|
|
Start the installation
|
|
|
|
|
|
|
|
:param iso: Information about the iso to use for the installation
|
|
|
|
:type iso: IsoMountpoint
|
|
|
|
:param list ks_paths: Paths to kickstart files. All are injected, the
|
|
|
|
first one is the one executed.
|
|
|
|
:param str disk_img: Path to a disk image, created it it doesn't exist
|
|
|
|
:param int img_size: The image size, in MiB, to create if it doesn't exist
|
|
|
|
:param str kernel_args: Extra kernel arguments to pass on the kernel cmdline
|
|
|
|
:param int memory: Amount of RAM to assign to the virt, in MiB
|
2015-11-13 21:46:08 +00:00
|
|
|
:param int vcpus: Number of virtual cpus
|
2014-05-09 18:46:32 +00:00
|
|
|
:param str vnc: Arguments to pass to virt-install --graphics
|
|
|
|
:param str arch: Optional architecture to use in the virt
|
|
|
|
:param log_check: Method that returns True if the installation fails
|
|
|
|
:type log_check: method
|
|
|
|
:param str virtio_host: Hostname to connect virtio log to
|
|
|
|
:param int virtio_port: Port to connect virtio log to
|
|
|
|
:param bool qcow2: Set to True if disk_img is a qcow2
|
2015-11-04 02:03:09 +00:00
|
|
|
:param bool boot_uefi: Use OVMF to boot the VM in UEFI mode
|
|
|
|
:param str ovmf_path: Path to the OVMF firmware
|
2011-09-24 00:36:09 +00:00
|
|
|
"""
|
|
|
|
self.virt_name = "LiveOS-"+str(uuid.uuid4())
|
|
|
|
# add --graphics none later
|
|
|
|
# add whatever serial cmds are needed later
|
2013-01-30 22:16:25 +00:00
|
|
|
args = ["-n", self.virt_name,
|
|
|
|
"-r", str(memory),
|
|
|
|
"--noreboot",
|
|
|
|
"--noautoconsole"]
|
2011-09-24 00:36:09 +00:00
|
|
|
|
2013-01-30 22:16:25 +00:00
|
|
|
args.append("--graphics")
|
2011-09-24 00:36:09 +00:00
|
|
|
if vnc:
|
2013-01-30 22:16:25 +00:00
|
|
|
args.append(vnc)
|
2011-09-24 00:36:09 +00:00
|
|
|
else:
|
2013-01-30 22:16:25 +00:00
|
|
|
args.append("none")
|
2011-09-24 00:36:09 +00:00
|
|
|
|
|
|
|
for ks in ks_paths:
|
2013-01-30 22:16:25 +00:00
|
|
|
args.append("--initrd-inject")
|
|
|
|
args.append(ks)
|
2012-01-26 01:23:25 +00:00
|
|
|
|
2011-09-24 00:36:09 +00:00
|
|
|
disk_opts = "path={0}".format(disk_img)
|
2014-03-08 02:43:14 +00:00
|
|
|
if qcow2:
|
|
|
|
disk_opts += ",format=qcow2"
|
|
|
|
else:
|
|
|
|
disk_opts += ",format=raw"
|
2011-09-24 00:36:09 +00:00
|
|
|
if not os.path.isfile(disk_img):
|
2014-04-03 18:52:41 +00:00
|
|
|
mksparse(disk_img, img_size * 1024**2)
|
2013-01-30 22:16:25 +00:00
|
|
|
args.append("--disk")
|
|
|
|
args.append(disk_opts)
|
2012-01-26 01:23:25 +00:00
|
|
|
|
2015-03-09 15:31:15 +00:00
|
|
|
if iso.stage2:
|
2014-07-09 17:05:14 +00:00
|
|
|
disk_opts = "path={0},device=cdrom,readonly=on,shareable=on".format(iso.iso_path)
|
2013-01-30 22:16:25 +00:00
|
|
|
args.append("--disk")
|
|
|
|
args.append(disk_opts)
|
2012-01-26 01:23:25 +00:00
|
|
|
|
2011-09-24 00:36:09 +00:00
|
|
|
extra_args = "ks=file:/{0}".format(os.path.basename(ks_paths[0]))
|
2013-07-25 22:26:42 +00:00
|
|
|
if not vnc:
|
2014-07-02 16:46:02 +00:00
|
|
|
extra_args += " inst.cmdline"
|
2011-09-24 00:36:09 +00:00
|
|
|
if kernel_args:
|
|
|
|
extra_args += " "+kernel_args
|
2015-03-09 15:31:15 +00:00
|
|
|
if iso.stage2:
|
2015-11-20 22:44:09 +00:00
|
|
|
extra_args += " stage2=hd:LABEL={0}".format(udev_escape(iso.label))
|
2013-01-30 22:16:25 +00:00
|
|
|
args.append("--extra-args")
|
|
|
|
args.append(extra_args)
|
2012-01-26 01:23:25 +00:00
|
|
|
|
2013-01-30 22:16:25 +00:00
|
|
|
args.append("--location")
|
|
|
|
args.append(iso.mount_dir)
|
2012-01-26 01:23:25 +00:00
|
|
|
|
2011-09-24 00:36:09 +00:00
|
|
|
channel_args = "tcp,host={0}:{1},mode=connect,target_type=virtio" \
|
|
|
|
",name=org.fedoraproject.anaconda.log.0".format(
|
|
|
|
virtio_host, virtio_port)
|
2013-01-30 22:16:25 +00:00
|
|
|
args.append("--channel")
|
|
|
|
args.append(channel_args)
|
2012-01-26 01:23:25 +00:00
|
|
|
|
2012-05-29 18:21:15 +00:00
|
|
|
if arch:
|
2013-01-30 22:16:25 +00:00
|
|
|
args.append("--arch")
|
|
|
|
args.append(arch)
|
2012-05-29 18:21:15 +00:00
|
|
|
|
2015-11-13 21:46:08 +00:00
|
|
|
if vcpus:
|
|
|
|
args.append("--vcpus")
|
|
|
|
args.append(str(vcpus))
|
|
|
|
|
2015-11-04 02:03:09 +00:00
|
|
|
if boot_uefi and ovmf_path:
|
|
|
|
args.append("--boot")
|
|
|
|
args.append(UEFI_FIRMWARE.format(ovmf_path))
|
|
|
|
|
2014-07-02 22:10:14 +00:00
|
|
|
log.info("Running virt-install.")
|
|
|
|
try:
|
|
|
|
execWithRedirect("virt-install", args, raise_err=True)
|
|
|
|
except subprocess.CalledProcessError as e:
|
|
|
|
raise InstallError("Problem starting virtual install: %s" % e)
|
2011-09-24 00:36:09 +00:00
|
|
|
|
|
|
|
conn = libvirt.openReadOnly(None)
|
|
|
|
dom = conn.lookupByName(self.virt_name)
|
|
|
|
|
|
|
|
# TODO: If vnc has been passed, we should look up the port and print that
|
|
|
|
# for the user at this point
|
|
|
|
while dom.isActive() and not log_check():
|
|
|
|
sys.stdout.write(".")
|
|
|
|
sys.stdout.flush()
|
|
|
|
sleep(10)
|
2015-05-08 15:39:06 +00:00
|
|
|
print()
|
2011-09-24 00:36:09 +00:00
|
|
|
|
|
|
|
if log_check():
|
2014-05-09 16:28:01 +00:00
|
|
|
log.info("Installation error detected. See logfile.")
|
2011-09-24 00:36:09 +00:00
|
|
|
else:
|
2014-05-09 16:28:01 +00:00
|
|
|
log.info("Install finished. Or at least virt shut down.")
|
2011-09-24 00:36:09 +00:00
|
|
|
|
2014-05-09 16:28:01 +00:00
|
|
|
def destroy(self):
|
2011-09-24 00:36:09 +00:00
|
|
|
"""
|
|
|
|
Make sure the virt has been shut down and destroyed
|
|
|
|
|
|
|
|
Could use libvirt for this instead.
|
|
|
|
"""
|
2015-05-01 00:10:08 +00:00
|
|
|
log.info("Shutting down %s", self.virt_name)
|
2013-01-30 22:16:25 +00:00
|
|
|
subprocess.call(["virsh", "destroy", self.virt_name])
|
|
|
|
subprocess.call(["virsh", "undefine", self.virt_name])
|
2011-09-24 00:36:09 +00:00
|
|
|
|
2012-05-12 00:35:16 +00:00
|
|
|
def is_image_mounted(disk_img):
|
|
|
|
"""
|
2014-05-09 18:46:32 +00:00
|
|
|
Check to see if the disk_img is mounted
|
|
|
|
|
|
|
|
:returns: True if disk_img is in /proc/mounts
|
|
|
|
:rtype: bool
|
2012-05-12 00:35:16 +00:00
|
|
|
"""
|
|
|
|
with open("/proc/mounts") as mounts:
|
2014-05-09 00:22:14 +00:00
|
|
|
for mnt in mounts:
|
|
|
|
fields = mnt.split()
|
2012-05-12 00:35:16 +00:00
|
|
|
if len(fields) > 2 and fields[1] == disk_img:
|
|
|
|
return True
|
|
|
|
return False
|
|
|
|
|
2014-07-30 15:59:27 +00:00
|
|
|
def find_ostree_root(phys_root):
|
|
|
|
"""
|
|
|
|
Find root of ostree deployment
|
|
|
|
|
|
|
|
:param str phys_root: Path to physical root
|
|
|
|
:returns: Relative path of ostree deployment root
|
|
|
|
:rtype: str
|
|
|
|
:raise Exception: More than one deployment roots were found
|
|
|
|
"""
|
|
|
|
ostree_root = ""
|
|
|
|
ostree_sysroots = glob.glob(joinpaths(phys_root, "ostree/boot.0/*/*/0"))
|
|
|
|
if ostree_sysroots:
|
|
|
|
if len(ostree_sysroots) > 1:
|
|
|
|
raise Exception("Too many deployment roots found: %s" % ostree_sysroots)
|
|
|
|
ostree_root = os.path.relpath(ostree_sysroots[0], phys_root)
|
|
|
|
return ostree_root
|
2011-09-24 00:36:09 +00:00
|
|
|
|
2014-01-15 00:06:33 +00:00
|
|
|
def get_arch(mount_dir):
|
2014-05-09 18:46:32 +00:00
|
|
|
"""
|
|
|
|
Get the kernel arch
|
|
|
|
|
|
|
|
:returns: Arch of first kernel found at mount_dir/boot/ or i386
|
|
|
|
:rtype: str
|
2011-12-09 01:12:06 +00:00
|
|
|
"""
|
2014-01-15 00:06:33 +00:00
|
|
|
kernels = findkernels(mount_dir)
|
|
|
|
if not kernels:
|
|
|
|
return "i386"
|
|
|
|
return kernels[0].arch
|
2011-09-24 00:36:09 +00:00
|
|
|
|
|
|
|
|
2012-05-24 22:55:46 +00:00
|
|
|
def make_appliance(disk_img, name, template, outfile, networks=None, ram=1024,
|
2012-05-29 18:21:15 +00:00
|
|
|
vcpus=1, arch=None, title="Linux", project="Linux",
|
2014-02-28 21:35:03 +00:00
|
|
|
releasever="7.0"):
|
2012-05-24 22:55:46 +00:00
|
|
|
"""
|
|
|
|
Generate an appliance description file
|
|
|
|
|
2014-05-09 18:46:32 +00:00
|
|
|
:param str disk_img: Full path of the disk image
|
|
|
|
:param str name: Name of the appliance, passed to the template
|
|
|
|
:param str template: Full path of Mako template
|
|
|
|
:param str outfile: Full path of file to write, using template
|
|
|
|
:param list networks: List of networks(str) from the kickstart
|
|
|
|
:param int ram: Ram, in MiB, passed to template. Default is 1024
|
|
|
|
:param int vcpus: CPUs, passed to template. Default is 1
|
|
|
|
:param str arch: CPU architecture. Default is 'x86_64'
|
|
|
|
:param str title: Title, passed to template. Default is 'Linux'
|
|
|
|
:param str project: Project, passed to template. Default is 'Linux'
|
|
|
|
:param str releasever: Release version, passed to template. Default is 17
|
2012-05-24 22:55:46 +00:00
|
|
|
"""
|
|
|
|
if not (disk_img and template and outfile):
|
|
|
|
return None
|
|
|
|
|
2015-05-01 00:10:08 +00:00
|
|
|
log.info("Creating appliance definition using %s", template)
|
2012-05-24 22:55:46 +00:00
|
|
|
|
2012-05-29 18:21:15 +00:00
|
|
|
if not arch:
|
|
|
|
arch = "x86_64"
|
2012-05-24 22:55:46 +00:00
|
|
|
|
2015-05-01 00:10:08 +00:00
|
|
|
log.info("Calculating SHA256 checksum of %s", disk_img)
|
2012-05-24 22:55:46 +00:00
|
|
|
sha256 = hashlib.sha256()
|
|
|
|
with open(disk_img) as f:
|
|
|
|
while True:
|
2014-04-03 18:52:41 +00:00
|
|
|
data = f.read(1024**2)
|
2012-05-24 22:55:46 +00:00
|
|
|
if not data:
|
|
|
|
break
|
|
|
|
sha256.update(data)
|
2015-05-01 00:10:08 +00:00
|
|
|
log.info("SHA256 of %s is %s", disk_img, sha256.hexdigest())
|
2012-05-24 22:55:46 +00:00
|
|
|
disk_info = DataHolder(name=os.path.basename(disk_img), format="raw",
|
|
|
|
checksum_type="sha256", checksum=sha256.hexdigest())
|
|
|
|
try:
|
|
|
|
result = Template(filename=template).render(disks=[disk_info], name=name,
|
2014-02-05 22:39:04 +00:00
|
|
|
arch=arch, memory=ram, vcpus=vcpus, networks=networks,
|
2012-05-24 22:55:46 +00:00
|
|
|
title=title, project=project, releasever=releasever)
|
|
|
|
except Exception:
|
|
|
|
log.error(text_error_template().render())
|
|
|
|
raise
|
|
|
|
|
|
|
|
with open(outfile, "w") as f:
|
|
|
|
f.write(result)
|
|
|
|
|
|
|
|
|
2014-04-03 18:52:41 +00:00
|
|
|
def make_fsimage(diskimage, fsimage, img_size=None, label="Anaconda"):
|
2012-02-28 21:57:17 +00:00
|
|
|
"""
|
2014-04-02 23:56:28 +00:00
|
|
|
Copy the / partition of a partitioned disk image to an un-partitioned
|
|
|
|
disk image.
|
2012-02-28 21:57:17 +00:00
|
|
|
|
2014-05-09 18:46:32 +00:00
|
|
|
:param str diskimage: The full path to partitioned disk image with a /
|
|
|
|
:param str fsimage: The full path of the output fs image file
|
|
|
|
:param int img_size: Optional size of the fsimage in MiB or None to make
|
|
|
|
it as small as possible
|
|
|
|
:param str label: The label to apply to the image. Defaults to "Anaconda"
|
2012-02-28 21:57:17 +00:00
|
|
|
"""
|
2014-04-02 23:56:28 +00:00
|
|
|
with PartitionMount(diskimage) as img_mount:
|
2012-05-22 20:11:53 +00:00
|
|
|
if not img_mount or not img_mount.mount_dir:
|
|
|
|
return None
|
|
|
|
|
2014-05-09 00:22:14 +00:00
|
|
|
log.info("Creating fsimage %s (%s)", fsimage, img_size or "minimized")
|
2014-04-03 18:52:41 +00:00
|
|
|
if img_size:
|
|
|
|
# convert to Bytes
|
|
|
|
img_size *= 1024**2
|
|
|
|
|
|
|
|
mkext4img(img_mount.mount_dir, fsimage, size=img_size, label=label)
|
2012-02-28 21:57:17 +00:00
|
|
|
|
|
|
|
|
2013-01-30 22:16:25 +00:00
|
|
|
def make_runtime(opts, mount_dir, work_dir):
|
|
|
|
"""
|
|
|
|
Make the squashfs image from a directory
|
|
|
|
|
2014-05-09 18:46:32 +00:00
|
|
|
:param opts: options passed to livemedia-creator
|
|
|
|
:type opts: argparse options
|
|
|
|
:param str mount_dir: Directory tree to compress
|
|
|
|
:param str work_dir: Output compressed image to work_dir+images/install.img
|
2013-01-30 22:16:25 +00:00
|
|
|
"""
|
2014-01-15 00:06:33 +00:00
|
|
|
kernel_arch = get_arch(mount_dir)
|
2013-01-30 22:16:25 +00:00
|
|
|
|
2015-03-09 16:15:31 +00:00
|
|
|
# Fake dnf object
|
|
|
|
fake_dbo = FakeDNF(conf=DataHolder(installroot=mount_dir))
|
2013-01-30 22:16:25 +00:00
|
|
|
# Fake arch with only basearch set
|
2014-05-20 00:29:27 +00:00
|
|
|
arch = ArchData(kernel_arch)
|
2013-01-30 22:16:25 +00:00
|
|
|
# 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"))
|
|
|
|
|
2015-03-09 16:15:31 +00:00
|
|
|
rb = RuntimeBuilder(product, arch, fake_dbo)
|
2013-01-30 22:16:25 +00:00
|
|
|
log.info("Creating runtime")
|
|
|
|
rb.create_runtime(joinpaths(work_dir, RUNTIME), size=None)
|
|
|
|
|
2014-07-30 15:59:27 +00:00
|
|
|
def rebuild_initrds_for_live(opts, sys_root_dir, results_dir):
|
|
|
|
"""
|
|
|
|
Rebuild intrds for pxe live image (root=live:http://)
|
|
|
|
|
|
|
|
:param opts: options passed to livemedia-creator
|
|
|
|
:type opts: argparse options
|
|
|
|
:param str sys_root_dir: Path to root of the system
|
|
|
|
:param str results_dir: Path of directory for storing results
|
|
|
|
"""
|
|
|
|
if not opts.dracut_args:
|
|
|
|
dracut_args = DRACUT_DEFAULT
|
|
|
|
else:
|
|
|
|
dracut_args = []
|
|
|
|
for arg in opts.dracut_args:
|
|
|
|
dracut_args += arg.split(" ", 1)
|
2015-05-01 00:10:08 +00:00
|
|
|
log.info("dracut args = %s", dracut_args)
|
2014-07-30 15:59:27 +00:00
|
|
|
|
|
|
|
dracut = ["dracut", "--nomdadmconf", "--nolvmconf"] + dracut_args
|
|
|
|
|
|
|
|
kdir = "boot"
|
|
|
|
if opts.ostree:
|
|
|
|
kernels_dir = glob.glob(joinpaths(sys_root_dir, "boot/ostree/*"))[0]
|
|
|
|
kdir = os.path.relpath(kernels_dir, sys_root_dir)
|
|
|
|
|
|
|
|
kernels = [kernel for kernel in findkernels(sys_root_dir, kdir)
|
|
|
|
if hasattr(kernel, "initrd")]
|
|
|
|
if not kernels:
|
|
|
|
raise Exception("No initrds found, cannot rebuild_initrds")
|
|
|
|
|
|
|
|
# Hush some dracut warnings. TODO: bind-mount proc in place?
|
|
|
|
open(joinpaths(sys_root_dir,"/proc/modules"),"w")
|
|
|
|
|
|
|
|
if opts.ostree:
|
|
|
|
# Dracut assumes to have some dirs in disk image
|
|
|
|
# /var/tmp for temp files
|
|
|
|
vartmp_dir = joinpaths(sys_root_dir, "var/tmp")
|
|
|
|
if not os.path.isdir(vartmp_dir):
|
|
|
|
os.mkdir(vartmp_dir)
|
|
|
|
# /root (maybe not fatal)
|
|
|
|
root_dir = joinpaths(sys_root_dir, "var/roothome")
|
|
|
|
if not os.path.isdir(root_dir):
|
|
|
|
os.mkdir(root_dir)
|
|
|
|
# /tmp (maybe not fatal)
|
|
|
|
tmp_dir = joinpaths(sys_root_dir, "sysroot/tmp")
|
|
|
|
if not os.path.isdir(tmp_dir):
|
|
|
|
os.mkdir(tmp_dir)
|
|
|
|
|
|
|
|
for kernel in kernels:
|
|
|
|
outfile = kernel.initrd.path + ".live"
|
|
|
|
log.info("rebuilding %s", outfile)
|
|
|
|
|
|
|
|
kver = kernel.version
|
|
|
|
|
|
|
|
cmd = dracut + [outfile, kver]
|
|
|
|
runcmd(cmd, root=sys_root_dir)
|
|
|
|
|
|
|
|
new_initrd_path = joinpaths(results_dir, os.path.basename(kernel.initrd.path))
|
|
|
|
shutil.move(joinpaths(sys_root_dir, outfile), new_initrd_path)
|
2015-05-08 15:39:06 +00:00
|
|
|
os.chmod(new_initrd_path, 0o644)
|
2014-07-30 15:59:27 +00:00
|
|
|
shutil.copy2(joinpaths(sys_root_dir, kernel.path), results_dir)
|
|
|
|
|
|
|
|
os.unlink(joinpaths(sys_root_dir,"/proc/modules"))
|
|
|
|
|
|
|
|
def create_pxe_config(template, images_dir, live_image_name, add_args = None):
|
|
|
|
"""
|
|
|
|
Create template for pxe to live configuration
|
|
|
|
|
|
|
|
:param str images_dir: Path of directory with images to be used
|
|
|
|
:param str live_image_name: Name of live rootfs image file
|
|
|
|
:param list add_args: Arguments to be added to initrd= pxe config
|
|
|
|
"""
|
|
|
|
|
|
|
|
add_args = add_args or []
|
|
|
|
|
|
|
|
kernels = [kernel for kernel in findkernels(images_dir, kdir="")
|
|
|
|
if hasattr(kernel, "initrd")]
|
|
|
|
if not kernels:
|
|
|
|
return
|
|
|
|
|
|
|
|
kernel = kernels[0]
|
|
|
|
|
|
|
|
add_args_str = " ".join(add_args)
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
result = Template(filename=template).render(kernel=kernel.path,
|
|
|
|
initrd=kernel.initrd.path, liveimg=live_image_name,
|
|
|
|
addargs=add_args_str)
|
|
|
|
except Exception:
|
|
|
|
log.error(text_error_template().render())
|
|
|
|
raise
|
|
|
|
|
|
|
|
with open (joinpaths(images_dir, "PXE_CONFIG"), "w") as f:
|
|
|
|
f.write(result)
|
2013-01-30 22:16:25 +00:00
|
|
|
|
2015-10-19 22:30:33 +00:00
|
|
|
|
|
|
|
def create_vagrant_metadata(path, size=0):
|
|
|
|
""" Create a default Vagrant metadata.json file
|
|
|
|
|
|
|
|
:param str path: Path to metadata.json file
|
|
|
|
:param int size: Disk size in MiB
|
|
|
|
"""
|
|
|
|
metadata = { "provider":"libvirt", "format":"qcow2", "virtual_size": ceil(size / 1024) }
|
|
|
|
with open(path, "wt") as f:
|
|
|
|
json.dump(metadata, f, indent=4)
|
|
|
|
|
|
|
|
|
|
|
|
def update_vagrant_metadata(path, size):
|
|
|
|
""" Update the Vagrant metadata.json file
|
|
|
|
|
|
|
|
:param str path: Path to metadata.json file
|
|
|
|
:param int size: Disk size in MiB
|
|
|
|
|
|
|
|
This function makes sure that the provider, format and virtual size of the
|
|
|
|
metadata file are set correctly. All other values are left untouched.
|
|
|
|
"""
|
|
|
|
with open(path, "rt") as f:
|
|
|
|
try:
|
|
|
|
metadata = json.load(f)
|
|
|
|
except ValueError as e:
|
|
|
|
log.error("Problem reading metadata file %s: %s", path, e)
|
|
|
|
return
|
|
|
|
|
|
|
|
metadata["provider"] = "libvirt"
|
|
|
|
metadata["format"] = "qcow2"
|
|
|
|
metadata["virtual_size"] = ceil(size / 1024)
|
|
|
|
with open(path, "wt") as f:
|
|
|
|
json.dump(metadata, f, indent=4)
|
|
|
|
|
|
|
|
|
2013-01-30 22:16:25 +00:00
|
|
|
def make_livecd(opts, mount_dir, work_dir):
|
2011-09-24 00:36:09 +00:00
|
|
|
"""
|
|
|
|
Take the content from the disk image and make a livecd out of it
|
|
|
|
|
2014-05-09 18:46:32 +00:00
|
|
|
:param opts: options passed to livemedia-creator
|
|
|
|
:type opts: argparse options
|
|
|
|
:param str mount_dir: Directory tree to compress
|
|
|
|
:param str work_dir: Output compressed image to work_dir+images/install.img
|
|
|
|
|
2011-09-24 00:36:09 +00:00
|
|
|
This uses wwood's squashfs live initramfs method:
|
|
|
|
* put the real / into LiveOS/rootfs.img
|
|
|
|
* make a squashfs of the LiveOS/rootfs.img tree
|
2014-05-09 18:46:32 +00:00
|
|
|
* This is loaded by dracut when the cmdline is passed to the kernel:
|
|
|
|
root=live:CDLABEL=<volid> rd.live.image
|
2011-09-24 00:36:09 +00:00
|
|
|
"""
|
2014-01-15 00:06:33 +00:00
|
|
|
kernel_arch = get_arch(mount_dir)
|
2013-01-30 22:16:25 +00:00
|
|
|
|
2014-05-20 00:29:27 +00:00
|
|
|
arch = ArchData(kernel_arch)
|
2013-01-30 22:16:25 +00:00
|
|
|
# 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)
|
2015-05-08 15:39:06 +00:00
|
|
|
copytree(configdir, fullpath)
|
2013-01-30 22:16:25 +00:00
|
|
|
|
2014-06-30 20:42:11 +00:00
|
|
|
isolabel = opts.volid or "{0.name}-{0.version}-{1.basearch}".format(product, arch)
|
2013-01-30 22:16:25 +00:00
|
|
|
if len(isolabel) > 32:
|
|
|
|
isolabel = isolabel[:32]
|
2014-05-09 00:22:14 +00:00
|
|
|
log.warn("Truncating isolabel to 32 chars: %s", isolabel)
|
2013-01-30 22:16:25 +00:00
|
|
|
|
2013-09-26 23:47:05 +00:00
|
|
|
tb = TreeBuilder(product=product, arch=arch, domacboot=opts.domacboot,
|
2013-01-30 22:16:25 +00:00
|
|
|
inroot=mount_dir, outroot=work_dir,
|
|
|
|
runtime=RUNTIME, isolabel=isolabel,
|
|
|
|
templatedir=joinpaths(opts.lorax_templates,"live/"))
|
2014-05-09 16:28:01 +00:00
|
|
|
log.info("Rebuilding initrds")
|
2013-01-30 22:16:25 +00:00
|
|
|
if not opts.dracut_args:
|
|
|
|
dracut_args = DRACUT_DEFAULT
|
|
|
|
else:
|
|
|
|
dracut_args = []
|
|
|
|
for arg in opts.dracut_args:
|
|
|
|
dracut_args += arg.split(" ", 1)
|
2015-05-01 00:10:08 +00:00
|
|
|
log.info("dracut args = %s", dracut_args)
|
2013-01-30 22:16:25 +00:00
|
|
|
tb.rebuild_initrds(add_args=dracut_args)
|
|
|
|
log.info("Building boot.iso")
|
|
|
|
tb.build()
|
2012-02-28 21:57:17 +00:00
|
|
|
|
2013-01-30 22:16:25 +00:00
|
|
|
return work_dir
|
2011-09-24 00:36:09 +00:00
|
|
|
|
2014-07-30 15:59:27 +00:00
|
|
|
def mount_boot_part_over_root(img_mount):
|
|
|
|
"""
|
|
|
|
Mount boot partition to /boot of root fs mounted in img_mount
|
|
|
|
|
|
|
|
Used for OSTree so it finds deployment configurations on live rootfs
|
|
|
|
|
|
|
|
param img_mount: object with mounted disk image root partition
|
|
|
|
type img_mount: imgutils.PartitionMount
|
|
|
|
"""
|
|
|
|
root_dir = img_mount.mount_dir
|
|
|
|
is_boot_part = lambda dir: os.path.exists(dir+"/loader.0")
|
|
|
|
tmp_mount_dir = tempfile.mkdtemp()
|
|
|
|
sys_root = find_ostree_root(root_dir)
|
|
|
|
sysroot_boot_dir = None
|
|
|
|
for dev, _size in img_mount.loop_devices:
|
|
|
|
if dev is img_mount.mount_dev:
|
|
|
|
continue
|
|
|
|
try:
|
|
|
|
mount("/dev/mapper/"+dev, mnt=tmp_mount_dir)
|
|
|
|
if is_boot_part(tmp_mount_dir):
|
|
|
|
umount(tmp_mount_dir)
|
|
|
|
sysroot_boot_dir = joinpaths(joinpaths(root_dir, sys_root), "boot")
|
|
|
|
mount("/dev/mapper/"+dev, mnt=sysroot_boot_dir)
|
|
|
|
break
|
|
|
|
else:
|
|
|
|
umount(tmp_mount_dir)
|
|
|
|
except subprocess.CalledProcessError as e:
|
|
|
|
log.debug("Looking for boot partition error: %s", e)
|
|
|
|
remove(tmp_mount_dir)
|
|
|
|
return sysroot_boot_dir
|
2013-01-30 22:16:25 +00:00
|
|
|
|
2015-01-09 21:13:55 +00:00
|
|
|
def novirt_log_check(log_check, proc):
|
|
|
|
"""
|
|
|
|
Check to see if there has been an error in the logs
|
|
|
|
|
|
|
|
:param log_check: method to call to check for an error in the logs
|
|
|
|
:param proc: Popen object for the anaconda process
|
|
|
|
:returns: True if the process has been terminated
|
|
|
|
|
|
|
|
The log_check method should return a True if an error has been detected.
|
|
|
|
When an error is detected the process is terminated and this returns True
|
|
|
|
"""
|
|
|
|
if log_check():
|
|
|
|
proc.terminate()
|
|
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
2013-01-30 22:16:25 +00:00
|
|
|
def novirt_install(opts, disk_img, disk_size, repo_url):
|
|
|
|
"""
|
|
|
|
Use Anaconda to install to a disk image
|
2014-04-03 18:52:41 +00:00
|
|
|
|
2014-05-09 18:46:32 +00:00
|
|
|
: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 str repo_url: The url of the repository to use for the installation
|
|
|
|
|
|
|
|
This method makes sure SELinux is permissive during the install, runs anaconda
|
|
|
|
to create the image and then based on the opts passed create a qcow2 image
|
|
|
|
or tarfile.
|
2013-01-30 22:16:25 +00:00
|
|
|
"""
|
|
|
|
import selinux
|
2015-10-20 21:23:33 +00:00
|
|
|
dirinstall_path = ROOT_PATH
|
2013-01-30 22:16:25 +00:00
|
|
|
|
|
|
|
# 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)
|
|
|
|
|
2013-11-19 01:09:00 +00:00
|
|
|
# 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)
|
|
|
|
|
2013-01-30 22:16:25 +00:00
|
|
|
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"]
|
|
|
|
|
2014-04-03 18:52:41 +00:00
|
|
|
mkext4img(None, disk_img, label=opts.fs_label, size=disk_size * 1024**2)
|
2015-10-20 21:23:33 +00:00
|
|
|
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"]
|
2014-04-04 21:38:51 +00:00
|
|
|
|
2015-10-20 21:23:33 +00:00
|
|
|
os.makedirs(dirinstall_path)
|
2013-01-30 22:16:25 +00:00
|
|
|
else:
|
|
|
|
args += ["--image", disk_img]
|
|
|
|
|
|
|
|
# Create the sparse image
|
2014-04-03 18:52:41 +00:00
|
|
|
mksparse(disk_img, disk_size * 1024**2)
|
2013-01-30 22:16:25 +00:00
|
|
|
|
2015-02-02 20:24:06 +00:00
|
|
|
log_monitor = LogMonitor(timeout=opts.timeout)
|
2015-01-09 21:13:55 +00:00
|
|
|
args += ["--remotelog", "%s:%s" % (log_monitor.host, log_monitor.port)]
|
|
|
|
|
2014-02-21 00:27:10 +00:00
|
|
|
# Make sure anaconda has the right product and release
|
2014-07-02 22:10:14 +00:00
|
|
|
log.info("Running anaconda.")
|
|
|
|
try:
|
2015-07-29 22:42:57 +00:00
|
|
|
for line in execReadlines("anaconda", args, reset_lang=False,
|
|
|
|
env_add={"ANACONDA_PRODUCTNAME": opts.project,
|
|
|
|
"ANACONDA_PRODUCTVERSION": opts.releasever},
|
|
|
|
callback=lambda p: not novirt_log_check(log_monitor.server.log_check, p)):
|
|
|
|
log.info(line)
|
2014-08-06 16:11:55 +00:00
|
|
|
|
|
|
|
# Make sure the new filesystem is correctly labeled
|
2015-10-20 21:23:33 +00:00
|
|
|
setfiles_args = ["-e", "/proc", "-e", "/sys", "-e", "/dev",
|
|
|
|
"/etc/selinux/targeted/contexts/files/file_contexts", "/"]
|
|
|
|
if "--dirinstall" in args:
|
|
|
|
execWithRedirect("setfiles", setfiles_args, root=dirinstall_path)
|
2014-08-06 16:11:55 +00:00
|
|
|
else:
|
|
|
|
with PartitionMount(disk_img) as img_mount:
|
|
|
|
if img_mount and img_mount.mount_dir:
|
2015-10-20 21:23:33 +00:00
|
|
|
execWithRedirect("setfiles", setfiles_args, root=img_mount.mount_dir)
|
2015-07-29 22:42:57 +00:00
|
|
|
except (subprocess.CalledProcessError, OSError) as e:
|
2014-07-02 22:10:14 +00:00
|
|
|
log.error("Running anaconda failed: %s", e)
|
2013-01-30 22:16:25 +00:00
|
|
|
raise InstallError("novirt_install failed")
|
2014-07-02 22:10:14 +00:00
|
|
|
finally:
|
2015-01-09 21:13:55 +00:00
|
|
|
log_monitor.shutdown()
|
|
|
|
|
2015-07-29 22:42:57 +00:00
|
|
|
# If anaconda failed there may be things needing cleanup
|
|
|
|
execWithRedirect("anaconda-cleanup", [])
|
|
|
|
|
2014-07-02 22:10:14 +00:00
|
|
|
# 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)
|
2015-01-14 19:01:23 +00:00
|
|
|
for l in glob.glob("/tmp/*log")+glob.glob("/tmp/anaconda-tb-*"):
|
2015-05-01 00:10:08 +00:00
|
|
|
shutil.copy2(l, log_anaconda)
|
|
|
|
os.unlink(l)
|
2014-07-02 22:10:14 +00:00
|
|
|
|
2015-07-29 22:42:57 +00:00
|
|
|
if not opts.make_iso and not opts.make_fsimage:
|
2014-07-02 22:10:14 +00:00
|
|
|
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)
|
2013-01-30 22:16:25 +00:00
|
|
|
|
2015-10-21 00:28:10 +00:00
|
|
|
# qcow2 is used by bare qcow2 images and by Vagrant
|
2014-03-08 02:43:14 +00:00
|
|
|
if opts.qcow2:
|
|
|
|
log.info("Converting %s to qcow2", disk_img)
|
|
|
|
qcow2_args = []
|
|
|
|
for arg in opts.qcow2_args:
|
|
|
|
qcow2_args += arg.split(" ", 1)
|
|
|
|
|
|
|
|
# convert the image to qcow2 format
|
|
|
|
if "-O" not in qcow2_args:
|
|
|
|
qcow2_args.extend(["-O", "qcow2"])
|
2015-03-20 00:22:16 +00:00
|
|
|
qcow2_img = tempfile.mktemp(prefix="disk", suffix=".img")
|
2014-03-08 02:43:14 +00:00
|
|
|
execWithRedirect("qemu-img", ["convert"] + qcow2_args + [disk_img, qcow2_img], raise_err=True)
|
2015-10-21 00:28:10 +00:00
|
|
|
if not opts.make_vagrant:
|
|
|
|
execWithRedirect("mv", ["-f", qcow2_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()
|
|
|
|
metadata_path = joinpaths(vagrant_dir, "metadata.json")
|
|
|
|
execWithRedirect("mv", ["-f", qcow2_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)
|
2014-04-04 21:38:51 +00:00
|
|
|
elif opts.make_tar:
|
|
|
|
compress_args = []
|
|
|
|
for arg in opts.compress_args:
|
|
|
|
compress_args += arg.split(" ", 1)
|
|
|
|
|
2015-10-20 21:23:33 +00:00
|
|
|
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)
|
2014-04-04 21:38:51 +00:00
|
|
|
rc = mktar(ROOT_PATH, disk_img, opts.compression, compress_args)
|
|
|
|
|
|
|
|
if rc:
|
2014-07-02 22:10:14 +00:00
|
|
|
raise InstallError("novirt_install mktar failed: rc=%s" % rc)
|
2014-03-08 02:43:14 +00:00
|
|
|
|
2013-01-30 22:16:25 +00:00
|
|
|
|
|
|
|
def virt_install(opts, install_log, disk_img, disk_size):
|
|
|
|
"""
|
|
|
|
Use virt-install to install to a disk image
|
2014-04-02 23:56:28 +00:00
|
|
|
|
2014-05-09 18:46:32 +00:00
|
|
|
:param opts: options passed to livemedia-creator
|
|
|
|
:type opts: argparse options
|
|
|
|
:param str install_log: The path to write the log from virt-install
|
|
|
|
: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
|
|
|
|
|
|
|
|
This uses virt-install with a boot.iso and a kickstart to create a disk
|
|
|
|
image and then optionally, based on the opts passed, creates tarfile.
|
2013-01-30 22:16:25 +00:00
|
|
|
"""
|
|
|
|
iso_mount = IsoMountpoint(opts.iso, opts.location)
|
2015-02-02 20:24:06 +00:00
|
|
|
log_monitor = LogMonitor(install_log, timeout=opts.timeout)
|
2013-01-30 22:16:25 +00:00
|
|
|
|
|
|
|
kernel_args = ""
|
|
|
|
if opts.kernel_args:
|
|
|
|
kernel_args += opts.kernel_args
|
|
|
|
if opts.proxy:
|
|
|
|
kernel_args += " proxy="+opts.proxy
|
|
|
|
|
2014-04-02 23:56:28 +00:00
|
|
|
if opts.qcow2 and not opts.make_fsimage:
|
2014-03-08 02:43:14 +00:00
|
|
|
# virt-install can't take all the qcow2 options so create the image first
|
|
|
|
qcow2_args = []
|
|
|
|
for arg in opts.qcow2_args:
|
|
|
|
qcow2_args += arg.split(" ", 1)
|
|
|
|
|
2014-04-03 18:52:41 +00:00
|
|
|
mkqcow2(disk_img, disk_size*1024**2, qcow2_args)
|
2014-03-08 02:43:14 +00:00
|
|
|
|
2015-10-19 22:35:50 +00:00
|
|
|
if opts.make_fsimage or opts.make_tar or opts.make_oci:
|
2015-03-20 00:22:16 +00:00
|
|
|
diskimg_path = tempfile.mktemp(prefix="disk", suffix=".img")
|
2014-04-02 23:56:28 +00:00
|
|
|
else:
|
|
|
|
diskimg_path = disk_img
|
|
|
|
|
2014-05-29 23:54:49 +00:00
|
|
|
try:
|
|
|
|
virt = VirtualInstall(iso_mount, opts.ks, diskimg_path, disk_size,
|
2015-11-13 21:46:08 +00:00
|
|
|
kernel_args, opts.ram, opts.vcpus, opts.vnc, opts.arch,
|
2014-05-29 23:54:49 +00:00
|
|
|
log_check = log_monitor.server.log_check,
|
|
|
|
virtio_host = log_monitor.host,
|
|
|
|
virtio_port = log_monitor.port,
|
2015-11-04 02:03:09 +00:00
|
|
|
qcow2=opts.qcow2, boot_uefi=opts.virt_uefi,
|
|
|
|
ovmf_path=opts.ovmf_path)
|
2014-05-29 23:54:49 +00:00
|
|
|
|
|
|
|
virt.destroy()
|
|
|
|
log_monitor.shutdown()
|
|
|
|
except InstallError as e:
|
|
|
|
log.error("VirtualInstall failed: %s", e)
|
|
|
|
raise
|
|
|
|
finally:
|
|
|
|
log.info("unmounting the iso")
|
|
|
|
iso_mount.umount()
|
2013-01-30 22:16:25 +00:00
|
|
|
|
|
|
|
if log_monitor.server.log_check():
|
2015-02-02 20:24:06 +00:00
|
|
|
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)
|
2013-01-30 22:16:25 +00:00
|
|
|
|
2014-04-02 23:56:28 +00:00
|
|
|
if opts.make_fsimage:
|
2014-04-03 18:52:41 +00:00
|
|
|
make_fsimage(diskimg_path, disk_img, disk_size, label=opts.fs_label)
|
2014-04-02 23:56:28 +00:00
|
|
|
os.unlink(diskimg_path)
|
2014-04-04 21:38:51 +00:00
|
|
|
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)
|
2015-11-20 22:47:41 +00:00
|
|
|
else:
|
|
|
|
rc = 1
|
2014-04-04 21:38:51 +00:00
|
|
|
os.unlink(diskimg_path)
|
|
|
|
|
|
|
|
if rc:
|
|
|
|
raise InstallError("virt_install failed")
|
2015-10-19 22:35:50 +00:00
|
|
|
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)
|
2015-11-20 22:47:41 +00:00
|
|
|
else:
|
|
|
|
rc = 1
|
2015-10-19 22:35:50 +00:00
|
|
|
os.unlink(diskimg_path)
|
|
|
|
|
|
|
|
if rc:
|
|
|
|
raise InstallError("virt_install failed")
|
2015-10-19 22:30:33 +00:00
|
|
|
elif opts.make_vagrant:
|
|
|
|
compress_args = []
|
|
|
|
for arg in opts.compress_args:
|
|
|
|
compress_args += arg.split(" ", 1)
|
|
|
|
|
|
|
|
vagrant_dir = tempfile.mkdtemp()
|
|
|
|
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")
|
2015-10-21 00:28:10 +00:00
|
|
|
shutil.rmtree(vagrant_dir)
|
2014-04-02 23:56:28 +00:00
|
|
|
|
2013-01-30 22:16:25 +00:00
|
|
|
|
|
|
|
def make_squashfs(disk_img, work_dir, compression="xz"):
|
|
|
|
"""
|
2014-05-09 18:46:32 +00:00
|
|
|
Create a squashfs image of an unpartitioned filesystem disk image
|
|
|
|
|
|
|
|
:param str disk_img: Path to the unpartitioned filesystem disk image
|
|
|
|
:param str work_dir: Output compressed image to work_dir+images/install.img
|
|
|
|
:param str compression: Compression type to use
|
|
|
|
|
2013-01-30 22:16:25 +00:00
|
|
|
Take disk_img and put it into LiveOS/rootfs.img and squashfs this
|
2014-05-09 18:46:32 +00:00
|
|
|
tree into work_dir+images/install.img
|
2013-01-30 22:16:25 +00:00
|
|
|
"""
|
|
|
|
liveos_dir = joinpaths(work_dir, "runtime/LiveOS")
|
|
|
|
os.makedirs(liveos_dir)
|
|
|
|
os.makedirs(os.path.dirname(joinpaths(work_dir, RUNTIME)))
|
2014-04-25 16:11:54 +00:00
|
|
|
|
|
|
|
rc = execWithRedirect("/bin/ln", [disk_img, joinpaths(liveos_dir, "rootfs.img")])
|
|
|
|
if rc != 0:
|
|
|
|
shutil.copy2(disk_img, joinpaths(liveos_dir, "rootfs.img"))
|
|
|
|
|
2013-01-30 22:16:25 +00:00
|
|
|
mksquashfs(joinpaths(work_dir, "runtime"),
|
|
|
|
joinpaths(work_dir, RUNTIME), compression)
|
|
|
|
remove(joinpaths(work_dir, "runtime"))
|
|
|
|
|
|
|
|
|
|
|
|
def make_image(opts, ks):
|
|
|
|
"""
|
2014-05-09 18:46:32 +00:00
|
|
|
Install to a disk image
|
2013-01-30 22:16:25 +00:00
|
|
|
|
2014-05-09 18:46:32 +00:00
|
|
|
:param opts: options passed to livemedia-creator
|
|
|
|
:type opts: argparse options
|
|
|
|
:param str ks: Path to the kickstart to use for the installation
|
|
|
|
:returns: Path of the image created
|
|
|
|
:rtype: str
|
2013-01-30 22:16:25 +00:00
|
|
|
|
2014-05-09 18:46:32 +00:00
|
|
|
Use virt-install or anaconda to install to a disk image.
|
2013-01-30 22:16:25 +00:00
|
|
|
"""
|
2015-08-26 22:54:44 +00:00
|
|
|
# Disk size for a filesystem image should only be the size of /
|
|
|
|
# to prevent surprises when using the same kickstart for different installations.
|
|
|
|
if opts.no_virt and (opts.make_iso or opts.make_fsimage):
|
|
|
|
disk_size = 2 + sum(p.size for p in ks.handler.partition.partitions if p.mountpoint == "/")
|
|
|
|
else:
|
|
|
|
disk_size = 2 + sum(p.size for p in ks.handler.partition.partitions)
|
2014-04-03 18:52:41 +00:00
|
|
|
log.info("disk_size = %sMiB", disk_size)
|
2013-01-30 22:16:25 +00:00
|
|
|
|
|
|
|
if opts.image_name:
|
2015-03-20 00:22:16 +00:00
|
|
|
disk_img = joinpaths(opts.result_dir, opts.image_name)
|
2013-01-30 22:16:25 +00:00
|
|
|
else:
|
2015-03-20 00:22:16 +00:00
|
|
|
disk_img = tempfile.mktemp(prefix="disk", suffix=".img", dir=opts.result_dir)
|
2014-04-02 23:56:28 +00:00
|
|
|
log.info("disk_img = %s", disk_img)
|
2013-01-30 22:16:25 +00:00
|
|
|
|
|
|
|
try:
|
|
|
|
if opts.no_virt:
|
2014-10-10 00:27:39 +00:00
|
|
|
method = ks.handler.method
|
|
|
|
if method.method == "url":
|
|
|
|
repo_url = method.url
|
|
|
|
elif method.method == "nfs":
|
|
|
|
if method.opts:
|
|
|
|
repo_url = "nfs:%s:%s:%s" % (method.opts, method.server, method.dir)
|
|
|
|
else:
|
|
|
|
repo_url = "nfs:%s:%s" % (method.server, method.dir)
|
|
|
|
else:
|
|
|
|
raise InstallError("Unsupported installation method: %s" % method.method)
|
|
|
|
|
|
|
|
novirt_install(opts, disk_img, disk_size, repo_url)
|
2011-09-24 00:36:09 +00:00
|
|
|
else:
|
2014-04-02 23:56:28 +00:00
|
|
|
install_log = os.path.abspath(os.path.dirname(opts.logfile))+"/virt-install.log"
|
|
|
|
log.info("install_log = %s", install_log)
|
|
|
|
|
2013-01-30 22:16:25 +00:00
|
|
|
virt_install(opts, install_log, disk_img, disk_size)
|
|
|
|
except InstallError as e:
|
2015-05-01 00:10:08 +00:00
|
|
|
log.error("Install failed: %s", e)
|
2014-05-29 23:54:49 +00:00
|
|
|
if not opts.keep_image and os.path.exists(disk_img):
|
2013-01-30 22:16:25 +00:00
|
|
|
log.info("Removing bad disk image")
|
|
|
|
os.unlink(disk_img)
|
|
|
|
raise
|
2011-09-24 00:36:09 +00:00
|
|
|
|
2013-01-30 22:16:25 +00:00
|
|
|
log.info("Disk Image install successful")
|
|
|
|
return disk_img
|
2011-09-24 00:36:09 +00:00
|
|
|
|
|
|
|
|
2015-01-15 10:22:30 +00:00
|
|
|
def make_live_images(opts, work_dir, root_dir, rootfs_image=None, size=None):
|
2014-07-30 15:59:27 +00:00
|
|
|
"""
|
|
|
|
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 root_dir: Root directory of live filesystem tree
|
|
|
|
:param str rootfs_image: Path to live rootfs image to be used
|
|
|
|
:returns: Path of directory with created images
|
|
|
|
:rtype: str
|
|
|
|
"""
|
|
|
|
sys_root = ""
|
|
|
|
if opts.ostree:
|
|
|
|
sys_root = find_ostree_root(root_dir)
|
|
|
|
|
|
|
|
squashfs_root_dir = joinpaths(work_dir, "squashfs_root")
|
|
|
|
liveos_dir = joinpaths(squashfs_root_dir, "LiveOS")
|
|
|
|
os.makedirs(liveos_dir)
|
|
|
|
|
|
|
|
if rootfs_image:
|
|
|
|
rc = execWithRedirect("/bin/ln", [rootfs_image, joinpaths(liveos_dir, "rootfs.img")])
|
|
|
|
if rc != 0:
|
|
|
|
shutil.copy2(rootfs_image, joinpaths(liveos_dir, "rootfs.img"))
|
|
|
|
else:
|
|
|
|
log.info("Creating live rootfs image")
|
2015-01-15 10:22:30 +00:00
|
|
|
mkrootfsimg(root_dir, joinpaths(liveos_dir, "rootfs.img"), "LiveOS", size=size, sysroot=sys_root)
|
2014-07-30 15:59:27 +00:00
|
|
|
|
|
|
|
log.info("Packing live rootfs image")
|
|
|
|
add_pxe_args = []
|
|
|
|
live_image_name = "live-rootfs.squashfs.img"
|
|
|
|
mksquashfs(squashfs_root_dir,
|
|
|
|
joinpaths(work_dir, live_image_name),
|
|
|
|
opts.compression,
|
|
|
|
opts.compress_args)
|
|
|
|
|
|
|
|
remove(squashfs_root_dir)
|
|
|
|
|
|
|
|
log.info("Rebuilding initramfs for live")
|
|
|
|
rebuild_initrds_for_live(opts, joinpaths(root_dir, sys_root), work_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
|
|
|
|
|
|
|
|
|
2014-05-09 00:22:14 +00:00
|
|
|
def main():
|
2014-05-09 16:28:01 +00:00
|
|
|
parser = argparse.ArgumentParser(description="Create Live Install Media",
|
|
|
|
fromfile_prefix_chars="@")
|
2011-09-24 00:36:09 +00:00
|
|
|
|
2011-12-09 01:12:06 +00:00
|
|
|
# These are mutually exclusive, one is required
|
2014-05-09 16:28:01 +00:00
|
|
|
action = parser.add_mutually_exclusive_group(required=True)
|
|
|
|
action.add_argument("--make-iso", action="store_true",
|
|
|
|
help="Build a live iso")
|
|
|
|
action.add_argument("--make-disk", action="store_true",
|
|
|
|
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",
|
|
|
|
help="Build an ami image")
|
|
|
|
action.add_argument("--make-tar", action="store_true",
|
|
|
|
help="Build a tar of the root filesystem")
|
2014-07-30 15:59:27 +00:00
|
|
|
action.add_argument("--make-pxe-live", action="store_true",
|
|
|
|
help="Build a live pxe boot squashfs image")
|
|
|
|
action.add_argument("--make-ostree-live", action="store_true",
|
|
|
|
help="Build a live pxe boot squashfs image of Atomic Host")
|
2015-10-19 22:35:50 +00:00
|
|
|
action.add_argument("--make-oci", action="store_true",
|
|
|
|
help="Build an Open Container Initiative image")
|
2015-10-19 22:30:33 +00:00
|
|
|
action.add_argument("--make-vagrant", action="store_true",
|
|
|
|
help="Build a Vagrant Box image")
|
2014-05-09 16:28:01 +00:00
|
|
|
|
|
|
|
parser.add_argument("--iso", type=os.path.abspath,
|
|
|
|
help="Anaconda installation .iso path to use for virt-install")
|
|
|
|
parser.add_argument("--ks", action="append", type=os.path.abspath,
|
|
|
|
help="Kickstart file defining the install.")
|
|
|
|
parser.add_argument("--image-only", action="store_true",
|
|
|
|
help="Exit after creating fs/disk image.")
|
|
|
|
|
|
|
|
parser.add_argument("--no-virt", action="store_true",
|
|
|
|
help="Use Anaconda's image install instead of virt-install")
|
|
|
|
parser.add_argument("--proxy",
|
|
|
|
help="proxy URL to use for the install")
|
|
|
|
parser.add_argument("--anaconda-arg", action="append", dest="anaconda_args",
|
|
|
|
help="Additional argument to pass to anaconda (no-virt "
|
|
|
|
"mode). Pass once for each argument")
|
|
|
|
parser.add_argument("--armplatform",
|
|
|
|
help="the platform to use when creating images for ARM, "
|
|
|
|
"i.e., highbank, mvebu, omap, tegra, etc.")
|
|
|
|
parser.add_argument("--location", default=None, type=os.path.abspath,
|
|
|
|
help="location of iso directory tree with initrd.img "
|
|
|
|
"and vmlinuz. Used to run virt-install with a "
|
|
|
|
"newer initrd than the iso.")
|
|
|
|
|
|
|
|
parser.add_argument("--logfile", default="./livemedia.log",
|
|
|
|
type=os.path.abspath,
|
|
|
|
help="Path to logfile")
|
|
|
|
parser.add_argument("--lorax-templates", default="/usr/share/lorax/",
|
|
|
|
type=os.path.abspath,
|
|
|
|
help="Path to mako templates for lorax")
|
|
|
|
parser.add_argument("--tmp", default="/var/tmp", type=os.path.abspath,
|
|
|
|
help="Top level temporary directory")
|
|
|
|
parser.add_argument("--resultdir", default=None, dest="result_dir",
|
|
|
|
type=os.path.abspath,
|
|
|
|
help="Directory to copy the resulting images and iso into. "
|
|
|
|
"Defaults to the temporary working directory")
|
|
|
|
|
|
|
|
parser.add_argument("--macboot", action="store_true", default=True,
|
|
|
|
dest="domacboot")
|
|
|
|
parser.add_argument("--nomacboot", action="store_false",
|
|
|
|
dest="domacboot")
|
2013-09-26 23:47:05 +00:00
|
|
|
|
2014-03-08 02:43:14 +00:00
|
|
|
image_group = parser.add_argument_group("disk/fs image arguments")
|
2014-05-09 16:28:01 +00:00
|
|
|
image_group.add_argument("--disk-image", type=os.path.abspath,
|
|
|
|
help="Path to existing disk image to use for creating final image.")
|
|
|
|
image_group.add_argument("--keep-image", action="store_true",
|
|
|
|
help="Keep raw disk image after .iso creation")
|
|
|
|
image_group.add_argument("--fs-image", type=os.path.abspath,
|
|
|
|
help="Path to existing filesystem image to use for creating final image.")
|
|
|
|
image_group.add_argument("--image-name", default=None,
|
|
|
|
help="Name of output file to create. Used for tar, fs and disk image. Default is a random name.")
|
|
|
|
image_group.add_argument("--fs-label", default="Anaconda",
|
|
|
|
help="Label to set on fsimage, default is 'Anaconda'")
|
2014-03-08 02:43:14 +00:00
|
|
|
image_group.add_argument("--qcow2", action="store_true",
|
2014-05-09 16:28:01 +00:00
|
|
|
help="Create qcow2 image instead of raw sparse image when making disk images.")
|
2014-03-08 02:43:14 +00:00
|
|
|
image_group.add_argument("--qcow2-arg", action="append", dest="qcow2_args", default=[],
|
2014-05-09 16:28:01 +00:00
|
|
|
help="Arguments to pass to qemu-img. Pass once for each argument")
|
2014-04-04 21:38:51 +00:00
|
|
|
image_group.add_argument("--compression", default="xz",
|
2014-05-09 16:28:01 +00:00
|
|
|
help="Compression binary for make-tar. xz, lzma, gzip, and bzip2 are supported. xz is the default.")
|
2014-04-04 21:38:51 +00:00
|
|
|
image_group.add_argument("--compress-arg", action="append", dest="compress_args", default=[],
|
2014-05-09 16:28:01 +00:00
|
|
|
help="Arguments to pass to compression. Pass once for each argument")
|
2012-05-24 22:55:46 +00:00
|
|
|
# Group of arguments for appliance creation
|
|
|
|
app_group = parser.add_argument_group("appliance arguments")
|
2014-05-09 16:28:01 +00:00
|
|
|
app_group.add_argument("--app-name", default=None,
|
|
|
|
help="Name of appliance to pass to template")
|
|
|
|
app_group.add_argument("--app-template", default=None,
|
|
|
|
help="Path to template to use for appliance data.")
|
|
|
|
app_group.add_argument("--app-file", default="appliance.xml",
|
|
|
|
help="Appliance template results file.")
|
2012-05-24 22:55:46 +00:00
|
|
|
|
2011-09-24 00:36:09 +00:00
|
|
|
# Group of arguments to pass to virt-install
|
2012-05-10 21:21:42 +00:00
|
|
|
if not libvirt:
|
|
|
|
virt_group = parser.add_argument_group("virt-install arguments (DISABLED -- no libvirt)")
|
|
|
|
else:
|
|
|
|
virt_group = parser.add_argument_group("virt-install arguments")
|
2013-10-30 18:06:37 +00:00
|
|
|
virt_group.add_argument("--ram", metavar="MEMORY", type=int, default=1024,
|
2014-05-09 16:28:01 +00:00
|
|
|
help="Memory to allocate for installer in megabytes.")
|
2015-11-13 21:46:08 +00:00
|
|
|
virt_group.add_argument("--vcpus", type=int, default=None,
|
2014-05-09 16:28:01 +00:00
|
|
|
help="Passed to --vcpus command")
|
2011-09-24 00:36:09 +00:00
|
|
|
virt_group.add_argument("--vnc",
|
2014-05-09 16:28:01 +00:00
|
|
|
help="Passed to --graphics command")
|
2012-05-29 18:21:15 +00:00
|
|
|
virt_group.add_argument("--arch", default=None,
|
2014-05-09 16:28:01 +00:00
|
|
|
help="Passed to --arch command")
|
|
|
|
virt_group.add_argument("--kernel-args",
|
|
|
|
help="Additional argument to pass to the installation kernel")
|
2015-11-04 02:03:09 +00:00
|
|
|
virt_group.add_argument("--ovmf-path", default="/usr/share/OVMF/",
|
|
|
|
help="Path to OVMF firmware")
|
|
|
|
virt_group.add_argument("--virt-uefi", action="store_true", default=False,
|
|
|
|
help="Use OVMF firmware to boot the VM in UEFI mode")
|
2011-09-24 00:36:09 +00:00
|
|
|
|
|
|
|
# dracut arguments
|
2014-05-09 16:28:01 +00:00
|
|
|
dracut_group = parser.add_argument_group("dracut arguments")
|
|
|
|
dracut_group.add_argument("--dracut-arg", action="append", dest="dracut_args",
|
|
|
|
help="Argument to pass to dracut when "
|
|
|
|
"rebuilding the initramfs. Pass this "
|
|
|
|
"once for each argument. NOTE: this "
|
|
|
|
"overrides the default. (default: %s)" % (DRACUT_DEFAULT,))
|
|
|
|
|
2015-01-15 10:22:30 +00:00
|
|
|
# pxe to live arguments
|
|
|
|
pxelive_group = parser.add_argument_group("pxe to live arguments")
|
|
|
|
pxelive_group.add_argument("--live-rootfs-size", type=int, default=0,
|
|
|
|
help="Size of root filesystem of live image in GiB")
|
2015-01-15 10:26:44 +00:00
|
|
|
pxelive_group.add_argument("--live-rootfs-keep-size", action="store_true",
|
|
|
|
help="Keep the original size of root filesystem in live image")
|
|
|
|
|
2015-10-19 22:35:50 +00:00
|
|
|
# OCI specific commands
|
|
|
|
oci_group = parser.add_argument_group("OCI arguments")
|
|
|
|
oci_group.add_argument("--oci-config",
|
|
|
|
help="config.json OCI configuration file")
|
|
|
|
oci_group.add_argument("--oci-runtime",
|
|
|
|
help="runtime.json OCI configuration file")
|
2015-01-15 10:22:30 +00:00
|
|
|
|
2015-10-19 22:30:33 +00:00
|
|
|
# Vagrant specific commands
|
|
|
|
vagrant_group = parser.add_argument_group("Vagrant arguments")
|
|
|
|
vagrant_group.add_argument("--vagrant-metadata",
|
|
|
|
help="optional metadata.json file")
|
|
|
|
vagrant_group.add_argument("--vagrantfile",
|
|
|
|
help="optional vagrantfile")
|
|
|
|
|
2014-05-09 16:28:01 +00:00
|
|
|
parser.add_argument("--title", default="Linux Live Media",
|
|
|
|
help="Substituted for @TITLE@ in bootloader config files")
|
|
|
|
parser.add_argument("--project", default="Linux",
|
|
|
|
help="substituted for @PROJECT@ in bootloader config files")
|
2015-01-20 18:59:18 +00:00
|
|
|
parser.add_argument("--releasever", default="22",
|
2014-05-09 16:28:01 +00:00
|
|
|
help="substituted for @VERSION@ in bootloader config files")
|
|
|
|
parser.add_argument("--volid", default=None, help="volume id")
|
|
|
|
parser.add_argument("--squashfs_args",
|
|
|
|
help="additional squashfs args")
|
2015-02-02 20:24:06 +00:00
|
|
|
parser.add_argument("--timeout", default=None, type=int,
|
|
|
|
help="Cancel installer after X minutes")
|
2011-09-24 00:36:09 +00:00
|
|
|
|
|
|
|
opts = parser.parse_args()
|
|
|
|
|
2015-05-27 17:36:40 +00:00
|
|
|
setup_logging(opts.logfile, log)
|
2011-09-24 00:36:09 +00:00
|
|
|
|
|
|
|
log.debug( opts )
|
|
|
|
|
2014-04-03 17:22:54 +00:00
|
|
|
# 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])
|
2011-09-24 00:36:09 +00:00
|
|
|
|
2014-05-09 16:28:01 +00:00
|
|
|
if opts.make_iso and not os.path.exists( opts.lorax_templates):
|
2014-04-03 17:22:54 +00:00
|
|
|
errors.append("The lorax templates directory (%s) doesn't "
|
|
|
|
"exist." % opts.lorax_templates)
|
2011-09-24 00:36:09 +00:00
|
|
|
|
|
|
|
if opts.result_dir and os.path.exists(opts.result_dir):
|
2014-04-03 17:22:54 +00:00
|
|
|
errors.append("The results_dir (%s) should not exist, please delete or "
|
|
|
|
"move its contents" % opts.result_dir)
|
2011-09-24 00:36:09 +00:00
|
|
|
|
2014-05-09 16:28:01 +00:00
|
|
|
if opts.iso and not os.path.exists(opts.iso):
|
2014-04-03 17:22:54 +00:00
|
|
|
errors.append("The iso %s is missing." % opts.iso)
|
2011-09-24 00:36:09 +00:00
|
|
|
|
2014-05-09 16:28:01 +00:00
|
|
|
if opts.disk_image and not os.path.exists(opts.disk_image):
|
2014-04-03 17:22:54 +00:00
|
|
|
errors.append("The disk image %s is missing." % opts.disk_image)
|
2011-09-24 00:36:09 +00:00
|
|
|
|
2014-05-09 16:28:01 +00:00
|
|
|
if opts.fs_image and not os.path.exists(opts.fs_image):
|
2014-04-03 17:22:54 +00:00
|
|
|
errors.append("The filesystem image %s is missing." % opts.fs_image)
|
2013-01-30 22:16:25 +00:00
|
|
|
|
|
|
|
is_install = not (opts.disk_image or opts.fs_image)
|
|
|
|
if is_install and not opts.no_virt and not opts.iso:
|
2014-04-03 17:22:54 +00:00
|
|
|
errors.append("virt install needs an install iso.")
|
2011-09-24 00:36:09 +00:00
|
|
|
|
2012-03-08 01:29:31 +00:00
|
|
|
if opts.volid and len(opts.volid) > 32:
|
2014-04-03 17:22:54 +00:00
|
|
|
errors.append("the volume id cannot be longer than 32 characters")
|
2012-03-08 01:29:31 +00:00
|
|
|
|
2013-01-30 22:16:25 +00:00
|
|
|
if is_install and not opts.no_virt and not libvirt:
|
2015-05-08 15:39:06 +00:00
|
|
|
errors.append("virt install requires libvirt-python3 to be installed.")
|
2012-07-21 00:44:30 +00:00
|
|
|
|
2013-01-30 22:16:25 +00:00
|
|
|
if is_install and not opts.no_virt \
|
|
|
|
and not os.path.exists("/usr/bin/virt-install"):
|
2014-04-03 17:22:54 +00:00
|
|
|
errors.append("virt-install needs to be installed.")
|
2012-05-10 21:21:42 +00:00
|
|
|
|
2013-05-24 19:20:56 +00:00
|
|
|
if is_install and opts.no_virt \
|
2013-01-30 22:16:25 +00:00
|
|
|
and not os.path.exists("/usr/sbin/anaconda"):
|
2014-04-03 17:22:54 +00:00
|
|
|
errors.append("no-virt requires anaconda to be installed.")
|
2012-05-24 22:55:46 +00:00
|
|
|
|
|
|
|
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):
|
2014-04-03 17:22:54 +00:00
|
|
|
errors.append("The appliance template (%s) doesn't "
|
|
|
|
"exist" % opts.app_template)
|
2012-05-24 22:55:46 +00:00
|
|
|
|
2015-03-20 00:22:16 +00:00
|
|
|
if opts.image_name and os.path.exists(joinpaths(opts.result_dir, opts.image_name)):
|
2014-04-03 17:22:54 +00:00
|
|
|
errors.append("The disk image to be created should not exist.")
|
2012-05-24 22:55:46 +00:00
|
|
|
|
2015-10-19 22:30:33 +00:00
|
|
|
# Vagrant creates a qcow2 inside a tar, turn on qcow2
|
|
|
|
if opts.make_vagrant:
|
|
|
|
opts.qcow2 = True
|
|
|
|
|
2014-03-08 02:43:14 +00:00
|
|
|
if opts.qcow2 and not os.path.exists("/usr/bin/qemu-img"):
|
2014-04-03 17:22:54 +00:00
|
|
|
errors.append("qcow2 requires the qemu-img utility to be installed.")
|
2014-03-08 02:43:14 +00:00
|
|
|
|
|
|
|
if opts.qcow2 and opts.make_iso:
|
2014-04-03 17:22:54 +00:00
|
|
|
errors.append("qcow2 cannot be used to make a bootable iso.")
|
|
|
|
|
|
|
|
if opts.make_fsimage and opts.qcow2:
|
|
|
|
errors.append("qcow2 cannot be used to make filesystem images.")
|
|
|
|
|
2014-04-04 21:38:51 +00:00
|
|
|
if opts.make_tar and opts.qcow2:
|
|
|
|
errors.append("qcow2 cannot be used to make a tar.")
|
|
|
|
|
2015-10-19 22:35:50 +00:00
|
|
|
if opts.make_oci and not (opts.oci_config and opts.oci_runtime):
|
|
|
|
errors.append("--make-oci requires --oci-config and --oci-runtime")
|
|
|
|
|
2015-10-20 21:23:33 +00:00
|
|
|
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)
|
|
|
|
|
2015-10-21 00:28:10 +00:00
|
|
|
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)
|
|
|
|
|
2015-11-04 02:03:09 +00:00
|
|
|
if opts.virt_uefi and not os.path.isdir(opts.ovmf_path):
|
|
|
|
errors.append("The OVMF firmware is missing from %s" % opts.ovmf_path)
|
|
|
|
|
2014-04-03 17:22:54 +00:00
|
|
|
if os.getuid() != 0:
|
|
|
|
errors.append("You need to run this as root")
|
2014-03-08 02:43:14 +00:00
|
|
|
|
2014-04-03 17:22:54 +00:00
|
|
|
if errors:
|
2015-05-08 15:39:06 +00:00
|
|
|
list(log.error(e) for e in errors)
|
2014-04-03 17:22:54 +00:00
|
|
|
sys.exit(1)
|
2014-04-02 23:56:28 +00:00
|
|
|
|
2015-10-19 22:30:33 +00:00
|
|
|
# Default to putting results under tmp
|
2015-05-08 15:39:06 +00:00
|
|
|
if not opts.result_dir:
|
|
|
|
opts.result_dir = opts.tmp
|
|
|
|
else:
|
|
|
|
os.makedirs(opts.result_dir)
|
|
|
|
|
2014-04-02 23:56:28 +00:00
|
|
|
# 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"
|
2014-04-04 21:38:51 +00:00
|
|
|
elif opts.make_tar:
|
|
|
|
if not opts.image_name:
|
|
|
|
opts.image_name = "root.tar.xz"
|
|
|
|
if opts.compression == "xz" and not opts.compress_args:
|
|
|
|
opts.compress_args = ["-9"]
|
2015-10-19 22:35:50 +00:00
|
|
|
elif opts.make_oci:
|
|
|
|
if not opts.image_name:
|
|
|
|
opts.image_name = "bundle.tar.xz"
|
|
|
|
if opts.compression == "xz" and not opts.compress_args:
|
|
|
|
opts.compress_args = ["-9"]
|
2015-10-19 22:30:33 +00:00
|
|
|
elif opts.make_vagrant:
|
|
|
|
if not opts.image_name:
|
|
|
|
opts.image_name = "vagrant.tar.xz"
|
|
|
|
if opts.compression == "xz" and not opts.compress_args:
|
|
|
|
opts.compress_args = ["-9"]
|
2014-04-02 23:56:28 +00:00
|
|
|
|
2012-05-24 22:55:46 +00:00
|
|
|
if opts.app_file:
|
2015-03-20 00:22:16 +00:00
|
|
|
opts.app_file = joinpaths(opts.result_dir, opts.app_file)
|
2012-05-24 22:55:46 +00:00
|
|
|
|
2014-07-30 15:59:27 +00:00
|
|
|
if opts.make_ostree_live:
|
|
|
|
opts.make_pxe_live = True
|
|
|
|
opts.ostree = True
|
|
|
|
else:
|
|
|
|
opts.ostree = False
|
|
|
|
|
2012-05-11 20:12:17 +00:00
|
|
|
tempfile.tempdir = opts.tmp
|
2012-05-24 22:55:46 +00:00
|
|
|
disk_img = None
|
2012-05-11 20:12:17 +00:00
|
|
|
|
2012-05-24 22:55:46 +00:00
|
|
|
# Parse the kickstart
|
|
|
|
if opts.ks:
|
2011-09-24 00:36:09 +00:00
|
|
|
ks_version = makeVersion()
|
2014-05-09 16:28:01 +00:00
|
|
|
ks = KickstartParser(ks_version, errorsAreFatal=False, missingIncludeIsFatal=False)
|
|
|
|
ks.readKickstart(opts.ks[0])
|
2012-05-24 22:55:46 +00:00
|
|
|
|
2013-01-30 22:16:25 +00:00
|
|
|
# Make the disk or filesystem image
|
|
|
|
if not opts.disk_image and not opts.fs_image:
|
|
|
|
errors = []
|
2014-10-10 00:27:39 +00:00
|
|
|
if opts.no_virt and ks.handler.method.method not in ("url", "nfs"):
|
|
|
|
errors.append("Only url and nfs install methods are currently supported. Please "
|
2013-01-30 22:16:25 +00:00
|
|
|
"fix your kickstart file." )
|
2011-09-24 00:36:09 +00:00
|
|
|
|
2013-01-30 22:16:25 +00:00
|
|
|
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.")
|
2011-09-24 00:36:09 +00:00
|
|
|
|
2014-04-03 17:45:06 +00:00
|
|
|
if opts.make_fsimage:
|
|
|
|
# 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.")
|
2014-04-02 23:56:28 +00:00
|
|
|
|
2015-11-16 22:07:32 +00:00
|
|
|
if not opts.no_virt and ks.handler.reboot.action != KS_SHUTDOWN:
|
|
|
|
errors.append("The kickstart must include shutdown when using virt installation.")
|
|
|
|
|
2013-01-30 22:16:25 +00:00
|
|
|
if errors:
|
2015-05-08 15:39:06 +00:00
|
|
|
list(log.error(e) for e in errors)
|
2013-01-30 22:16:25 +00:00
|
|
|
sys.exit(1)
|
2011-12-09 01:12:06 +00:00
|
|
|
|
2013-01-30 22:16:25 +00:00
|
|
|
# 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:
|
2014-05-09 00:22:14 +00:00
|
|
|
log.error("ERROR: Image creation failed: %s", e)
|
2013-01-30 22:16:25 +00:00
|
|
|
sys.exit(1)
|
2012-05-12 00:35:16 +00:00
|
|
|
|
2013-01-30 22:16:25 +00:00
|
|
|
if not opts.image_only:
|
|
|
|
result_dir = None
|
|
|
|
if opts.make_iso:
|
|
|
|
work_dir = tempfile.mkdtemp()
|
2015-05-01 00:10:08 +00:00
|
|
|
log.info("working dir is %s", work_dir)
|
2013-01-30 22:16:25 +00:00
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
# 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,
|
2015-11-13 21:46:08 +00:00
|
|
|
opts.vcpus or 1, opts.arch, opts.title, opts.project, opts.releasever)
|
2014-07-30 15:59:27 +00:00
|
|
|
elif opts.make_pxe_live:
|
|
|
|
work_dir = tempfile.mkdtemp()
|
2015-05-01 00:10:08 +00:00
|
|
|
log.info("working dir is %s", work_dir)
|
2014-07-30 15:59:27 +00:00
|
|
|
|
2015-10-28 21:44:24 +00:00
|
|
|
if opts.fs_image and not opts.disk_image:
|
2014-07-30 15:59:27 +00:00
|
|
|
# Create pxe live images from a filesystem image
|
|
|
|
disk_img = opts.fs_image or disk_img
|
|
|
|
with Mount(disk_img, opts="loop") as mnt_dir:
|
|
|
|
result_dir = make_live_images(opts, work_dir, mnt_dir, rootfs_image=disk_img)
|
|
|
|
else:
|
|
|
|
# Create pxe live images from a partitioned disk image
|
|
|
|
disk_img = opts.disk_image or disk_img
|
|
|
|
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:
|
|
|
|
mounted_sysroot_boot_dir = mount_boot_part_over_root(img_mount)
|
2015-01-15 10:26:44 +00:00
|
|
|
if opts.live_rootfs_keep_size:
|
|
|
|
size = img_mount.mount_size / 1024**3
|
|
|
|
else:
|
|
|
|
size = opts.live_rootfs_size or None
|
2015-01-15 10:22:30 +00:00
|
|
|
result_dir = make_live_images(opts, work_dir, img_mount.mount_dir, size=size)
|
2014-07-30 15:59:27 +00:00
|
|
|
finally:
|
|
|
|
if mounted_sysroot_boot_dir:
|
|
|
|
umount(mounted_sysroot_boot_dir)
|
2013-01-30 22:16:25 +00:00
|
|
|
|
2015-03-20 00:22:16 +00:00
|
|
|
if opts.result_dir != opts.tmp and result_dir:
|
2015-05-08 15:39:06 +00:00
|
|
|
copytree(result_dir, opts.result_dir)
|
2014-05-09 16:28:01 +00:00
|
|
|
shutil.rmtree(result_dir)
|
2011-09-24 00:36:09 +00:00
|
|
|
|
|
|
|
log.info("SUMMARY")
|
|
|
|
log.info("-------")
|
2015-05-01 00:10:08 +00:00
|
|
|
log.info("Logs are in %s", os.path.abspath(os.path.dirname(opts.logfile)))
|
2012-05-24 22:55:46 +00:00
|
|
|
if disk_img:
|
2015-05-01 00:10:08 +00:00
|
|
|
log.info("Disk image is at %s", disk_img)
|
2012-05-24 22:55:46 +00:00
|
|
|
if opts.make_appliance:
|
2015-05-01 00:10:08 +00:00
|
|
|
log.info("Appliance description is in %s", opts.app_file)
|
|
|
|
log.info("Results are in %s", opts.result_dir)
|
2011-09-24 00:36:09 +00:00
|
|
|
|
|
|
|
sys.exit( 0 )
|
|
|
|
|
2014-05-09 00:22:14 +00:00
|
|
|
if __name__ == '__main__':
|
|
|
|
main()
|