Generate live squashfs and initrd for pxe live install.
Also generate pxe config template.
To be used with virt installation with /boot on separate partition (the only
way supported by Anaconda currently). Content of boot partition is added to
live root fs so that ostree can find deployment by boot configuration.
--ostree flag ensures using deployment root instead of physical root of
installed disk image where needed.
---
docs/rhel-atomic-pxe-live.ks | 39 +++++++
src/sbin/livemedia-creator | 238 ++++++++++++++++++++++++++++++++++++++++++-
2 files changed, 274 insertions(+), 3 deletions(-)
create mode 100644 docs/rhel-atomic-pxe-live.ks
diff --git a/docs/rhel-atomic-pxe-live.ks b/docs/rhel-atomic-pxe-live.ks
new file mode 100644
index 0000000..324dad2
--- /dev/null
+++ b/docs/rhel-atomic-pxe-live.ks
@@ -0,0 +1,39 @@
+# Kickstart to be used to genrate pxe to live atomic image using --pxe-to-live and --ostree options
+lang en_US.UTF-8
+keyboard us
+timezone America/New_York
+clearpart --all --initlabel
+network --bootproto=dhcp --device=link --activate
+rootpw --plaintext atomic
+
+# We are only able to install atomic with separate /boot partition currently
+part / --fstype="ext4" --size=6000
+part /boot --size=500 --fstype="ext4"
+
+shutdown
+
+services --disabled=cloud-init,cloud-init-local,cloud-final,cloud-config,docker-storage-setup
+
+# Regenerating initramfs requires dracut-network package in repo, relabeling rootfs requires policycoreutils
+#ostreesetup --osname=rhel-atomic-host --remote=rhel-atomic-host --url=http://10.34.102.90:8000/repo --ref=rhel-atomic-host/7.0-buildmaster/x86_64/standard --nogpg
+ostreesetup --osname=rhel-atomic-host --remote=rhel-atomic-host --url=http://10.34.102.90:8000/repo --ref=rhel-atomic-host/7.0-buildmaster/x86_64/live-selinux --nogpg
+
+# We throw away separate /boot partition when building live squashfs image,
+# and we don't want systemd to try to mount it when pxe booting
+%post
+cat /dev/null > /etc/fstab
+%end
+
+# This comes from interactive-defaults.ks of installer.iso with included ostree repo
+%post --erroronfail
+rm -f /etc/ostree/remotes.d/rhel-atomic-host.conf
+%end
+
+# This comes from interactive-defaults.ks of installer.iso with included ostree repo
+%post --erroronfail
+rm -f /etc/ostree/remotes.d/*.conf
+echo 'unconfigured-state=This system is not registered to Red Hat Subscription Management. You can use subscription-manager to register.' >> $(ostree admin --print-current-dir).origin
+%end
+
+
+
diff --git a/src/sbin/livemedia-creator b/src/sbin/livemedia-creator
index e25d422..e25291c 100755
--- a/src/sbin/livemedia-creator
+++ b/src/sbin/livemedia-creator
@@ -37,6 +37,7 @@ import shutil
import argparse
import hashlib
import re
+import glob
# Use pykickstart to calculate disk image size
from pykickstart.parser import KickstartParser
@@ -52,9 +53,9 @@ from pylorax.treebuilder import TreeBuilder, RuntimeBuilder, udev_escape
from pylorax.treebuilder import findkernels
from pylorax.sysutils import joinpaths, remove
from pylorax.imgutils import PartitionMount, mksparse, mkext4img, loop_detach
-from pylorax.imgutils import get_loop_name, dm_detach, mount, umount, Mount
+from pylorax.imgutils import get_loop_name, dm_detach, mount, umount, Mount, LoopDev
from pylorax.imgutils import mksquashfs, mkqcow2, mktar
-from pylorax.executils import execWithRedirect, execWithCapture
+from pylorax.executils import execWithRedirect, execWithCapture, runcmd
# no-virt mode doesn't need libvirt, so make it optional
try:
@@ -414,6 +415,21 @@ def is_image_mounted(disk_img):
return True
return False
+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
+ """
+ ostree_root = ""
+ ostree_sysroots = glob.glob(joinpaths(phys_root, "ostree/boot.0/*/*/0"))
+ if ostree_sysroots:
+ if len(ostree_sysroots) > 1:
+ log.error("Too many deployment roots found: %s" % ostree_sysroots)
+ ostree_root = os.path.relpath(ostree_sysroots[0], phys_root)
+ return ostree_root
def get_arch(mount_dir):
"""
@@ -499,7 +515,6 @@ def make_fsimage(diskimage, fsimage, img_size=None, label="Anaconda"):
mkext4img(img_mount.mount_dir, fsimage, size=img_size, label=label)
-
def make_runtime(opts, mount_dir, work_dir):
"""
Make the squashfs image from a directory
@@ -527,6 +542,118 @@ def make_runtime(opts, mount_dir, work_dir):
log.info("Creating runtime")
rb.create_runtime(joinpaths(work_dir, RUNTIME), size=None)
+def make_live_rootfs(mount_dir, image_path, size=2, sys_root=""):
+ """
+ Make rootfs image from a directory
+
+ :param str mount_dir: Root directory
+ :param str image_path: Path of output image file
+ :param int size: Size of the image, if None computed automatically
+ :param str sys_root: path to system (deployment) root relative to physical root
+ """
+ if size:
+ fssize = size * (1024*1024*1024) # 2GB sparse file compresses down to nothin'
+ else:
+ fssize = None # Let mkext4img figure out the needed size
+
+ mkext4img(mount_dir, image_path, label="LiveOS", size=fssize)
+ # Reset selinux context on new rootfs
+ with LoopDev(image_path) as loopdev:
+ with Mount(loopdev) as mnt:
+ cmd = [ "setfiles", "-e", "/proc", "-e", "/sys", "-e", "/dev", "-e", "/install",
+ "/etc/selinux/targeted/contexts/files/file_contexts", "/"]
+ root = joinpaths(mnt, sys_root.lstrip("/"))
+ runcmd(cmd, root=root)
+
+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)
+ log.info("dracut args = {0}".format(dracut_args))
+
+ 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)
+ shutil.copy2(joinpaths(sys_root_dir, kernel.path), results_dir)
+
+ os.unlink(joinpaths(sys_root_dir,"/proc/modules"))
+
+def create_pxe_config(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)
+
+ buf = """# PXE configuration template generated by livemedia-creator
+kernel <PXE_DIR>%s
+append initrd=<PXE_DIR>%s root=live:<URL>/%s %s
+""" % (kernel.path, kernel.initrd.path, live_image_name, add_args_str)
+ with open (joinpaths(images_dir, "PXE_CONFIG"), "w") as f:
+ f.write(buf)
def make_livecd(opts, mount_dir, work_dir):
"""
@@ -588,6 +715,34 @@ def make_livecd(opts, mount_dir, work_dir):
return work_dir
+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)
+ sys_root_bootdir = None
+ for dev, size in img_mount.loop_devices:
+ 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
def novirt_install(opts, disk_img, disk_size, repo_url):
"""
@@ -840,6 +995,52 @@ def make_image(opts, ks):
log.info("Disk Image install successful")
return disk_img
+def make_live_images(opts, work_dir, root_dir, rootfs_image=None):
+ """
+ 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")
+ make_live_rootfs(root_dir, joinpaths(liveos_dir, "rootfs.img"), size=None, sys_root=sys_root)
+
+ 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)
+ create_pxe_config(work_dir, live_image_name, add_pxe_args)
+
+ return work_dir
def setup_logging(opts):
"""
@@ -892,6 +1093,8 @@ def main():
help="Build an ami image")
action.add_argument("--make-tar", action="store_true",
help="Build a tar of the root filesystem")
+ action.add_argument("--make-pxe-live", action="store_true",
+ help="Build live in squashfs and initrd for pxe boot")
parser.add_argument("--iso", type=os.path.abspath,
help="Anaconda installation .iso path to use for virt-install")
@@ -914,6 +1117,9 @@ def main():
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("--ostree", action="store_true",
+ help="Ostree (Atomic) installation")
+
parser.add_argument("--logfile", default="./livemedia.log",
type=os.path.abspath,
@@ -1165,6 +1371,32 @@ def main():
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)
+ elif opts.make_pxe_live:
+
+ work_dir = tempfile.mkdtemp()
+ log.info("working dir is {0}".format(work_dir))
+
+ if (opts.fs_image or opts.no_virt) and not opts.disk_image:
+ # 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:
+
+ mounted_sysroot_boot_dir = None
+ if opts.ostree:
+ mounted_sysroot_boot_dir = mount_boot_part_over_root(img_mount)
+
+ result_dir = make_live_images(opts, work_dir, img_mount.mount_dir)
+ if mounted_sysroot_boot_dir:
+ umount(mounted_sysroot_boot_dir)
if opts.result_dir and result_dir:
shutil.copytree(result_dir, opts.result_dir)
--
1.9.3