[lorax 2/3] livemedia-creator: Add appliance creation

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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


[Index of Archives]     [Kickstart]     [Fedora Users]     [Fedora Legacy List]     [Fedora Maintainers]     [Fedora Desktop]     [Fedora SELinux]     [Big List of Linux Books]     [Yosemite News]     [Yosemite Photos]     [KDE Users]     [Fedora Tools]
  Powered by Linux