From: "Brian C. Lane" <bcl@xxxxxxxxxx> This adds support for creating an appliance description file for the disk image. Mako templates are used to make it easy to support other appliance targets. The included example works with virt-image. --- README.livemedia-creator | 68 +++++++++++++-------- lorax.spec | 1 + share/appliance/libvirt.tmpl | 35 +++++++++++ src/sbin/livemedia-creator | 134 +++++++++++++++++++++++++++++++++++++----- 4 files changed, 196 insertions(+), 42 deletions(-) create mode 100644 share/appliance/libvirt.tmpl diff --git a/README.livemedia-creator b/README.livemedia-creator index 630f639..76ae1e0 100644 --- a/README.livemedia-creator +++ b/README.livemedia-creator @@ -24,7 +24,7 @@ If you are using the lorax git repo you can run it like so: sudo PATH=./src/sbin/:$PATH PYTHONPATH=./src/ ./src/sbin/livemedia-creator \ --make-iso --iso=/extra/iso/Fedora-16-x86_64-netinst.iso \ ---ks=./docs/livemedia-example.ks --lorax-templates=./share/ +--ks=./docs/fedora-livemedia.ks --lorax-templates=./share/ If you want to watch the install you can pass '--vnc vnc' and use a vnc client to connect to localhost:0 @@ -35,8 +35,7 @@ to monitor the logs for fatal errors, but may not catch everything. HOW IT WORKS ------------ -The --make-* switches define the final output. Currently only --make-iso -and --make-disk are working. +The --make-* switches define the final output. You then need to either pass --iso and --ks in order to create a disk image using virt-install, or --disk-image to use a disk image from a previous run @@ -48,19 +47,19 @@ customize the installed system in the same way that current spin-kickstarts do. livemedia-creator monitors the install process for problems by watching the -install logs. They are written to the current directory or to the base directory -specified by the --logfile command. You can also monitor the install by passing ---vnc vnc and using a vnc client. This is recommended when first modifying a -kickstart, since there are still places where Anaconda may get stuck without -the log monitor catching it. +install logs. They are written to the current directory or to the base +directory specified by the --logfile command. You can also monitor the install +by passing --vnc vnc and using a vnc client. This is recommended when first +modifying a kickstart, since there are still places where Anaconda may get +stuck without the log monitor catching it. The output from this process is a partitioned disk image. kpartx can be used to mount and examine it when there is a problem with the install. It can also be booted using kvm. -Once the disk image is created it copies the / partition into a formatted -disk image which is then used as the input to lorax for creation of the -final media. +When creating an iso the disk image's / partition is copied into a formatted +disk image which is then used as the input to lorax for creation of the final +media. The final image is created by lorax, using the templates in /usr/share/lorax/ or the directory specified by --lorax-templates @@ -169,6 +168,38 @@ This will produce an ami-root.img file in the working directory. At this time I have not tested the image with EC2. Feedback would we welcome. +APPLIANCE CREATION ------------------ livemedia-creator can now replace +appliance-tools by using the --make-appliance switch. This will create the +partitioned disk image and an XML file that can be used with virt-image to +setup a virtual system. + +The XML is generated using the Mako template from +/usr/share/lorax/appliance/virt-image.xml You can use a different template by +passing --app-template <template path> + +Documentation on the Mako template system can be found here: +http://docs.makotemplates.org/en/latest/index.html + +The name of the final output XML is appliance.xml, this can be changed with +--app-file <file path> + +The following variables are passed to the template: +disks A list of disk_info about each disk. + Each entry has the following attributes: + name base name of the disk image file + format "raw" + checksum_type "sha256" + checksum sha256 checksum of the disk image +name Name of appliance, from --app-name argument +arch Architecture +memory Memory in KB (from --ram) +vcpus from --vcpus +networks list of networks from the kickstart or [] +title from --title +project from --project +releasever from --releasever + + DEBUGGING PROBLEMS ------------------ Cleaning up an aborted (ctrl-c) virt-install run (as root): @@ -190,21 +221,6 @@ Cleaning up aborted --no-virt installs can sometimes be accomplished by running the anaconda-cleanup script. -THE FUTURE ----------- -The current release supports creating live iso's and ami images. In the future -I also want it to be able to create appliance images. - -It is also limited to x86 architectures because of it's use of virt-install. -I hope to be able to support other arches by using Anaconda's image install -feature instead of virt-install. This will require that livemedia-creator -be running on the same release as is being created in order to avoid odd -problems. - -I would like to provide a set of alternate lorax template scripts to create -other media. - - HACKING ------- Development on this will take place as part of the lorax project, and on the diff --git a/lorax.spec b/lorax.spec index 4415ec1..d30c553 100644 --- a/lorax.spec +++ b/lorax.spec @@ -31,6 +31,7 @@ Requires: xz Requires: squashfs-tools >= 4.2 Requires: e2fsprogs Requires: yum +Requires: pykickstart %ifarch %{ix86} x86_64 Requires: syslinux >= 4.02-5 diff --git a/share/appliance/libvirt.tmpl b/share/appliance/libvirt.tmpl new file mode 100644 index 0000000..a739e63 --- /dev/null +++ b/share/appliance/libvirt.tmpl @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="UTF-8"?> +<image> + <name>${name}</name> + <domain> + <boot type='hvm'> + <guest> + <arch>${arch}</arch> + </guest> + <os> + <loader dev='hd'/> + </os> +%for disk, letter in zip(disks, xrange(97, 123)): + <drive disk='${disk.name}' target='hd${chr(letter)}'/> +%endfor + </boot> + <devices> + <vcpu>${vcpus}</vcpu> + <memory>${memory}</memory> +%for net in networks: + <interface/> +%endfor + <graphics/> + </devices> + </domain> + <storage> +%for disk in disks: + <disk file='${disk.name}' use='system' format='${disk.format}'> + %if disk.checksum: + <checksum type='${disk.checksum_type}'>${disk.checksum}</checksum> + %endif + </disk> +%endfor + </storage> +</image> + diff --git a/src/sbin/livemedia-creator b/src/sbin/livemedia-creator index 3c37cb5..5a2ecae 100755 --- a/src/sbin/livemedia-creator +++ b/src/sbin/livemedia-creator @@ -36,11 +36,16 @@ from time import sleep import shutil import traceback import argparse +import hashlib # Use pykickstart to calculate disk image size from pykickstart.parser import KickstartParser from pykickstart.version import makeVersion +# Use Mako templates for appliance builder descriptions +from mako.template import Template +from mako.exceptions import text_error_template + # Use the Lorax treebuilder branch for iso creation from pylorax.base import DataHolder from pylorax.treebuilder import TreeBuilder, RuntimeBuilder, udev_escape @@ -332,7 +337,11 @@ def is_image_mounted(disk_img): def anaconda_install( disk_img, disk_size, kickstart, repo, args ): """ - + disk_img Full path of the disk image + disk_size Disk size in GB + kickstart Full path to kickstart file + repo URL of repository + args Extra args to pass to anaconda --image install """ # Create the sparse image mksparse( disk_img, disk_size * 1024**3 ) @@ -354,6 +363,53 @@ def get_kernels( boot_dir ): return [f[8:] for f in files if f.startswith("vmlinuz-")] +def make_appliance(disk_img, name, template, outfile, networks=None, ram=1024, + vcpus=1, title="Linux", project="Linux", releasever=17): + """ + Generate an appliance description file + + disk_img Full path of the disk image + name Name of the appliance, passed to the template + template Full path of Mako template + outfile Full path of file to write, using template + networks List of networks from the kickstart + ram Ram, in MB, passed to template. Default is 1024 + vcpus CPUs, passed to template. Default is 1 + title Title, passed to template. Default is 'Linux' + project Project, passed to template. Default is 'Linux' + releasever Release version, passed to template. Default is 17 + """ + if not (disk_img and template and outfile): + return None + + log.info("Creating appliance definition using ${0}".format(template)) + + # Mount the disk and figure out the arch + arch = "x86_64" + + log.info("Calculating SHA256 checksum of {0}".format(disk_img)) + sha256 = hashlib.sha256() + with open(disk_img) as f: + while True: + data = f.read(1024*1024) + if not data: + break + sha256.update(data) + log.info("SHA256 of {0} is {1}".format(disk_img, sha256.hexdigest())) + 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, + arch=arch, memory=ram*1024, vcpus=vcpus, networks=networks, + title=title, project=project, releasever=releasever) + except Exception: + log.error(text_error_template().render()) + raise + + with open(outfile, "w") as f: + f.write(result) + + def make_ami( disk_img, ami_img="ami-root.img", ami_label="AMI" ): """ Copy the / partition to an un-partitioned disk image @@ -483,6 +539,8 @@ if __name__ == '__main__': parser.add_argument( "--ks", action="append", type=os.path.abspath, help="Kickstart file defining the install." ) + parser.add_argument( "--image-name", default=None, + help="Name of disk image to create. Default is a random name." ) parser.add_argument( "--image-only", action="store_true", help="Exit after creating disk image." ) parser.add_argument( "--keep-image", action="store_true", @@ -508,6 +566,15 @@ if __name__ == '__main__': help="Directory to copy the resulting images and iso into. " "Defaults to the temporary working directory") + # Group of arguments for appliance creation + app_group = parser.add_argument_group("appliance arguments") + 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.") + # Group of arguments to pass to virt-install if not libvirt: virt_group = parser.add_argument_group("virt-install arguments (DISABLED -- no libvirt)") @@ -590,10 +657,6 @@ if __name__ == '__main__': log.error( "The disk image {0} is missing.".format( opts.disk_image ) ) sys.exit( 1 ) - if opts.make_appliance: - log.error( "--make-appliance is not yet implemented." ) - sys.exit( 1 ) - if not opts.no_virt and not opts.iso and not opts.disk_image: log.error( "virt-install needs an install iso." ) sys.exit( 1 ) @@ -606,14 +669,41 @@ if __name__ == '__main__': log.error("virt-install requires libvirt to be installed.") sys.exit(1) + if opts.no_virt and not os.path.exists("/usr/sbin/anaconda"): + log.error("no-virt requires anaconda to be installed.") + sys.exit(1) + + 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): + log.error("The appliance template ({0}) doesn't " + "exist".format(opts.app_template)) + sys.exit(1) + + if opts.image_name and os.path.exists(joinpaths(opts.tmp,opts.image_name)): + log.error("The disk image to be created should not exist.") + sys.exit(1) + + if opts.app_file: + opts.app_file = joinpaths(opts.tmp, opts.app_file) + tempfile.tempdir = opts.tmp + disk_img = None - # Make the disk image - if not opts.disk_image: - # Parse the kickstart to get the partition sizes + # Parse the kickstart + if opts.ks: ks_version = makeVersion() ks = KickstartParser( ks_version, errorsAreFatal=False, missingIncludeIsFatal=False ) ks.readKickstart( opts.ks[0] ) + + # Make the disk image + if not opts.disk_image: + if not opts.ks: + log.error("Image creation requires a kickstart file") + sys.exit(1) + disk_size = 1 + (sum( [p.size for p in ks.handler.partition.partitions] ) / 1024) log.info( "disk_size = {0}GB".format(disk_size) ) @@ -627,7 +717,10 @@ if __name__ == '__main__': "graphical), this will interfere with livemedia-creator.") sys.exit(1) - disk_img = tempfile.mktemp( prefix="disk", suffix=".img", dir=opts.tmp ) + if opts.image_name: + disk_img = joinpaths(opts.tmp, opts.image_name) + else: + disk_img = tempfile.mktemp( prefix="disk", suffix=".img", dir=opts.tmp ) install_log = os.path.abspath(os.path.dirname(opts.logfile))+"/virt-install.log" log.info( "disk_img = {0}".format(disk_img) ) @@ -700,12 +793,21 @@ if __name__ == '__main__': opts.lorax_templates, opts.title, opts.project, opts.releasever, opts.volid ) + # cleanup the mess + if disk_img and not opts.keep_image and not opts.disk_image: + os.unlink( disk_img ) + log.info("Disk image erased") + disk_img = None elif opts.make_ami and not opts.image_only: result_dir = make_ami(opts.disk_image or disk_img) - - # cleanup the mess - if disk_img and not opts.keep_image and not opts.disk_image: - os.unlink( disk_img ) + elif opts.make_appliance and not opts.image_only: + if not opts.ks: + networks = [] + else: + networks = ks.handler.network.network + make_appliance(opts.disk_image or disk_img, opts.app_name, + opts.app_template, opts.app_file, networks, opts.ram, + opts.vcpus, opts.arch, opts.title, opts.project, opts.releasever) if opts.result_dir and result_dir: shutil.copytree( result_dir, opts.result_dir ) @@ -714,10 +816,10 @@ if __name__ == '__main__': log.info("SUMMARY") log.info("-------") log.info("Logs are in {0}".format(os.path.abspath(os.path.dirname(opts.logfile)))) - if opts.keep_image or opts.make_disk: + if disk_img: log.info("Disk image is at {0}".format(disk_img)) - else: - log.info("Disk image erased") + if opts.make_appliance: + log.info("Appliance description is in {0}".format(opts.app_file)) if result_dir: log.info("Results are in {0}".format(opts.result_dir or result_dir)) -- 1.7.7.6 _______________________________________________ Anaconda-devel-list mailing list Anaconda-devel-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/anaconda-devel-list