Blob Blame History Raw
#
# appliance.py: ApplianceImageCreator class
#
# Copyright 2007-2008, Red Hat  Inc.
# Copyright 2008, Daniel P. Berrange
# Copyright 2018, Neal Gompa
#
# 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; version 2 of the License.
#
# 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 Library General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

from __future__ import print_function
from builtins import chr
from builtins import range
import os
import os.path
import glob
import shutil
import zipfile
import tarfile
import subprocess
import logging
import re

from imgcreate.errors import *
from imgcreate.fs import *
from imgcreate.creator import *
from appcreate.partitionedfs import *
import progress.bar as progress

class ApplianceImageCreator(ImageCreator):
    """Installs a system into a file containing a partitioned disk image.

    ApplianceImageCreator is an advanced ImageCreator subclass; a sparse file
    is formatted with a partition table, each partition loopback mounted
    and the system installed into an virtual disk. The disk image can
    subsequently be booted in a virtual machine or accessed with kpartx

    """

    def __init__(self, ks, name, disk_format, vmem, vcpu, releasever=None, no_compress=False):
        """Initialize a ApplianceImageCreator instance.

        This method takes the same arguments as ImageCreator.__init__()

        """
        ImageCreator.__init__(self, ks, name, releasever=releasever)

        self.__instloop = None
        self.__imgdir = None
        self.__disks = {}
        self.__disk_format = disk_format
        self.__compress = not no_compress

        #appliance parameters
        self.vmem = vmem
        self.vcpu = vcpu
        self.checksum = False
        self.appliance_version = None
        self.appliance_release = None

        #additional modules to include
        self.modules = ["sym53c8xx", "aic7xxx", "mptspi"]

        # This determines which partition layout we'll be using
        self.bootloader = None
        self.grub2inst_params = []

    def _get_fstab(self):
        s = ""
        for mp in self.__instloop.mountOrder:
            p = None
            for p1 in self.__instloop.partitions:
                if p1['mountpoint'] == mp:
                    p = p1
                    break

            if not p['UUID'] is None:
                mountdev = p['UUID']
            else:
                mountdev = "LABEL=_%s" % p['mountpoint']
            s +=  "%(mountdev)s  %(mountpoint)s %(fstype)s    defaults,noatime 0 0\n" %  {
                'mountdev': mountdev,
                'mountpoint': p['mountpoint'],
                'fstype': p['fstype'] }
        return s

    def _create_mkinitrd_config(self):
        #write  to tell which modules to be included in initrd

        extramods = ""
        for module in self.modules:
            extramods += '%s ' % module

        mkinitrd = ""
        mkinitrd += "PROBE=\"no\"\n"
        mkinitrd += "MODULES=\"ext3 ata_piix sd_mod libata scsi_mod\"\n"
        mkinitrd += "MODULES=\"%s\"\n" % extramods
        mkinitrd += "rootfs=\"ext3\"\n"
        mkinitrd += "rootopts=\"defaults\"\n"

        logging.debug("Writing mkinitrd config %s/etc/sysconfig/mkinitrd" % self._instroot)
        os.makedirs(self._instroot + "/etc/sysconfig/", mode=644)
        cfg = open(self._instroot + "/etc/sysconfig/mkinitrd", "w")
        cfg.write(mkinitrd)
        cfg.close()

    #
    # Actual implementation
    #
    def _mount_instroot(self, base_on = None):
        self.__imgdir = self._mkdtemp()

        #list of partitions from kickstart file
        parts = kickstart.get_partitions(self.ks)
        # need to eliminate duplicate partitions
        # this is a bit of a hack but we assume the last partition for a given mount point is the one we want
        mountpoints = []
        toremove = []
        for part in parts:
            mp = part.mountpoint
            if mp in mountpoints:
                toremove.append(part)
            else:
                mountpoints.append(mp)
        for part in toremove:
            parts.remove(part)
        #list of disks where a disk is an dict with name: and size
        disks = []

        for i in range(len(parts)):
            if parts[i].disk:
                disk = parts[i].disk
            else:
                logging.debug("No --ondisk specified in partition line of ks file; assuming 'sda'")
                disk = "sda"

            size = parts[i].size * 1024 * 1024

            if len(disks) == 0:
                disks.append({ 'name': disk, 'size': size })
            else:
                found = 'false'
                for j in range(len(disks)):
                    if disks[j]['name'] == disk:
                        disks[j]['size'] = disks[j]['size'] + size
                        found = 'true'
                        break
                    else: found = 'false'
                if found == 'false':
                    disks.append({ 'name': disk, 'size': size })

        #create disk
        for item in disks:
            logging.debug("Adding disk %s as %s/%s-%s.raw" % (item['name'], self.__imgdir, self.name, item['name']))
            disk = SparseLoopbackDisk("%s/%s-%s.raw" % (self.__imgdir, self.name, item['name']), item['size'])
            self.__disks[item['name']] = disk


        # Search for bootloader package in package list
        packages = kickstart.get_packages(self.ks)
        # make this the default
        partition_layout = None
        # set bootloader only if it is enabled and use user specified partition_layout
        if ((not hasattr(self.ks.handler.bootloader, "disabled")) or
           (hasattr(self.ks.handler.bootloader, "disabled") and self.ks.handler.bootloader.disabled is False)):
            # check for extlinux in kickstart then grub2 and falling back to grub
            if hasattr(self.ks.handler.bootloader, "extlinux") and self.ks.handler.bootloader.extlinux is True:
                if 'syslinux-extlinux' in packages:
                    self.bootloader = 'extlinux'
                    partition_layout = 'msdos'
                elif 'extlinux-bootloader' in packages:
                    self.bootloader = 'extlinux-bootloader'
                    partition_layout = 'msdos'
                else:
                    logging.warning("WARNING! syslinux-extlinux package not found.")
            if self.bootloader is None:
                if 'grub2-efi-arm' in packages:
                    self.bootloader = 'grub2'
                    self.grub2inst_params = ["--target=arm-efi", "--removable"]
                elif 'grub2-efi-aa64' in packages:
                    self.bootloader = 'grub2'
                elif 'grub2-efi-ia32' in packages:
                    self.bootloader = 'grub2'
                elif 'grub2-efi-x64' in packages:
                    self.bootloader = 'grub2'
                elif 'grub2' in packages or 'grub2-pc' in packages:
                    self.bootloader = 'grub2'
                elif 'grub' in packages:
                    self.bootloader = 'grub'
                else:
                    logging.warning("WARNING! grub package not found.")
        else:
            # user explicitly disabled bootloader (i.e. not part of disk image)
            pass

        # Determine partition type if it was not forced
        if partition_layout is None:
            if (hasattr(self.ks.handler.clearpart, "disklabel")
               and self.ks.handler.clearpart.disklabel is not None and self.ks.handler.clearpart.disklabel != ""):
                logging.debug("Using user set default disk label: {}".format(self.ks.handler.clearpart.disklabel))
                partition_layout = self.ks.handler.clearpart.disklabel
            else:
                partition_layout = 'msdos'
        else:
            logging.debug("Forcing disk label to %s, because bootloader is %s" % (partition_layout, self.bootloader))

        self.__instloop = PartitionedMount(self.__disks,
                                           self._instroot,
                                           partition_layout,
                                           self.bootloader)

        for p in parts:
            if p.disk:
                self.__instloop.add_partition(int(p.size), p.disk, p.mountpoint, p.fstype)
            else:
                self.__instloop.add_partition(int(p.size), "sda", p.mountpoint, p.fstype)

        try:
            self.__instloop.mount()
        except MountError as e:
            raise CreatorError("Failed mount disks : %s" % e)

        self._create_mkinitrd_config()

    def _kernel_cmdline_append(self):
        options = self.ks.handler.bootloader.appendLine
        if options == '':
            options = 'rhgb quiet'
        lang = self.ks.handler.lang.lang
        if lang != '':
            options = '%s LANG=%s' % (options, lang)
        return options

    def _create_grub_devices(self, grubversion = 1):
        devs = []
        parts = kickstart.get_partitions(self.ks)
        for p in parts:
            dev = p.disk
            if dev != "" and not dev in devs:
                devs.append(dev)

        if devs == []:
            devs.append("sda")

        devs.sort()

        n = 0
        devmap = ""
        for dev in devs:
            devmap += "(hd%-d) /dev/%s\n" % (n, dev)
            n += 1

        if grubversion == 2:
            grubdir = "/boot/grub2"
        else:
            grubdir = "/boot/grub"

        logging.debug("Writing grub %s%s/device.map" % (self._instroot, grubdir))
        cfg = open(self._instroot + "%s/device.map" % grubdir, "w")
        cfg.write(devmap)
        cfg.close()

    def _get_grub_boot_config(self):
        bootdevnum = None
        rootdevnum = None
        rootdev = None
        for p in self.__instloop.partitions:
            if p['mountpoint'] == "/boot":
                bootdevnum = p['num'] - 1
            elif p['mountpoint'] == "/" and bootdevnum is None:
                bootdevnum = p['num'] - 1

            if p['mountpoint'] == "/":
                rootdevnum = p['num'] - 1
                if not p['UUID'] is None:
                    rootdev = p['UUID']
                else:
                    rootdev = "LABEL=_/"

        prefix = ""
        if bootdevnum == rootdevnum:
            prefix = "/boot"

        return (bootdevnum, rootdevnum, rootdev, prefix)

    def _create_grub_config(self):
        (bootdevnum, rootdevnum, rootdev, prefix) = self._get_grub_boot_config()
        options = self._kernel_cmdline_append()

        # NB we're assuming that grub config is on the first physical disk
        # ie /boot must be on sda, or if there's no /boot, then / must be sda

        # XXX don't hardcode default kernel - see livecd code
        grub = ""
        grub += "default=0\n"
        grub += "timeout=5\n"
        grub += "splashimage=(hd0,%d)%s/grub/splash.xpm.gz\n" % (bootdevnum, prefix)
        grub += "hiddenmenu\n"

        versions = []
        kernels = self._get_kernel_versions()
        for kernel in kernels:
            for version in kernels[kernel]:
                versions.append(version)

        if int(subprocess.Popen("ls " + self._instroot + "/boot/initramfs* | wc -l", shell=True, stdout=subprocess.PIPE).communicate()[0].decode("utf-8").strip()) > 0:
            initrd = "initramfs"
        else:
            initrd = "initrd"

        for v in versions:
            grub += "title %s (%s)\n" % (self.name, v)
            grub += "        root (hd0,%d)\n" % bootdevnum
            grub += "        kernel %s/vmlinuz-%s ro root=%s %s\n" % (prefix, v, rootdev, options)
            grub += "        initrd %s/%s-%s.img\n" % (prefix, initrd, v)

        logging.debug("Writing grub config %s/boot/grub/grub.conf" % self._instroot)
        if not os.path.isdir(self._instroot + "/boot/grub/"):
            os.mkdir(self._instroot + "/boot/grub/")
        cfg = open(self._instroot + "/boot/grub/grub.conf", "w")
        cfg.write(grub)
        cfg.close()

    def _get_extlinux_boot_config(self):
        bootdevnum = None
        rootdevnum = None
        rootdev = None
        for p in self.__instloop.partitions:
            if p['mountpoint'] == "/boot":
                bootdevnum = p['num'] - 1
            elif p['mountpoint'] == "/" and bootdevnum is None:
                bootdevnum = p['num'] - 1

            if p['mountpoint'] == "/":
                rootdevnum = p['num'] - 1
                if not p['UUID'] is None:
                    rootdev = p['UUID']
                else:
                    rootdev = "LABEL=_/"
        prefix = ""
        if bootdevnum == rootdevnum:
            prefix = "/boot"

        return (bootdevnum, rootdevnum, rootdev, prefix)

    def _create_extlinux_config(self):
        (bootdevnum, rootdevnum, rootdev, prefix) = self._get_grub_boot_config()
        options = self._kernel_cmdline_append()
        # Search for bootloader package in package list
        packages = kickstart.get_packages(self.ks)

        extlinux = "# extlinux.conf generated by appliance-creator\n"
        extlinux += "ui menu.c32\n"
        extlinux += "menu autoboot Welcome to %s. Automatic boot in # second{,s}. Press a key for options.\n" % (self.name)
        extlinux += "menu title %s Boot Options.\n" % (self.name)
        extlinux += "menu hidden\n"
        extlinux += "timeout 20\n"
        extlinux += "totaltimeout 600\n\n"

        versions = []
        kernels = self._get_kernel_versions()
        for kernel in kernels:
            for version in kernels[kernel]:
                versions.append(version)

        if int(subprocess.Popen("ls " + self._instroot + "/boot/initramfs* | wc -l", shell=True, stdout=subprocess.PIPE).communicate()[0].decode("utf-8").strip()) > 0:
            initrd = "initramfs"
        else:
            initrd = "initrd"

        for v in versions:
            extlinux += "label %s (%s)\n" % (self.name, v)
            extlinux += "\tkernel %s/vmlinuz-%s\n" % (prefix, v)
            extlinux += "\tappend ro root=%s %s\n" % (rootdev, options)
            if 'extlinux-bootloader' in packages:
                extlinux += "\tfdtdir %s/dtb-%s/\n" % (prefix, v)
            extlinux += "\tinitrd %s/%s-%s.img\n\n" % (prefix, initrd, v)


        logging.debug("Writing extlinux config %s/boot/extlinux/extlinux.conf" % self._instroot)
        cfg = open(self._instroot + "/boot/extlinux/extlinux.conf", "w")
        cfg.write(extlinux)
        cfg.close()

    def _copy_grub_files(self):
        imgpath = None
        # http://bugs.centos.org/view.php?id=4995
        # https://issues.jboss.org/browse/BGBUILD-267
        for machine in ["x86_64-redhat", "i386-redhat", "x86_64-unknown", "i386-unknown", "x86_64-pc", "i386-pc"]:
            imgpath = self._instroot + "/usr/share/grub/" + machine
            if os.path.exists(imgpath):
                break

        files = ["e2fs_stage1_5", "stage1", "stage2"]
        for f in files:
            path = imgpath + "/" + f
            if not os.path.isfile(path):
                raise CreatorError("grub not installed : "
                                   "%s not found" % path)

            logging.debug("Copying %s to %s/boot/grub/%s" %(path, self._instroot, f))
            shutil.copy(path, self._instroot + "/boot/grub/" + f)

    def _install_grub(self):
        (bootdevnum, rootdevnum, rootdev, prefix) = self._get_grub_boot_config()

        # Ensure all data is flushed to disk before doing grub install
        subprocess.call(["sync"])

        stage2 = "/boot/grub/stage2"
        setup = ""

        i = 0
        for name in list(self.__disks.keys()):
            loopdev = self.__disks[name].device
            setup += "device (hd%s) %s\n" % (i, loopdev)
            i = i + 1
        setup += "root (hd0,%d)\n" % bootdevnum
        setup += "setup --stage2=%s --prefix=%s/grub  (hd0)\n" % (stage2, prefix)
        setup += "quit\n"

        logging.debug("Installing grub to %s" % loopdev)

        subprocess.call(["mount", "--bind", "/dev", self._instroot + "/dev"])

        grub = subprocess.Popen(["chroot", self._instroot, "/sbin/grub", "--batch", "--no-floppy"],
                                stdin=subprocess.PIPE)

        grub.communicate(setup.encode("utf-8"))
        rc = grub.wait()

        subprocess.call(["umount", self._instroot + "/dev"])

        if rc != 0:
            raise MountError("Unable to install grub bootloader")

        logging.debug("Grub installed.")

    def _install_grub2(self):
        (bootdevnum, rootdevnum, rootdev, prefix) = self._get_grub_boot_config()

        i = 0
        for name in list(self.__disks.keys()):
            loopdev = self.__disks[name].device
            i = i + 1

        logging.debug("Installing grub2 to %s" % loopdev)

        # mount full /dev filesystem
        subprocess.call(["mount", "--bind", "/dev", self._instroot + "/dev"])

        rc = subprocess.call(["chroot", self._instroot, "grub2-install", "--no-floppy", "--grub-mkdevicemap=/boot/grub2/device.map"] + self.grub2inst_params + [loopdev])

        if rc != 0:
            subprocess.call(["umount", self._instroot + "/dev"])
            raise MountError("Unable to install grub2 bootloader")

        mkconfig_dest = "/boot/grub2/grub.cfg"
        try:
            logging.debug(self._instroot + "/boot/grub2/grubenv")
            if os.path.islink(self._instroot + "/boot/grub2/grubenv"):
                dest=os.readlink(self._instroot + "/boot/grub2/grubenv")
                dest=dest.replace("grubenv","grub.cfg")
                os.symlink(dest,self._instroot+mkconfig_dest)
                logging.debug("Symlink created from %s to %s" % (dest,mkconfig_dest))
        except:
            logging.debug("Error creating symlink for %s" % mkconfig_dest)

        logging.debug("Grub2 installed.")
        logging.debug("Generating grub2 configuration file in (%s)..." % mkconfig_dest)

        # Generating grub2 config file
        subprocess.call(["chroot", self._instroot, "grub2-mkconfig", "-o", mkconfig_dest])

        # umount /dev filesystem
        subprocess.call(["umount", self._instroot + "/dev"])

        rootpartition = self.__instloop.partitions[rootdevnum]
        bootpartition = self.__instloop.partitions[bootdevnum]

        # Grub2 config file needs some cleanup because it has root device specified as a loop disk...
        grub2_cfg = open(self._instroot + "/boot/grub2/grub.cfg","r+")
        data = grub2_cfg.read()
        # Changing values for both - root and boot partitions
        if not bootpartition['UUID'] is None:
            data = re.sub(bootpartition['devicemapper'], bootpartition['UUID'], data)
        else:
            data = re.sub(bootpartition['devicemapper'], "LABEL=_%s" % bootpartition['mountpoint'], data)
        if not rootpartition['UUID'] is None:
            data = re.sub(rootpartition['devicemapper'], rootpartition['UUID'], data)
            data = re.sub(loopdev, rootpartition['UUID'], data)
        else:
            data = re.sub(rootpartition['devicemapper'], "LABEL=_%s" % rootpartition['mountpoint'], data)
            data = re.sub(loopdev, "LABEL=_%s" % rootpartition['mountpoint'], data)

        grub2_cfg.seek(0)
        grub2_cfg.truncate()
        grub2_cfg.write(data)
        grub2_cfg.close()

        logging.debug("Grub2 configuration file generated.")

    def _install_extlinux(self):
        i = 0
        for name in list(self.__disks.keys()):
            loopdev = self.__disks[name].device
            i = i + 1

        logging.debug("Installing extlinux bootloader to %s" % loopdev)

        (bootdevnum, rootdevnum, rootdev, prefix) = self._get_extlinux_boot_config()


        # Set MBR
        mbrsize = os.stat("%s/usr/share/syslinux/mbr.bin" % self._instroot)[stat.ST_SIZE]
        rc = subprocess.call(["/bin/dd", "if=%s/usr/share/syslinux/mbr.bin" % self._instroot, "of=" + loopdev])
        if rc != 0:
            raise MountError("Unable to set MBR to %s" % loopdev)

        # Set Bootable flag
        parted = "/usr/sbin/parted"
        if not os.path.exists(parted):
            parted = "/sbin/parted"
            if not os.path.exists(parted):
                raise CreatorError("Missed parted, please install it.")
        dev_null = os.open("/dev/null", os.O_WRONLY)
        rc = subprocess.call([parted, "-s", loopdev, "set", "%d" % (bootdevnum + 1), "boot", "on"],
                             stdout = dev_null, stderr = dev_null)
        os.close(dev_null)
        # XXX disabled return code check because parted always fails to
        # reload part table with loop devices. Annoying because we can't
        # distinguish this failure from real partition failures :-(
        if rc != 0 and 1 == 0:
            raise MountError("Unable to set bootable flag to %sp%d" % (loopdev, (bootdevnum + 1)))


        # Ensure all data is flushed to disk before doing extlinux install
        subprocess.call(["sync"])

        fullpathextlinux = "/sbin/extlinux"
        if not os.path.isfile(fullpathextlinux):
            fullpathextlinux = "/usr/sbin/extlinux"
            if not os.path.isfile(fullpathextlinux):
                fullpathextlinux = "/usr/bin/extlinux"
        rc = subprocess.call([fullpathextlinux, "-i", "%s/boot/extlinux" % self._instroot])
        if rc != 0:
            raise MountError("Unable to install extlinux bootloader to %sp)d" % (loopdev, (bootdevnum + 1)))


    def _create_bootconfig(self):
        logging.debug("Writing kickstart file.")
        self._write_kickstart()
        # For EC2 lets make a grub Legacy config file
        # (only if bootloader is enabled)
        if ((hasattr(self.ks.handler.bootloader, "disabled") and self.ks.handler.bootloader.disabled is False) and
           (hasattr(self.ks.handler.bootloader, "location") and self.ks.handler.bootloader.location != "none")):
            logging.debug("Writing GRUB Legacy config.")
            self._create_grub_config()

        if self.bootloader == 'grub2':
            # We have GRUB2 package installed
            # Most probably this is Fedora 16+
            logging.debug("Using GRUB2.")
            self._create_grub_devices(2)
            self._install_grub2()
        elif self.bootloader == 'grub':
            # We have GRUB Legacy installed
            logging.debug("Using GRUB Legacy.")
            self._create_grub_devices()
            self._copy_grub_files()
            self._install_grub()
        elif self.bootloader == 'extlinux':
            logging.debug("Using EXTLINUX.")
            self._create_extlinux_config()
            self._install_extlinux()
        elif self.bootloader == 'extlinux-bootloader':
            logging.debug("Using EXTLINUX from extlinux-bootloader package.")
            self._create_extlinux_config()
        else:
            # No GRUB package is available
            logging.warning("WARNING! No bootloader found.")

    def _unmount_instroot(self):
        if not self.__instloop is None:
            self.__instloop.cleanup()

    def _resparse(self, size = None):
        return self.__instloop.resparse(size)

    def package(self, destdir, package, include):
        """Prepares the created image for final delivery.
           Stage
           add includes
           package
        """
        self._stage_final_image()

        #add stuff
        if include and os.path.isdir(include):
            logging.debug("adding everything in %s to %s" % (include, self._outdir))
            files = glob.glob('%s/*' % include)
            for file in files:
                if os.path.isdir(file):
                    logging.debug("adding dir %s to %s" % (file, os.path.join(self._outdir, os.path.basename(file))))
                    shutil.copytree(file, os.path.join(self._outdir, os.path.basename(file)), symlinks=False)
                else:
                    logging.debug("adding %s to %s" % (file, self._outdir))
                    shutil.copy(file, self._outdir)
        elif include:
            logging.debug("adding %s to %s" % (include, self._outdir))
            shutil.copy(include, self._outdir)

        #package
        (pkg, comp) = os.path.splitext(package)
        if comp:
            comp = comp.lstrip(".")

        if pkg == "zip":
            dst = "%s/%s.zip" % (destdir, self.name)
            files = glob.glob('%s/*' % self._outdir)
            if comp == "64":
                logging.debug("creating %s with ZIP64 extensions" %  (dst))
                z = zipfile.ZipFile(dst, "w", compression=8, allowZip64="True")
            else:
                logging.debug("creating %s" %  (dst))
                z = zipfile.ZipFile(dst, "w", compression=8, allowZip64="False")
            for file in files:
                if file != dst:
                    if os.path.isdir(file):
                        #because zip sucks we cannot just add a dir
                        for root, dirs, dirfiles in os.walk(file):
                            for dirfile in dirfiles:
                                arcfile = self.name+"/"+root[len(os.path.commonprefix((os.path.dirname(file), root)))+1:]+"/"+dirfile
                                filepath = os.path.join(root, dirfile)
                                logging.debug("adding %s to %s" % (arcfile, dst))
                                z.write(filepath, arcfile, compress_type=None)
                    else:
                        logging.debug("adding %s to %s" % (os.path.join(self.name, os.path.basename(file)), dst))
                        z.write(file, arcname = os.path.join(self.name, os.path.basename(file)), compress_type=None)
            z.close()

        elif pkg == "tar":
            if comp:
                dst = "%s/%s.tar.%s" % (destdir, self.name, comp)
            else:
                dst = "%s/%s.tar" % (destdir, self.name)
            files = glob.glob('%s/*' % self._outdir)
            logging.debug("creating %s" %  (dst))
            tar = tarfile.open(dst, "w|"+comp)
            for file in files:
                logging.debug("adding %s to %s" % (file, dst))
                tar.add(file, arcname = os.path.join(self.name, os.path.basename(file)))
            tar.close()

        else:
            dst = os.path.join(destdir, self.name)
            logging.debug("creating destination dir: " + dst)
            makedirs(dst)
            for f in os.listdir(self._outdir):
                logging.debug("moving %s to %s" % (os.path.join(self._outdir, f), os.path.join(dst, f)))
                shutil.move(os.path.join(self._outdir, f), os.path.join(dst, f))
        print("Finished")

    def _stage_final_image(self):
        """Stage the final system image in _outdir.
           Convert disks
           write meta data
        """
        self._resparse()

        #if disk_format is not raw convert the disk and put in _outdir
        if self.__disk_format != "raw":
            self._convert_image()
        #else move to _outdir
        else:
            logging.debug("moving disks to stage location")
            for name in list(self.__disks.keys()):
                src = "%s/%s-%s.%s" % (self.__imgdir, self.name, name, self.__disk_format)
                dst = "%s/%s-%s.%s" % (self._outdir, self.name, name, self.__disk_format)

                if self.__compress:
                    # Compress with xz using 16 MiB block size for seekability
                    rc = subprocess.call(["xz", "-z", "--block-size=16777216", "-T 0", src])
                    if rc == 0:
                        logging.debug("compression successful")
                    if rc != 0:
                        raise CreatorError("Unable to compress disk to %s" % self.__disk_format)
                    src = "%s.xz" % (src)
                    dst = "%s.xz" % (dst)

                logging.debug("moving %s to %s" % (src, dst))
                shutil.move(src, dst)
        #write meta data in stage dir
        self._write_image_xml()

    def _convert_image(self):
        #convert disk format
        for name in list(self.__disks.keys()):
            dst = "%s/%s-%s.%s" % (self._outdir, self.name, name, self.__disk_format)
            logging.debug("converting %s image to %s" % (self.__disks[name].lofile, dst))
            if self.__compress and self.__disk_format == "qcow2":
                logging.debug("using compressed qcow2")
                compressflag = "-c"
            else:
                compressflag = ""
            rc = subprocess.call(["qemu-img", "convert", compressflag,
                                   "-f", "raw", self.__disks[name].lofile,
                                   "-O", self.__disk_format,  dst])
            if rc == 0:
                logging.debug("convert successful")
            if rc != 0:
                raise CreatorError("Unable to convert disk to %s" % self.__disk_format)

    def _write_kickstart(self):
        #write out the kicks tart to /root/anaconda-ks.cfg
        ks = open(self._instroot + "/root/anaconda-ks.cfg", "w")
        ks.write("%s" % (self.ks.handler,))
        ks.close()



    def _write_image_xml(self):
        xml = "<image>\n"

        name_attributes = ""
        if self.appliance_version:
            name_attributes += " version='%s'" % self.appliance_version
        if self.appliance_release:
            name_attributes += " release='%s'" % self.appliance_release
        xml += "  <name%s>%s</name>\n" % (name_attributes, self.name)
        xml += "  <domain>\n"
        # XXX don't hardcode - determine based on the kernel we installed for grub
        # baremetal vs xen
        xml += "    <boot type='hvm'>\n"
        xml += "      <guest>\n"
        xml += "        <arch>%s</arch>\n" % os.uname()[4]
        xml += "      </guest>\n"
        xml += "      <os>\n"
        xml += "        <loader dev='hd'/>\n"
        xml += "      </os>\n"

        i = 0
        for name in list(self.__disks.keys()):
            xml += "      <drive disk='%s-%s.%s' target='hd%s'/>\n" % (self.name, name, self.__disk_format, chr(ord('a')+i))
            i = i + 1

        xml += "    </boot>\n"
        xml += "    <devices>\n"
        xml += "      <vcpu>%s</vcpu>\n" % self.vcpu
        xml += "      <memory>%d</memory>\n" % (self.vmem * 1024)
        for network in self.ks.handler.network.network:
            xml += "      <interface/>\n"
        xml += "      <graphics/>\n"
        xml += "    </devices>\n"
        xml += "  </domain>\n"
        xml += "  <storage>\n"

        if self.checksum is True:
            for name in list(self.__disks.keys()):
                diskpath = "%s/%s-%s.%s" % (self._outdir, self.name, name, self.__disk_format)
                disk_size = os.path.getsize(diskpath)
                meter_ct = 0
                meter = progress.Bar("Generating disk signature for %s-%s.%s" % (self.name, name, self.__disk_format), max=disk_size)
                xml += "    <disk file='%s-%s.%s' use='system' format='%s'>\n" % (self.name, name, self.__disk_format, self.__disk_format)

                try:
                    import hashlib
                    m1 = hashlib.sha1()
                    m2 = hashlib.sha256()
                except:
                    import sha
                    m1 = sha.new()
                    m2 = None
                f = open(diskpath, "r")
                while 1:
                    chunk = f.read(65536)
                    if not chunk:
                        break
                    m1.update(chunk)
                    if m2:
                        m2.update(chunk)
                    meter.next(65536)

                sha1checksum = m1.hexdigest()
                xml +=  """      <checksum type='sha1'>%s</checksum>\n""" % sha1checksum

                if m2:
                    sha256checksum = m2.hexdigest()
                    xml += """      <checksum type='sha256'>%s</checksum>\n""" % sha256checksum
                xml += "    </disk>\n"
        else:
            for name in list(self.__disks.keys()):
                xml += "    <disk file='%s-%s.%s' use='system' format='%s'/>\n" % (self.name, name, self.__disk_format, self.__disk_format)

        xml += "  </storage>\n"
        xml += "</image>\n"

        logging.debug("writing image XML to %s/%s.xml" %  (self._outdir, self.name))
        cfg = open("%s/%s.xml" % (self._outdir, self.name), "w")
        cfg.write(xml)
        cfg.close()
        #print "Wrote: %s.xml" % self.name