livemedia-creator: Add support for making tarfiles (#1144140)

This adds the --make-tar option which will produce a xz compressed tar
of the root filesystem. This works with either virt-install or no-virt
modes. Use --image-name to set the output filename.

--compression is used to set the compression type to use, which defaults
to xz. Supported types are xz, lzma, gzip and bzip2.

--compress-arg is used to pass arguments to the compression utility.

(cherry picked from commit d04a99e8f4)

Resolves: rhbz#1144140
This commit is contained in:
Brian C. Lane 2014-04-04 14:38:51 -07:00
parent 8f2283cf1c
commit 526988651d
4 changed files with 126 additions and 20 deletions

View File

@ -224,6 +224,21 @@ The created image can be imported into libvirt using:
virt-image appliance.xml virt-image appliance.xml
TAR FILE CREATION
-----------------
The --make-tar command can be used to create a tar of the root filesystem. By
default it is compressed using xz, but this can be changed using the
--compression and --compress-arg options. This option works with both virt and
--no-virt install methods.
As with --make-fsimage the kickstart should be limited to a single / partition.
eg.
livemedia-creator --make-tar --iso=/path/to/boot.iso --ks=./docs/fedora-minimal.ks \
--image-name=fedora-root.tar.xz
DEBUGGING PROBLEMS DEBUGGING PROBLEMS
------------------ ------------------
Cleaning up an aborted (ctrl-c) virt-install run (as root): Cleaning up an aborted (ctrl-c) virt-install run (as root):

View File

@ -4,10 +4,12 @@ livemedia-creator \- Create live install media
.SH SYNOPSIS .SH SYNOPSIS
livemedia-creator [-h] livemedia-creator [-h]
(--make-iso | --make-disk | --make-fsimage | --make-appliance | --make-ami) (--make-iso | --make-disk | --make-fsimage | --make-appliance | --make-ami | --make-tar)
[--iso ISO] [--disk-image DISK_IMAGE] [--iso ISO] [--disk-image DISK_IMAGE]
[--fs-image FS_IMAGE] [--ks KS] [--fs-image FS_IMAGE] [--ks KS]
[--image-name IMAGE_NAME] [--image-only] [--image-name IMAGE_NAME] [--image-only]
[--fs-label FS_LABEL]
[--compression] [--compress-arg]
[--keep-image] [--no-virt] [--proxy PROXY] [--keep-image] [--no-virt] [--proxy PROXY]
[--anaconda-arg ANACONDA_ARGS] [--anaconda-arg ANACONDA_ARGS]
[--armplatform ARMPLATFORM] [--location LOCATION] [--armplatform ARMPLATFORM] [--location LOCATION]
@ -63,6 +65,10 @@ Build an appliance image and XML description
\fB\-\-make\-ami\fR \fB\-\-make\-ami\fR
Build an ami image Build an ami image
.TP
\fB\-\-make\-tar\fR
Build a tar of the root filesystem. Defaults to root.tar.xz
.TP .TP
\fB\-\-iso ISO\fR \fB\-\-iso ISO\fR
Anaconda installation .iso path to use for virt-install Anaconda installation .iso path to use for virt-install
@ -73,7 +79,24 @@ Path to disk image to use for creating final image
.TP .TP
\fB\-\-fs\-image FS_IMAGE\fR \fB\-\-fs\-image FS_IMAGE\fR
Path to filesystem image to use for creating final image Path to existing filesystem image to use for creating final image.
.TP
\fB\-\-qcow2\fR
Create qcow2 image instead of raw sparse image when making disk images.
.TP
\fB\-\-qcow2\-arg\fR
Arguments to pass to qemu-img. Pass once for each argument
>>>>>>> d04a99e... livemedia-creator: Add support for making tarfiles
.TP
\fB\-\-compress\fR
Compression binary for make-tar. xz, lzma, gzip, and bzip2 are supported. xz is the default.
.TP
\fB\-\-compress\-arg\fR
Arguments to pass to compression. Pass once for each argument
.TP .TP
\fB\-\-ks KS\fR \fB\-\-ks KS\fR

View File

@ -22,37 +22,64 @@ logger = logging.getLogger("pylorax.imgutils")
import os, tempfile import os, tempfile
from os.path import join, dirname from os.path import join, dirname
from subprocess import CalledProcessError from subprocess import Popen, PIPE, CalledProcessError
import sys import sys
import traceback import traceback
import multiprocessing
from time import sleep from time import sleep
from pylorax.sysutils import cpfile from pylorax.sysutils import cpfile
from pylorax.executils import execWithRedirect, execWithCapture from pylorax.executils import execWithRedirect, execWithCapture
from pylorax.executils import runcmd, runcmd_output from pylorax.executils import runcmd, runcmd_output
######## Functions for making container images (cpio, squashfs) ########## ######## Functions for making container images (cpio, tar, squashfs) ##########
def mkcpio(rootdir, outfile, compression="xz", compressargs=["-9"]): def compress(command, rootdir, outfile, compression="xz", compressargs=["-9"]):
'''Make a compressed CPIO archive of the given rootdir. '''Make a compressed archive of the given rootdir.
compression should be "xz", "gzip", "lzma", or None. command is a list of the archiver commands to run
compression should be "xz", "gzip", "lzma", "bzip2", or None.
compressargs will be used on the compression commandline.''' compressargs will be used on the compression commandline.'''
if compression not in (None, "xz", "gzip", "lzma"): if compression not in (None, "xz", "gzip", "lzma", "bzip2"):
raise ValueError, "Unknown compression type %s" % compression raise ValueError, "Unknown compression type %s" % compression
if compression == "xz": if compression == "xz":
compressargs.insert(0, "--check=crc32") compressargs.insert(0, "--check=crc32")
if compression is None: if compression is None:
compression = "cat" # this is a little silly compression = "cat" # this is a little silly
compressargs = [] compressargs = []
logger.debug("mkcpio %s | %s %s > %s", rootdir, compression,
" ".join(compressargs), outfile) # make compression run with multiple threads if possible
if compression in ("xz", "lzma"):
compressargs.insert(0, "-T%d" % multiprocessing.cpu_count())
elif compression == "gzip":
compression = "pigz"
compressargs.insert(0, "-p%d" % multiprocessing.cpu_count())
elif compression == "bzip2":
compression = "pbzip2"
compressargs.insert(0, "-p%d" % multiprocessing.cpu_count())
logger.debug("find %s -print0 |%s | %s %s > %s", rootdir, " ".join(command),
compression, " ".join(compressargs), outfile)
find, archive, comp = None, None, None
try:
find = Popen(["find", ".", "-print0"], stdout=PIPE, cwd=rootdir) find = Popen(["find", ".", "-print0"], stdout=PIPE, cwd=rootdir)
cpio = Popen(["cpio", "--null", "--quiet", "-H", "newc", "-o"], archive = Popen(command, stdin=find.stdout, stdout=PIPE, cwd=rootdir)
stdin=find.stdout, stdout=PIPE, cwd=rootdir)
comp = Popen([compression] + compressargs, comp = Popen([compression] + compressargs,
stdin=cpio.stdout, stdout=open(outfile, "wb")) stdin=archive.stdout, stdout=open(outfile, "wb"))
comp.wait() comp.wait()
return comp.returncode return comp.returncode
except OSError as e:
logger.error(e)
# Kill off any hanging processes
[p.kill() for p in (find, archive, comp) if p]
return 1
def mkcpio(rootdir, outfile, compression="xz", compressargs=["-9"]):
return compress(["cpio", "--null", "--quiet", "-H", "newc", "-o"],
rootdir, outfile, compression, compressargs)
def mktar(rootdir, outfile, compression="xz", compressargs=["-9"]):
return compress(["tar", "--selinux", "--acls", "--xattrs", "-cf-", "--null", "-T-"],
rootdir, outfile, compression, compressargs)
def mksquashfs(rootdir, outfile, compression="default", compressargs=[]): def mksquashfs(rootdir, outfile, compression="default", compressargs=[]):
'''Make a squashfs image containing the given rootdir.''' '''Make a squashfs image containing the given rootdir.'''

View File

@ -2,7 +2,7 @@
# #
# Live Media Creator # Live Media Creator
# #
# Copyright (C) 2011-2013 Red Hat, Inc. # Copyright (C) 2011-2014 Red Hat, Inc.
# #
# This program is free software; you can redistribute it and/or modify # This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by
@ -53,7 +53,7 @@ from pylorax.treebuilder import TreeBuilder, RuntimeBuilder, udev_escape
from pylorax.sysutils import joinpaths, remove from pylorax.sysutils import joinpaths, remove
from pylorax.imgutils import PartitionMount, mksparse, mkext4img, loop_detach from pylorax.imgutils import PartitionMount, mksparse, mkext4img, loop_detach
from pylorax.imgutils import get_loop_name, dm_detach, mount, umount, Mount from pylorax.imgutils import get_loop_name, dm_detach, mount, umount, Mount
from pylorax.imgutils import mksquashfs from pylorax.imgutils import mksquashfs, mktar
from pylorax.executils import execWithRedirect, execWithCapture from pylorax.executils import execWithRedirect, execWithCapture
# no-virt mode doesn't need libvirt, so make it optional # no-virt mode doesn't need libvirt, so make it optional
@ -578,6 +578,13 @@ def novirt_install(opts, disk_img, disk_size, repo_url):
if not os.path.isdir(ROOT_PATH): if not os.path.isdir(ROOT_PATH):
os.mkdir(ROOT_PATH) os.mkdir(ROOT_PATH)
mount(disk_img, opts="loop", mnt=ROOT_PATH) mount(disk_img, opts="loop", mnt=ROOT_PATH)
elif opts.make_tar:
args += ["--dirinstall"]
# Install directly into ROOT_PATH, make sure it starts clean
if os.path.exists(ROOT_PATH):
shutil.rmtree(ROOT_PATH)
os.mkdir(ROOT_PATH)
else: else:
args += ["--image", disk_img] args += ["--image", disk_img]
@ -617,6 +624,17 @@ def novirt_install(opts, disk_img, disk_size, repo_url):
if rc: if rc:
raise InstallError("novirt_install failed") raise InstallError("novirt_install failed")
if opts.make_tar:
compress_args = []
for arg in opts.compress_args:
compress_args += arg.split(" ", 1)
rc = mktar(ROOT_PATH, disk_img, opts.compression, compress_args)
shutil.rmtree(ROOT_PATH)
if rc:
raise InstallError("novirt_install failed")
def virt_install(opts, install_log, disk_img, disk_size): def virt_install(opts, install_log, disk_img, disk_size):
""" """
@ -635,7 +653,7 @@ def virt_install(opts, install_log, disk_img, disk_size):
if opts.proxy: if opts.proxy:
kernel_args += " proxy="+opts.proxy kernel_args += " proxy="+opts.proxy
if opts.make_fsimage: if opts.make_fsimage or opts.make_tar:
diskimg_path = tempfile.mktemp(prefix="disk", suffix=".img", dir=opts.tmp) diskimg_path = tempfile.mktemp(prefix="disk", suffix=".img", dir=opts.tmp)
else: else:
diskimg_path = disk_img diskimg_path = disk_img
@ -655,6 +673,18 @@ def virt_install(opts, install_log, disk_img, disk_size):
if opts.make_fsimage: if opts.make_fsimage:
make_fsimage(diskimg_path, disk_img, label=opts.fs_label) make_fsimage(diskimg_path, disk_img, label=opts.fs_label)
os.unlink(diskimg_path) os.unlink(diskimg_path)
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)
os.unlink(diskimg_path)
if rc:
raise InstallError("virt_install failed")
def make_squashfs(disk_img, work_dir, compression="xz"): def make_squashfs(disk_img, work_dir, compression="xz"):
@ -754,6 +784,8 @@ if __name__ == '__main__':
help="Build an appliance image and XML description" ) help="Build an appliance image and XML description" )
action.add_argument( "--make-ami", action="store_true", action.add_argument( "--make-ami", action="store_true",
help="Build an ami image" ) help="Build an ami image" )
action.add_argument( "--make-tar", action="store_true",
help="Build a tar of the root filesystem" )
parser.add_argument( "--iso", type=os.path.abspath, parser.add_argument( "--iso", type=os.path.abspath,
help="Anaconda installation .iso path to use for virt-install" ) help="Anaconda installation .iso path to use for virt-install" )
@ -763,6 +795,10 @@ if __name__ == '__main__':
help="Path to existing filesystem image to use for creating final image." ) help="Path to existing filesystem image to use for creating final image." )
parser.add_argument( "--fs-label", default="Anaconda", parser.add_argument( "--fs-label", default="Anaconda",
help="Label to set on fsimage, default is 'Anaconda'") help="Label to set on fsimage, default is 'Anaconda'")
parser.add_argument("--compression", default="xz",
help="Compression binary for make-tar. xz, lzma, gzip, and bzip2 are supported. xz is the default.")
parser.add_argument("--compress-arg", action="append", dest="compress_args", default=[],
help="Arguments to pass to compression. Pass once for each argument")
parser.add_argument( "--ks", action="append", type=os.path.abspath, parser.add_argument( "--ks", action="append", type=os.path.abspath,
help="Kickstart file defining the install." ) help="Kickstart file defining the install." )
@ -923,6 +959,11 @@ if __name__ == '__main__':
opts.image_name = "ami-root.img" opts.image_name = "ami-root.img"
if opts.fs_label == "Anaconda": if opts.fs_label == "Anaconda":
opts.fs_label = "AMI" opts.fs_label = "AMI"
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"]
if opts.app_file: if opts.app_file:
opts.app_file = joinpaths(opts.tmp, opts.app_file) opts.app_file = joinpaths(opts.tmp, opts.app_file)