[pkg-eucalyptus-commits] [SCM] managing cloud instances for Eucalyptus branch, master, updated. 3.0.0-alpha3-257-g1da8e3a

Matt Spaulding mspaulding06 at gmail.com
Sun Jun 16 02:31:23 UTC 2013


The following commit has been merged in the master branch:
commit e8074a10b14fb6d5aed0562f7167e90f13798993
Author: Matt Spaulding <mspaulding06 at gmail.com>
Date:   Fri May 10 21:52:31 2013 -0700

    First attempt at euca-bundle-vol

diff --git a/euca2ools/bundler.py b/euca2ools/bundler.py
deleted file mode 100644
index d74653a..0000000
--- a/euca2ools/bundler.py
+++ /dev/null
@@ -1,770 +0,0 @@
-# Software License Agreement (BSD License)
-#
-# Copyright (c) 2009-2011, Eucalyptus Systems, Inc.
-# All rights reserved.
-#
-# Redistribution and use of this software in source and binary forms, with or
-# without modification, are permitted provided that the following conditions
-# are met:
-#
-#   Redistributions of source code must retain the above
-#   copyright notice, this list of conditions and the
-#   following disclaimer.
-#
-#   Redistributions in binary form must reproduce the above
-#   copyright notice, this list of conditions and the
-#   following disclaimer in the documentation and/or other
-#   materials provided with the distribution.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
-# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-# POSSIBILITY OF SUCH DAMAGE.
-#
-# Author: Neil Soman neil at eucalyptus.com
-#         Mitch Garnaat mgarnaat at eucalyptus.com
-
-import sys
-import os
-import tarfile
-from xml.dom.minidom import Document
-from xml.dom import minidom
-from hashlib import sha1 as sha
-from M2Crypto import BN, EVP, RSA, X509
-from binascii import hexlify, unhexlify
-import subprocess
-import tempfile
-import stat
-import platform
-import re
-import shutil
-import logging
-import base64
-import image
-import utils
-from exceptions import *
-
-BUNDLER_NAME = 'euca-tools'
-BUNDLER_VERSION = '1.3.2'
-VERSION = '2007-10-10'
-RELEASE = '31337'
-AES = 'AES-128-CBC'
-
-# Number of bytes to read per I/O operation while bundling
-IMAGE_IO_CHUNK = 10 * 1024
-# Number of bytes per part of a bundle
-IMAGE_SPLIT_CHUNK = IMAGE_IO_CHUNK * 1024
-# Recommended maximum number of bytes in an instance-store image (10G)
-# Don't make this an error, though -- 10G might be the limit in EC2, but in
-# Eucalyptus the sky's the limit; people can make images as large as their
-# patience allows.
-IMAGE_MAX_SIZE = 10 * 1024 * 1024 * 1024
-
-MAX_LOOP_DEVS = 256
-
-class Bundler(object):
-
-    def __init__(self, euca):
-        self.euca = euca
-        system_string = platform.system()
-        if system_string == 'Linux':
-            self.img = image.LinuxImage(self.euca.debug)
-        elif system_string == 'SunOS':
-            self.img = image.SolarisImage(self.euca.debug)
-        else:
-            self.img = 'Unsupported'
-
-    def split_file(self, file, chunk_size):
-        parts = []
-        parts_digest = []
-        file_size = os.path.getsize(file)
-        in_file = open(file, 'rb')
-        number_parts = int(file_size / chunk_size)
-        number_parts += 1
-        bytes_read = 0
-        for i in range(0, number_parts, 1):
-            filename = '%s.%02d' % (file, i)
-            part_digest = sha()
-            file_part = open(filename, 'wb')
-            print 'Part:', self.euca.get_relative_filename(filename)
-            part_bytes_written = 0
-            while part_bytes_written < IMAGE_SPLIT_CHUNK:
-                data = in_file.read(IMAGE_IO_CHUNK)
-                file_part.write(data)
-                part_digest.update(data)
-                data_len = len(data)
-                part_bytes_written += data_len
-                bytes_read += data_len
-                if bytes_read >= file_size:
-                    break
-            file_part.close()
-            parts.append(filename)
-            parts_digest.append(hexlify(part_digest.digest()))
-
-        in_file.close()
-        return (parts, parts_digest)
-
-    def check_image(self, image_file, path):
-        print 'Checking image'
-        if not os.path.exists(path):
-            os.makedirs(path)
-        image_size = os.path.getsize(image_file)
-        if self.euca.debug:
-            print 'Image Size:', image_size, 'bytes'
-        if image_size > IMAGE_MAX_SIZE:
-            print >> sys.stderr, ('warning: this image is larger than 10 GB.  '
-                                  'It will not work in EC2.')
-        return image_size
-
-    def get_fs_info(self, path):
-        fs_type = None
-        uuid = None
-        label = None
-        devpth = None
-        tmpd = None
-        try:
-            st_dev=os.stat(path).st_dev
-            dev=os.makedev(os.major(st_dev),os.minor(st_dev))
-            tmpd=tempfile.mkdtemp()
-            devpth=("%s/dev" % tmpd)
-            os.mknod(devpth,0400 | stat.S_IFBLK ,dev)
-        except:
-            raise
-
-        ret = { }
-        pairs = { 'LABEL' : 'label', 'UUID' : 'uuid' , 'FS_TYPE' : 'fs_type' }
-        for (blkid_n, my_n) in pairs.iteritems():
-            cmd = [ 'blkid', '-s%s' % blkid_n, '-ovalue', devpth ]
-            print cmd
-            try:
-                output = subprocess.Popen(cmd, stdout=subprocess.PIPE).communicate()[0]
-                ret[my_n]=output.rstrip()
-            except Exception, e:
-                os.unlink(devpth)
-                os.rmdir(tmpd)
-                raise UnsupportedException("Unable to determine %s for %s" % (blkid_n, path))
-
-        os.unlink(devpth)
-        os.rmdir(tmpd)
-        return(ret)
-   
-    def tarzip_image(self, prefix, file, path):
-        utils.check_prerequisite_command('tar')
-        print 'Compressing image'
-
-        targz = '%s.tar.gz' % os.path.join(path, prefix)
-        targzfile = open(targz, 'w')
-
-        # make process pipes
-        tar_cmd = ['tar', 'ch', '-S']
-        file_path = self.euca.get_file_path(file)
-        if file_path:
-            tar_cmd.append('-C')
-            tar_cmd.append(file_path)
-            tar_cmd.append(self.euca.get_relative_filename(file))
-        else:
-            tar_cmd.append(file)
-        tarproc = subprocess.Popen(tar_cmd, stdout=subprocess.PIPE)
-        zipproc = subprocess.Popen(['gzip'], stdin=subprocess.PIPE, stdout=targzfile)
-
-        # pass tar output to digest and gzip
-        sha_image = sha()
-        buf=os.read(tarproc.stdout.fileno(), 8196)
-        while buf:
-            zipproc.stdin.write(buf)
-            sha_image.update(buf)
-            buf=os.read(tarproc.stdout.fileno(), 8196)
-
-        zipproc.stdin.close();
-        targzfile.close()
-
-        tarproc.wait()
-        zipproc.wait()
-        for p, pname in [(tarproc, 'tar'), (zipproc, 'gzip')]:
-            if p.returncode != 0:
-                print "'%s' returned error (%i)" % (pname, p.returncode)
-                raise CommandFailed
-            
-        if os.path.getsize(targz) <= 0:
-            print 'Could not tar/compress image'
-            raise CommandFailed
-        return (targz, hexlify(sha_image.digest()))
-
-    def hexToBytes(self, hexString):
-        bytes = []
-        hexString = ''.join(hexString.split(' '))
-        for i in range(0, len(hexString), 2):
-            bytes.append(chr(int(hexString[i:i + 2], 16)))
-
-        return ''.join(bytes)
-
-    def crypt_file(self, cipher, in_file, out_file):
-        while 1:
-            buf = in_file.read(IMAGE_IO_CHUNK)
-            if not buf:
-                break
-            out_file.write(cipher.update(buf))
-        out_file.write(cipher.final())
-
-    def encrypt_image(self, file):
-        print 'Encrypting image'
-        enc_file = '%s.part' % file.replace('.tar.gz', '')
-
-        # get 17 bytes of randomness with top bit a '1'.
-        # convert to a hex string like '0x<34 hex chars>L'
-        # then take the last 32 of the hex digits, giving 32 random hex chars
-        gen_key = hex(BN.rand(17 * 8,top=0))
-        key     = gen_key[4:36]
-        if self.euca.debug:
-            print 'Key: %s' % gen_key
-        gen_iv = hex(BN.rand(17 * 8,top=0))
-        iv     = gen_iv[4:36]
-        if self.euca.debug:
-            print 'IV: %s' % gen_iv
-
-        try:
-            k = EVP.Cipher(alg='aes_128_cbc', key=unhexlify(key),
-                           iv=unhexlify(iv), op=1)
-        except TypeError:
-            print >> sys.stderr
-            print >> sys.stderr, 'WARNING: retrying encryption to work around a rare RNG bug'
-            print >> sys.stderr, 'Please report the following values to Eucalyptus Systems at'
-            print >> sys.stderr, 'https://eucalyptus.atlassian.net/browse/TOOLS-103 to help'
-            print >> sys.stderr, 'diagnose this issue.'
-            print >> sys.stderr, 'k: ', key
-            print >> sys.stderr, 'iv:', iv
-            print >> sys.stderr
-            return self.encrypt_image(file)
-
-        in_file = open(file, 'rb')
-        out_file = open(enc_file, 'wb')
-        self.crypt_file(k, in_file, out_file)
-        in_file.close()
-        out_file.close()
-        bundled_size = os.path.getsize(enc_file)
-        return (enc_file, key, iv, bundled_size)
-
-    def split_image(self, file):
-        print 'Splitting image...'
-        return self.split_file(file, IMAGE_SPLIT_CHUNK)
-
-    def get_verification_string(self, manifest_string):
-        start_mc = manifest_string.find('<machine_configuration>')
-        end_mc = manifest_string.find('</machine_configuration>')
-        mc_config_string = manifest_string[start_mc:end_mc
-            + len('</machine_configuration>')]
-        start_image = manifest_string.find('<image>')
-        end_image = manifest_string.find('</image>')
-        image_string = manifest_string[start_image:end_image
-            + len('</image>')]
-
-        return mc_config_string + image_string
-
-    def parse_manifest(self, manifest_filename):
-        parts = []
-        encrypted_key = None
-        encrypted_iv = None
-        dom = minidom.parse(manifest_filename)
-        manifest_elem = dom.getElementsByTagName('manifest')[0]
-        parts_list = manifest_elem.getElementsByTagName('filename')
-        for part_elem in parts_list:
-            nodes = part_elem.childNodes
-            for node in nodes:
-                if node.nodeType == node.TEXT_NODE:
-                    parts.append(node.data)
-        encrypted_key_elem = \
-            manifest_elem.getElementsByTagName('user_encrypted_key')[0]
-        nodes = encrypted_key_elem.childNodes
-        for node in nodes:
-            if node.nodeType == node.TEXT_NODE:
-                encrypted_key = node.data
-        encrypted_iv_elem = \
-            manifest_elem.getElementsByTagName('user_encrypted_iv')[0]
-        nodes = encrypted_iv_elem.childNodes
-        for node in nodes:
-            if node.nodeType == node.TEXT_NODE:
-                encrypted_iv = node.data
-        return (parts, encrypted_key, encrypted_iv)
-
-    def assemble_parts(self, src_directory, directory, manifest_path, parts):
-        manifest_filename = self.euca.get_relative_filename(manifest_path)
-        encrypted_filename = os.path.join(directory,
-                manifest_filename.replace('.manifest.xml', '.enc.tar.gz'
-                ))
-        if len(parts) > 0:
-            if not os.path.exists(directory):
-                os.makedirs(directory)
-            encrypted_file = open(encrypted_filename, 'wb')
-            for part in parts:
-                print 'Part:', self.euca.get_relative_filename(part)
-                part_filename = os.path.join(src_directory, part)
-                part_file = open(part_filename, 'rb')
-                while 1:
-                    data = part_file.read(IMAGE_IO_CHUNK)
-                    if not data:
-                        break
-                    encrypted_file.write(data)
-                part_file.close()
-            encrypted_file.close()
-        return encrypted_filename
-
-    def decrypt_image(self, encrypted_filename, encrypted_key,
-                      encrypted_iv, private_key_path):
-        user_priv_key = RSA.load_key(private_key_path)
-        key = user_priv_key.private_decrypt(unhexlify(encrypted_key),
-                RSA.pkcs1_padding)
-        iv = user_priv_key.private_decrypt(unhexlify(encrypted_iv),
-                RSA.pkcs1_padding)
-        k = EVP.Cipher(alg='aes_128_cbc', key=unhexlify(key),
-                       iv=unhexlify(iv), op=0)
-
-        decrypted_filename = encrypted_filename.replace('.enc', '')
-        decrypted_file = open(decrypted_filename, 'wb')
-        encrypted_file = open(encrypted_filename, 'rb')
-        self.crypt_file(k, encrypted_file, decrypted_file)
-        encrypted_file.close()
-        decrypted_file.close()
-        return decrypted_filename
-
-    def decrypt_string(self, encrypted_string, private_key_path, encoded=False):
-        user_priv_key = RSA.load_key(private_key_path)
-        string_to_decrypt = encrypted_string
-        if encoded:
-            string_to_decrypt = base64.b64decode(encrypted_string)
-        return user_priv_key.private_decrypt(string_to_decrypt,
-                RSA.pkcs1_padding)
-
-    def untarzip_image(self, path, file):
-        untarred_filename = file.replace('.tar.gz', '')
-        tar_file = tarfile.open(file, 'r|gz')
-        tar_file.extractall(path)
-        untarred_names = tar_file.getnames()
-        tar_file.close()
-        return untarred_names
-
-    def generate_manifest(self, path, prefix, parts, parts_digest,
-                          file, key, iv, cert_path, ec2cert_path,
-                          private_key_path, target_arch,
-                          image_size, bundled_size,
-                          image_digest, user, kernel,
-                          ramdisk, mapping=None,
-                          product_codes=None, ancestor_ami_ids=None):
-        user_pub_key = X509.load_cert(cert_path).get_pubkey().get_rsa()
-        cloud_pub_key = \
-            X509.load_cert(ec2cert_path).get_pubkey().get_rsa()
-
-        user_encrypted_key = hexlify(user_pub_key.public_encrypt(key,
-                RSA.pkcs1_padding))
-        user_encrypted_iv = hexlify(user_pub_key.public_encrypt(iv,
-                                    RSA.pkcs1_padding))
-
-        cloud_encrypted_key = hexlify(cloud_pub_key.public_encrypt(key,
-                RSA.pkcs1_padding))
-        cloud_encrypted_iv = hexlify(cloud_pub_key.public_encrypt(iv,
-                RSA.pkcs1_padding))
-
-        user_priv_key = None
-        if private_key_path:
-            user_priv_key = RSA.load_key(private_key_path)
-
-        manifest_file = '%s.manifest.xml' % os.path.join(path, prefix)
-        if self.euca.debug:
-            print 'Manifest: ', manifest_file
-
-        print 'Generating manifest %s' % manifest_file
-
-        manifest_out_file = open(manifest_file, 'wb')
-        doc = Document()
-
-        manifest_elem = doc.createElement('manifest')
-        doc.appendChild(manifest_elem)
-
-        # version
-
-        version_elem = doc.createElement('version')
-        version_value = doc.createTextNode(VERSION)
-        version_elem.appendChild(version_value)
-        manifest_elem.appendChild(version_elem)
-
-        # bundler info
-
-        bundler_elem = doc.createElement('bundler')
-        bundler_name_elem = doc.createElement('name')
-        bundler_name_value = doc.createTextNode(BUNDLER_NAME)
-        bundler_name_elem.appendChild(bundler_name_value)
-        bundler_version_elem = doc.createElement('version')
-        bundler_version_value = doc.createTextNode(BUNDLER_VERSION)
-        bundler_version_elem.appendChild(bundler_version_value)
-        bundler_elem.appendChild(bundler_name_elem)
-        bundler_elem.appendChild(bundler_version_elem)
-
-           # release
-
-        release_elem = doc.createElement('release')
-        release_value = doc.createTextNode(RELEASE)
-        release_elem.appendChild(release_value)
-        bundler_elem.appendChild(release_elem)
-        manifest_elem.appendChild(bundler_elem)
-
-        # machine config
-
-        machine_config_elem = doc.createElement('machine_configuration')
-        manifest_elem.appendChild(machine_config_elem)
-
-        target_arch_elem = doc.createElement('architecture')
-        target_arch_value = doc.createTextNode(target_arch)
-        target_arch_elem.appendChild(target_arch_value)
-        machine_config_elem.appendChild(target_arch_elem)
-
-        # block device mapping
-
-        if mapping:
-            block_dev_mapping_elem = \
-                doc.createElement('block_device_mapping')
-            for vname, dname in mapping.items():
-                mapping_elem = doc.createElement('mapping')
-                virtual_elem = doc.createElement('virtual')
-                virtual_value = doc.createTextNode(vname)
-                virtual_elem.appendChild(virtual_value)
-                mapping_elem.appendChild(virtual_elem)
-                device_elem = doc.createElement('device')
-                device_value = doc.createTextNode(dname)
-                device_elem.appendChild(device_value)
-                mapping_elem.appendChild(device_elem)
-                block_dev_mapping_elem.appendChild(mapping_elem)
-            machine_config_elem.appendChild(block_dev_mapping_elem)
-
-        if product_codes:
-            product_codes_elem = doc.createElement('product_codes')
-            for product_code in product_codes:
-                product_code_elem = doc.createElement('product_code')
-                product_code_value = doc.createTextNode(product_code)
-                product_code_elem.appendChild(product_code_value)
-                product_codes_elem.appendChild(product_code_elem)
-            machine_config_elem.appendChild(product_codes_elem)
-
-        # kernel and ramdisk
-
-        if kernel:
-            kernel_id_elem = doc.createElement('kernel_id')
-            kernel_id_value = doc.createTextNode(kernel)
-            kernel_id_elem.appendChild(kernel_id_value)
-            machine_config_elem.appendChild(kernel_id_elem)
-
-        if ramdisk:
-            ramdisk_id_elem = doc.createElement('ramdisk_id')
-            ramdisk_id_value = doc.createTextNode(ramdisk)
-            ramdisk_id_elem.appendChild(ramdisk_id_value)
-            machine_config_elem.appendChild(ramdisk_id_elem)
-
-        image_elem = doc.createElement('image')
-        manifest_elem.appendChild(image_elem)
-
-        # name
-
-        image_name_elem = doc.createElement('name')
-        image_name_value = \
-            doc.createTextNode(self.euca.get_relative_filename(file))
-        image_name_elem.appendChild(image_name_value)
-        image_elem.appendChild(image_name_elem)
-
-        # user
-
-        user_elem = doc.createElement('user')
-        user_value = doc.createTextNode('%s' % user)
-        user_elem.appendChild(user_value)
-        image_elem.appendChild(user_elem)
-
-        # type
-        # TODO: fixme
-
-        image_type_elem = doc.createElement('type')
-        image_type_value = doc.createTextNode('machine')
-        image_type_elem.appendChild(image_type_value)
-        image_elem.appendChild(image_type_elem)
-
-    # ancestor ami ids
-
-        if ancestor_ami_ids:
-            ancestry_elem = doc.createElement('ancestry')
-            for ancestor_ami_id in ancestor_ami_ids:
-                ancestor_id_elem = doc.createElement('ancestor_ami_id')
-                ancestor_id_value = doc.createTextNode(ancestor_ami_id)
-                ancestor_id_elem.appendChild(ancestor_id_value)
-                ancestry_elem.appendChild(ancestor_id_elem)
-            image_elem.appendChild(ancestry_elem)
-
-        # digest
-
-        image_digest_elem = doc.createElement('digest')
-        image_digest_elem.setAttribute('algorithm', 'SHA1')
-        image_digest_value = doc.createTextNode('%s' % image_digest)
-        image_digest_elem.appendChild(image_digest_value)
-        image_elem.appendChild(image_digest_elem)
-
-        # size
-
-        image_size_elem = doc.createElement('size')
-        image_size_value = doc.createTextNode('%s' % image_size)
-        image_size_elem.appendChild(image_size_value)
-        image_elem.appendChild(image_size_elem)
-
-        # bundled size
-
-        bundled_size_elem = doc.createElement('bundled_size')
-        bundled_size_value = doc.createTextNode('%s' % bundled_size)
-        bundled_size_elem.appendChild(bundled_size_value)
-        image_elem.appendChild(bundled_size_elem)
-
-        # key, iv
-
-        cloud_encrypted_key_elem = doc.createElement('ec2_encrypted_key'
-                )
-        cloud_encrypted_key_value = doc.createTextNode('%s'
-                % cloud_encrypted_key)
-        cloud_encrypted_key_elem.appendChild(cloud_encrypted_key_value)
-        cloud_encrypted_key_elem.setAttribute('algorithm', AES)
-        image_elem.appendChild(cloud_encrypted_key_elem)
-
-        user_encrypted_key_elem = doc.createElement('user_encrypted_key'
-                )
-        user_encrypted_key_value = doc.createTextNode('%s'
-                % user_encrypted_key)
-        user_encrypted_key_elem.appendChild(user_encrypted_key_value)
-        user_encrypted_key_elem.setAttribute('algorithm', AES)
-        image_elem.appendChild(user_encrypted_key_elem)
-
-        cloud_encrypted_iv_elem = doc.createElement('ec2_encrypted_iv')
-        cloud_encrypted_iv_value = doc.createTextNode('%s'
-                % cloud_encrypted_iv)
-        cloud_encrypted_iv_elem.appendChild(cloud_encrypted_iv_value)
-        image_elem.appendChild(cloud_encrypted_iv_elem)
-
-        user_encrypted_iv_elem = doc.createElement('user_encrypted_iv')
-        user_encrypted_iv_value = doc.createTextNode('%s'
-                % user_encrypted_iv)
-        user_encrypted_iv_elem.appendChild(user_encrypted_iv_value)
-        image_elem.appendChild(user_encrypted_iv_elem)
-
-        # parts
-
-        parts_elem = doc.createElement('parts')
-        parts_elem.setAttribute('count', '%s' % len(parts))
-        part_number = 0
-        for part in parts:
-            part_elem = doc.createElement('part')
-            filename_elem = doc.createElement('filename')
-            filename_value = \
-                doc.createTextNode(self.euca.get_relative_filename(part))
-            filename_elem.appendChild(filename_value)
-            part_elem.appendChild(filename_elem)
-
-            # digest
-
-            part_digest_elem = doc.createElement('digest')
-            part_digest_elem.setAttribute('algorithm', 'SHA1')
-            part_digest_value = \
-                doc.createTextNode(parts_digest[part_number])
-            part_digest_elem.appendChild(part_digest_value)
-            part_elem.appendChild(part_digest_elem)
-            part_elem.setAttribute('index', '%s' % part_number)
-            parts_elem.appendChild(part_elem)
-            part_number += 1
-        image_elem.appendChild(parts_elem)
-
-        manifest_string = doc.toxml()
-
-        if user_priv_key:
-            string_to_sign = self.get_verification_string(manifest_string)
-            signature_elem = doc.createElement('signature')
-            sha_manifest = sha()
-            sha_manifest.update(string_to_sign)
-            signature_value = doc.createTextNode('%s'
-                    % hexlify(user_priv_key.sign(sha_manifest.digest())))
-            signature_elem.appendChild(signature_value)
-            manifest_elem.appendChild(signature_elem)
-            
-        manifest_out_file.write(doc.toxml())
-        manifest_out_file.close()
-        return manifest_file
-
-    def add_excludes(self, path, excludes):
-        if self.euca.debug:
-            print 'Reading /etc/mtab...'
-        mtab_file = open('/etc/mtab', 'r')
-        while 1:
-            mtab_line = mtab_file.readline()
-            if not mtab_line:
-                break
-            mtab_line_parts = mtab_line.split(' ')
-            mount_point = mtab_line_parts[1]
-            fs_type = mtab_line_parts[2]
-            if mount_point.find(path) == 0 and fs_type \
-                not in self.img.ALLOWED_FS_TYPES:
-                if self.euca.debug:
-                    print 'Excluding %s...' % mount_point
-                excludes.append(mount_point)
-        mtab_file.close()
-        for banned in self.img.BANNED_MOUNTS:
-            excludes.append(banned)
-
-    def make_image(self, size_in_MB, excludes, prefix,
-                   destination_path, fs_type = None,
-                   uuid = None, label = None):
-        image_file = '%s.img' % prefix
-        image_path = '%s/%s' % (destination_path, image_file)
-        if not os.path.exists(destination_path):
-            os.makedirs(destination_path)
-        if self.img == 'Unsupported':
-            print >> sys.stderr, 'Platform not fully supported.'
-            raise UnsupportedException
-        self.img.create_image(size_in_MB, image_path)
-        self.img.make_fs(image_path, fs_type=fs_type, uuid=uuid, label=label)
-        return image_path
-
-    def create_loopback(self, image_path):
-        utils.check_prerequisite_command('losetup')
-        tries = 0
-        while tries < MAX_LOOP_DEVS:
-            loop_dev = subprocess.Popen(['losetup', '-f'],
-                                        stdout=subprocess.PIPE).communicate()[0].replace('\n', '')
-            if loop_dev:
-                output = subprocess.Popen(['losetup', '%s' % loop_dev, '%s'
-                                           % image_path], stdout=subprocess.PIPE,
-                                          stderr=subprocess.PIPE).communicate()
-                if not output[1]:
-                    return loop_dev
-            else:
-                print >> sys.stderr, 'Could not create loopback device. Aborting'
-                raise CommandFailed
-            tries += 1
-
-    def mount_image(self, image_path):
-        utils.check_prerequisite_command('mount')
-
-        tmp_mnt_point = '/tmp/%s' % hex(BN.rand(16))[2:6]
-        if not os.path.exists(tmp_mnt_point):
-            os.makedirs(tmp_mnt_point)
-        if self.euca.debug:
-            print 'Creating loopback device...'
-        loop_dev = self.create_loopback(image_path)
-        if self.euca.debug:
-            print 'Mounting image...'
-        subprocess.Popen(['mount', loop_dev, tmp_mnt_point],
-              stdout=subprocess.PIPE).communicate()
-        return (tmp_mnt_point, loop_dev)
-
-    def copy_to_image(self, mount_point, volume_path, excludes):
-        try:
-            utils.check_prerequisite_command('rsync')
-        except NotFoundError:
-            raise CopyError
-        rsync_cmd = ['rsync', '-aXS']
-        for exclude in excludes:
-            rsync_cmd.append('--exclude')
-            rsync_cmd.append(exclude)
-        rsync_cmd.append(volume_path)
-        rsync_cmd.append(mount_point)
-        if self.euca.debug:
-            print 'Copying files...'
-            for exclude in excludes:
-                print 'Excluding:', exclude
-
-        pipe = subprocess.Popen(rsync_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
-        output = pipe.communicate()
-        for dir in self.img.ESSENTIAL_DIRS:
-            dir_path = os.path.join(mount_point, dir)
-            if not os.path.exists(dir_path):
-                os.mkdir(dir_path)
-                if dir == 'tmp':
-                    os.chmod(dir_path, 01777)
-        self.img.make_essential_devs(mount_point)
-        mtab_file = open('/etc/mtab', 'r')
-        while 1:
-            mtab_line = mtab_file.readline()
-            if not mtab_line:
-                break
-            mtab_line_parts = mtab_line.split(' ')
-            mount_location = mtab_line_parts[1]
-            fs_type = mtab_line_parts[2]
-            if fs_type == 'tmpfs':
-                mount_location = mount_location[1:]
-                dir_path = os.path.join(mount_point, mount_location)
-                if not os.path.exists(dir_path):
-                    if self.euca.debug:
-                        print 'Making essential directory %s' \
-                            % mount_location
-                    os.makedirs(dir_path)
-        mtab_file.close()
-        if pipe.returncode:
-
-            # rsync return code 23: Partial transfer due to error
-            # rsync return code 24: Partial transfer due to vanished source files
-
-            if pipe.returncode in (23, 24):
-                print >> sys.stderr, 'Warning: rsync reports files partially copied:'
-                print >> sys.stderr, output
-            else:
-                print >> sys.stderr, 'Error: rsync failed with return code %d' \
-                    % pipe.returncode
-                raise CopyError
-
-    def unmount_image(self, mount_point):
-        utils.check_prerequisite_command('umount')
-        if self.euca.debug:
-            print 'Unmounting image...'
-        subprocess.Popen(['umount', '-d', mount_point],
-                         stdout=subprocess.PIPE).communicate()[0]
-        os.rmdir(mount_point)
-
-    def copy_volume(self, image_path, volume_path, excludes,
-                    generate_fstab, fstab_path):
-        (mount_point, loop_dev) = self.mount_image(image_path)
-        try:
-            output = self.copy_to_image(mount_point, volume_path,
-                    excludes)
-            if self.img == 'Unsupported':
-                print >> sys.stderr, 'Platform not fully supported.'
-                raise UnsupportedException
-            self.img.add_fstab(mount_point, generate_fstab, fstab_path)
-        except CopyError:
-            raise CopyError
-        finally:
-            self.unmount_image(mount_point)
-            
-    def display_error_and_exit(self, msg):
-        code = None
-        message = None
-        index = msg.find('<')
-        if index < 0:
-            print >> sys.stderr, msg
-            sys.exit(1)
-        msg = msg[index - 1:]
-        msg = msg.replace('\n', '')
-        dom = minidom.parseString(msg)
-        try:
-            error_elem = dom.getElementsByTagName('Error')[0]
-            code_elem = error_elem.getElementsByTagName('Code')[0]
-            nodes = code_elem.childNodes
-            for node in nodes:
-                if node.nodeType == node.TEXT_NODE:
-                    code = node.data
-
-            msg_elem = error_elem.getElementsByTagName('Message')[0]
-            nodes = msg_elem.childNodes
-            for node in nodes:
-                if node.nodeType == node.TEXT_NODE:
-                    message = node.data
-
-            print >> sys.stderr, '%s:' % code, message
-        except Exception:
-            print >> sys.stderr, msg
-        sys.exit(1)
-
diff --git a/euca2ools/commands/argtypes.py b/euca2ools/commands/argtypes.py
index bdffa1d..b7f15ce 100644
--- a/euca2ools/commands/argtypes.py
+++ b/euca2ools/commands/argtypes.py
@@ -34,6 +34,21 @@ from requestbuilder import EMPTY
 import sys
 
 
+def manifest_block_device_mappings(mappings_as_str):
+    mappings = {}
+    mapping_strs = mappings_as_str.split(',')
+    for mapping_str in mapping_strs:
+        if mapping_str.strip():
+            bits = mapping_str.strip().split('=')
+            if len(bits) == 2:
+                mappings[bits[0].strip()] = bits[1].strip()
+            else:
+                raise argparse.ArgumentTypeError(
+                    "invalid device mapping '{0}' (must have format "
+                    "'VIRTUAL=DEVICE')".format(mapping_str))
+    return mappings
+
+
 def ec2_block_device_mapping(map_as_str):
     '''
     Parse a block device mapping from an image registration command line.
diff --git a/euca2ools/commands/bundle/__init__.py b/euca2ools/commands/bundle/__init__.py
index d88eb0c..a48428f 100644
--- a/euca2ools/commands/bundle/__init__.py
+++ b/euca2ools/commands/bundle/__init__.py
@@ -29,7 +29,79 @@
 # POSSIBILITY OF SUCH DAMAGE.
 
 import os.path
+from euca2ools.commands.argtypes import manifest_block_device_mappings
 from requestbuilder.exceptions import ArgumentError
+from requestbuilder.mixins import FileTransferProgressBarMixin
+from requestbuilder.util import set_userregion
+
+
+class BundleCommand(BaseCommand, FileTransferProgressBarMixin):
+    ARGS = [Arg('-r', '--arch', choices=('i386', 'x86_64', 'armhf'),
+                required=True,
+                help="the image's processor architecture (required)"),
+            Arg('-c', '--cert', metavar='FILE',
+                help='file containing your X.509 certificate.'),
+            Arg('-k', '--privatekey', metavar='FILE', help='''file containing
+                the private key to sign the bundle's manifest with.  This
+                private key will also be required to unbundle the image in
+                the future.'''),
+            Arg('-u', '--user', metavar='ACCOUNT', help='your account ID'),
+            Arg('--region', dest='userregion', metavar='USER at REGION',
+                help='''use encryption keys and the account ID specified for
+                a user and/or region in configuration files'''),
+            Arg('--ec2cert', metavar='FILE', help='''file containing the
+                cloud's X.509 certificate'''),
+            Arg('--kernel', metavar='IMAGE', help='''[machine image only] ID
+                of the kernel image to associate with the bundle'''),
+            Arg('--ramdisk', metavar='IMAGE', help='''[machine image only] ID
+                of the ramdisk image to associate with the bundle'''),
+            Arg('--block-device-mappings',
+                metavar='VIRTUAL1=DEVICE1,VIRTUAL2=DEVICE2,...',
+                type=manifest_block_device_mappings,
+                help='''[machine image only] default block device mapping
+                scheme with which to launch instances of this image'''),
+            Arg('-d', '--destination', metavar='DIR', help='''location to
+                place the bundle's files (default:  dir named by TMPDIR, TEMP,
+                or TMP environment variables, or otherwise /var/tmp)'''),
+            Arg('--productcodes', metavar='CODE1,CODE2,...',
+                type=delimited_list(','),
+                help='comma-separated list of product codes'),
+            Arg('--batch', action='store_true', help=argparse.SUPPRESS)]
+
+    def configure(self):
+        BaseCommand.configure(self)
+        set_userregion(self.config, self.args.get('userregion'))
+        set_userregion(self.config, os.getenv('EUCA_REGION'))
+
+        # Get creds
+        add_bundle_creds(self.args, self.config)
+        if not self.args.get('cert'):
+            raise ArgumentError(
+                'missing certificate; please supply one with -c')
+        self.log.debug('certificate: %s', self.args['cert'])
+        if not self.args.get('privatekey'):
+            raise ArgumentError(
+                'missing private key; please supply one with -k')
+        self.log.debug('private key: %s', self.args['privatekey'])
+        if not self.args.get('ec2cert'):
+            raise ArgumentError(
+                'missing cloud certificate; please supply one with --ec2cert')
+        self.log.debug('cloud certificate: %s', self.args['ec2cert'])
+        if not self.args.get('user'):
+            raise ArgumentError(
+                'missing account ID; please supply one with --user')
+        self.log.debug('account ID: %s', self.args['user'])
+
+    def process_userregion(self, userregion):
+        if '@' in userregion:
+            user, region = userregion.split('@', 1)
+        else:
+            user = None
+            region = userregion
+        if region and self.config.current_region is None:
+            self.config.current_region = region
+        if user and self.config.current_user is None:
+            self.config.current_user = user
 
 
 def add_bundle_creds(args, config):
diff --git a/euca2ools/commands/bundle/bundleimage.py b/euca2ools/commands/bundle/bundleimage.py
index d8cc012..ed9621a 100644
--- a/euca2ools/commands/bundle/bundleimage.py
+++ b/euca2ools/commands/bundle/bundleimage.py
@@ -48,21 +48,6 @@ from requestbuilder.util import set_userregion
 import subprocess
 
 
-def manifest_block_device_mappings(mappings_as_str):
-    mappings = {}
-    mapping_strs = mappings_as_str.split(',')
-    for mapping_str in mapping_strs:
-        if mapping_str.strip():
-            bits = mapping_str.strip().split('=')
-            if len(bits) == 2:
-                mappings[bits[0].strip()] = bits[1].strip()
-            else:
-                raise argparse.ArgumentTypeError(
-                    "invalid device mapping '{0}' (must have format "
-                    "'VIRTUAL=DEVICE')".format(mapping_str))
-    return mappings
-
-
 class BundleImage(BaseCommand, FileTransferProgressBarMixin):
     DESCRIPTION = 'Prepare an image for uploading to a cloud'
     SUITE = Euca2ools
diff --git a/euca2ools/commands/bundle/bundlevol.py b/euca2ools/commands/bundle/bundlevol.py
index 9da891c..339e7b8 100644
--- a/euca2ools/commands/bundle/bundlevol.py
+++ b/euca2ools/commands/bundle/bundlevol.py
@@ -31,275 +31,100 @@
 # Author: Neil Soman neil at eucalyptus.com
 #         Mitch Garnaat mgarnaat at eucalyptus.com
 
+import argparse
 import os
 import sys
 import platform
-import euca2ools.commands.eucacommand
-from boto.roboto.param import Param
 import euca2ools.bundler
 import euca2ools.metadata
-from euca2ools.exceptions import *
+from euca2ools.commands.bundle.helpers import get_metadata, get_metadata_dict
+from euca2ools.commands.bundle.imagecreator import ImageCreator
+from euca2ools.exceptions import AWSError
+from requestbuilder import Arg, MutuallyExclusiveArgList
+from requestbuilder.exceptions import ArgumentError
+from requestbuilder.command import BaseCommand
+from requestbuilder.mixins import FileTransferProgressBarMixin
+from requestbuilder.util import set_userregion
+
 
 IMAGE_MAX_SIZE_IN_MB = euca2ools.bundler.IMAGE_MAX_SIZE / 1024 // 1024
 
-class BundleVol(euca2ools.commands.eucacommand.EucaCommand):
-    Description = 'Bundles an image for use with Eucalyptus or Amazon EC2.'
-    Options = [Param(name='size', short_name='s', long_name='size',
-                     optional=True, ptype='integer',
-                     default=IMAGE_MAX_SIZE_IN_MB,
-                     doc=('Size of the image in MB (default: {0}; recommended '
-                          'maximum: {0})').format(IMAGE_MAX_SIZE_IN_MB)),
-               Param(name='user', short_name='u', long_name='user',
-                     optional=True, ptype='string',
-                     doc="""User ID (12-digit) of the user who is
-                     bundling the image"""),
-               Param(name='cert_path', short_name='c', long_name='cert',
-                     optional=True, ptype='file',
-                     doc='Path the users PEM-encoded certificate.'),
-               Param(name='private_key_path',
-                     short_name='k', long_name='privatekey',
-                     optional=True, ptype='file',
-                     doc='Path to users PEM-encoded private key.'),
-               Param(name='all', short_name='a', long_name='all',
-                     optional=True, ptype='boolean', default=False,
-                     doc="""Bundle all directories (including
-                     mounted filesystems."""),
-               Param(name='prefix', short_name='p', long_name='prefix',
-                     optional=True, ptype='string', default='image',
-                     doc="""The prefix for the bundle image files.
-                     (default: image name)."""),
-               Param(name='no_inherit',  long_name='no-inherit',
-                     optional=True, ptype='boolean', default=False,
-                     doc='Do not add instance metadata to the bundled image.'),
-               Param(name='exclude',  short_name='e', long_name='exclude',
-                     optional=True, ptype='string', default='',
-                     doc='Comma-separated list of directories to exclude.'),
-               Param(name='kernel_id', long_name='kernel',
-                     optional=True, ptype='string',
-                     doc='ID of the kernel to be associated with the image.'),
-               Param(name='ramdisk_id', long_name='ramdisk',
-                     optional=True, ptype='string',
-                     doc='ID of the ramdisk to be associated with the image.'),
-               Param(name='product_codes', long_name='product-codes',
-                     optional=True, ptype='string',
-                     doc='Product code to be associated with the image.'),
-               Param(name='block_device_mapping',
-                     short_name='b', long_name='block-device-mapping',
-                     optional=True, ptype='string', cardinality='*',
-                     doc="""Default block device mapping for the image
-                     (comma-separated list of key=value pairs)."""),
-               Param(name='destination_path',
-                     short_name='d', long_name='destination',
-                     optional=True, ptype='string', default='/disk1',
-                     doc="""Directory to store the bundled image in.
-                     Defaults to /tmp.  Recommended."""),
-               Param(name='ec2cert_path', long_name='ec2cert',
-                     optional=True, ptype='file',
-                     doc="Path to the Cloud's X509 public key certificate."),
-               Param(name='target_architecture',
-                     short_name='r', long_name='arch',
-                     optional=True, ptype='string', default='x86_64',
-                     choices=['i386', 'x86_64', 'armhf'],
-                     doc='Target architecture for the image'),
-               Param(name='volume_path', long_name='volume',
-                     optional=True, ptype='dir', default='/',
-                     doc='Path to mounted volume to bundle.'),
-               Param(name='fstab_path', long_name='fstab',
-                     optional=True, ptype='file',
-                     doc='Path to the fstab to be bundled with image.'),
-               Param(name='generate_fstab', long_name='generate-fstab',
-                     optional=True, ptype='boolean', default=False,
-                     doc='Generate fstab to bundle in image.'),
-               Param(name='batch', long_name='batch',
-                     optional=True, ptype='boolean',
-                     doc='Run in batch mode.  For compatibility has no effect')]
 
-    def check_root(self):
-        if os.geteuid() == 0:
-            return
-        else:
+class BundleVol(BundleCommand):
+    DESCRIPTION = 'Bundles an image for use with Eucalyptus or Amazon EC2.'
+    ARGS = [Arg('-s', '--size', metavar='SIZE',
+                type=filesize, default=IMAGE_MAX_SIZE_IN_MB,
+                help="""Size of the image in MB (default: {0}; recommended
+                maximum: {0})""").format(IMAGE_MAX_SIZE_IN_MB)),
+            Arg('-p', '--prefix', default='image', help='''the file name prefix
+                to give the bundle's files (defaults to 'image')'''),
+            Arg('-a', '--all', dest="bundle_all_dirs", action='store_true',
+                help="""Bundle all directories (including
+                mounted filesystems."""),
+            MutuallyExclusiveArgList(
+                Arg('--no-inherit', dest='inherit',
+                    action='store_false', help="""Do not add instance metadata to
+                    the bundled image."""),
+                Arg('--inherit', dest='inherit', action='store_true',
+                    help=argparse.SUPPRESS)),
+            Arg('-e', '--exclude', type=delimited_list(','),
+                help='''Comma-separated list of directories to exclude.'''),
+            Arg('--volume', dest='bundle_volume', default='/',
+                help='Path to mounted volume to bundle.'),
+            Arg('--no-filter', dest='filter', default=True,
+                action='store_false', help="""Do not use the default filtered
+                files list."""),
+            MutuallyExclusiveArgList(
+                Arg('--fstab', dest='fstab',
+                    help='Path to the fstab to be bundled with image.'),
+                Arg('--generate-fstab', dest='generate_fstab',
+                    action='store_true',
+                    help='Generate fstab to bundle in image.')),
+            Arg('--batch',
+                help='Run in batch mode.  For compatibility has no effect')]
+
+    def _check_root(self):
+        if os.geteuid() != 0:
             print >> sys.stderr, 'Must be superuser to execute this command.'
-            sys.exit()
-
-    def parse_excludes(self, excludes_string):
-        excludes = []
-        if excludes_string:
-            excludes_string = excludes_string.replace(' ', '')
-            excludes = excludes_string.split(',')
-        return excludes
+            sys.exit(1)
 
-    def get_instance_metadata(self, ramdisk_id, kernel_id, block_dev_mapping):
-        md = euca2ools.metadata.MetaData()
-        product_codes = None
-        ancestor_ami_ids = None
+    def _inherit_metadata(self):
         try:
-            if not ramdisk_id:
-                try:
-                    ramdisk_id = md.get_instance_ramdisk()
-                except MetadataReadError:
-                    print >> sys.stderr, 'Unable to read ramdisk id'
-
-            if not kernel_id:
-                try:
-                    kernel_id = md.get_instance_kernel()
-                except MetadataReadError:
-                    print >> sys.stderr, 'Unable to read kernel id'
-
-            if not block_dev_mapping:
-                try:
-                    block_dev_mapping = \
-                        md.get_instance_block_device_mappings()
-                except MetadataReadError:
-                    print >> sys.stderr, 'Unable to read block device mapping'
-
+            if not self.args.get('ramdisk_id'):
+                self.args['ramdisk_id'] = get_metadata('ramdisk-id')
+            if not self.args.get('kernel_id'):
+                self.args['kernel_id'] = get_metadata('kernel-id')
+            if not self.args.get('block_dev_mapping'):
+                self.args['block_dev_mapping'] = \
+                    get_metadata_dict('block-device-mapping')
+            #
+            # Product codes and ancestor AMI ids are special cases since they
+            # aren't supported by Eucalyptus yet.
+            #
             try:
-                product_codes = md.get_instance_product_codes().split('\n'
-                        )
+                self.args['productcodes'].extend(get_metadata_list('product-codes'))
             except MetadataReadError:
-                print >> sys.stderr, 'Unable to read product codes'
-
+                print >> sys.stderr, 'Unable to read product codes.'
             try:
-                ancestor_ami_ids = md.get_ancestor_ami_ids().split('\n')
+                self.args['ancestor_ami_ids'].extend(
+                    get_metadata_list('ancestor-ami-ids'))
             except MetadataReadError:
-                print >> sys.stderr, 'Unable to read ancestor ids'
-        except IOError:
-
-            print >> sys.stderr, 'Unable to read instance metadata. Pass the --no-inherit option if you wish to exclude instance metadata.'
-            sys.exit()
-
-        return (ramdisk_id, kernel_id, block_dev_mapping, product_codes,
-                ancestor_ami_ids)
-
-
-    def add_product_codes(self, product_code_string, product_codes=None):
-        if not product_codes:
-            product_codes = []
-        product_code_values = product_code_string.split(',')
-
-        for p in product_code_values:
-            product_codes.append(p)
-
-        return product_codes
-
-    def cleanup(self, path):
-        if os.path.exists(path):
-            os.remove(path)
-
-    def get_block_devs(self, mapping_str):
-        mapping = []
-        mapping_pairs = mapping_str.split(',')
-        for m in mapping_pairs:
-            m_parts = m.split('=')
-            if len(m_parts) > 1:
-                mapping.append(m_parts[0])
-                mapping.append(m_parts[1])
-        return mapping
-
-    def main(self):
-        ancestor_ami_ids = None
-        if self.cert_path is None:
-            self.cert_path = self.get_environ('EC2_CERT')
-        if self.private_key_path is None:
-            self.private_key_path = self.get_environ('EC2_PRIVATE_KEY')
-        if self.user is None:
-            self.user = self.get_environ('EC2_USER_ID')
-        if self.ec2cert_path is None:
-            self.ec2cert_path = self.get_environ('EUCALYPTUS_CERT')
-        self.inherit = not self.no_inherit
-        excludes_string = self.exclude
-        
-        bundler = euca2ools.bundler.Bundler(self)
-        
-        self.user = self.user.replace('-', '')
-
-        if self.generate_fstab and self.fstab_path:
-            msg = '--generate-fstab and --fstab path cannot both be set.'
-            self.display_error_and_exit(msg)
-        if not self.fstab_path:
-            if platform.machine() == 'i386':
-                self.fstab_path = 'old'
-            else:
-                self.fstab_path = 'new'
-        self.check_root()
-        self.volume_path = os.path.normpath(self.volume_path)
-
-        noex='EUCA_BUNDLE_VOL_EMPTY_EXCLUDES'
-        if noex in os.environ and os.environ[noex] != "0":
-            excludes = []
-        else:
-            excludes = ['/etc/udev/rules.d/70-persistent-net.rules',
-                        '/etc/udev/rules.d/z25_persistent-net.rules']
-
-        if not self.all:
-            excludes.extend(self.parse_excludes(excludes_string))
-            bundler.add_excludes(self.volume_path, excludes)
-        if self.inherit:
-            (self.ramdisk_id, self.kernel_id, self.block_device_mapping, self.product_codes,
-             ancestor_ami_ids) = self.get_instance_metadata(self.ramdisk_id,
-                                                            self.kernel_id,
-                                                            self.block_device_mapping)
-        if self.product_codes and isinstance(self.product_codes, basestring):
-            self.product_codes = self.add_product_codes(self.product_codes)
-
-        try:
-            fsinfo = bundler.get_fs_info(self.volume_path)
-        except UnsupportedException, e:
-            print >> sys.stderr, e
+                print >> sys.stderr, 'Unable to read ancestor ids.'
+        except MetadataReadError:
+            print >> sys.stderr, 'Unable to read instance metadata.'
+            print >> sys.stderr, 'Pass the --no-inherit option if you wish to', \
+                'exclude instance metadata.'
             sys.exit(1)
-        try:
-            image_path = bundler.make_image(self.size, excludes, self.prefix,
-                                            self.destination_path,
-                                            fs_type=fsinfo['fs_type'],
-                                            uuid=fsinfo['uuid'],
-                                            label=fsinfo['label'])
 
-        except NotFoundError:
-            sys.exit(1)
-        except UnsupportedException:
-            sys.exit(1)
-        image_path = os.path.normpath(image_path)
-        if image_path.find(self.volume_path) == 0:
-            exclude_image = image_path.replace(self.volume_path, '', 1)
-            image_path_parts = exclude_image.split('/')
-            if len(image_path_parts) > 1:
-                exclude_image = \
-                    exclude_image.replace(image_path_parts[0] + '/', ''
-                        , 1)
-            excludes.append(exclude_image)
-        try:
-            bundler.copy_volume(image_path, self.volume_path, excludes,
-                                self.generate_fstab, self.fstab_path)
-        except CopyError:
-            print >> sys.stderr, 'Unable to copy files'
-            self.cleanup(image_path)
-            sys.exit(1)
-        except (NotFoundError, CommandFailed, UnsupportedException):
-            self.cleanup(image_path)
-            sys.exit(1)
-
-        image_size = bundler.check_image(image_path, self.destination_path)
-        if not self.prefix:
-            self.prefix = self.get_relative_filename(image_path)
-        try:
-            (tgz_file, sha_tar_digest) = bundler.tarzip_image(self.prefix,
-                                                              image_path,
-                                                              self.destination_path)
-        except (NotFoundError, CommandFailed):
-            sys.exit(1)
+    def main(self):
+        self._check_root()
 
-        (encrypted_file, key, iv, bundled_size) = \
-            bundler.encrypt_image(tgz_file)
-        os.remove(tgz_file)
-        (parts, parts_digest) = bundler.split_image(encrypted_file)
-        bundler.generate_manifest(self.destination_path, self.prefix,
-                                  parts, parts_digest, image_path,
-                                  key, iv, self.cert_path, self.ec2cert_path,
-                                  self.private_key_path, self.target_architecture,
-                                  image_size, bundled_size,
-                                  sha_tar_digest, self.user, self.kernel_id,
-                                  self.ramdisk_id, self.block_device_mapping,
-                                  self.product_codes, ancestor_ami_ids)
-        os.remove(encrypted_file)
+        if self.args.get('inherit') is True:
+            self._inherit_metadata()
+        
+        # No need to check existence.
+        # Requirement check is in BundleCommand's configure method.
+        self.args['user'] = self.args.get('user').replace('-', '')
 
-    def main_cli(self):
-        self.main()
+        image_file = ImageCreator(self.args).run()
+        print "Image Created: ", image_file
diff --git a/euca2ools/commands/bundle/helpers.py b/euca2ools/commands/bundle/helpers.py
index e49542d..3cb370f 100644
--- a/euca2ools/commands/bundle/helpers.py
+++ b/euca2ools/commands/bundle/helpers.py
@@ -30,11 +30,17 @@
 
 from euca2ools.commands.walrus.getobject import GetObject
 from euca2ools.commands.walrus.listbucket import ListBucket
+from euca2ools.exceptions import MetadataReadError
 import os
+import requests
 import sys
+from urlparse import urljoin
 from xml.dom import minidom
 
 
+METADATA_URL = 'http://169.254.169.254/latest/meta-data/'
+
+
 def get_manifest_parts(manifest, bucket=None):
     """Gets a list object containing the filenames of parts in the manifest.
     Returns a list of parts contained in the manifest.
@@ -86,3 +92,52 @@ def download_files(bucket, keys, directory, **kwargs):
     paths = [os.path.join(bucket, key) for key in keys]
     kwargs.update(paths=paths, opath=directory)
     GetObject(**kwargs).main()
+
+
+def check_metadata():
+    """Check if instance metadata is available."""
+    if not requests.get(METADATA_URL).ok:
+        raise MetadataReadError
+
+
+def get_metadata(*paths):
+    """Get a single metadata value.
+    Returns a string containing the value of the metadata key.
+    :param paths: A variable number of items to be joined together as segments
+    of the metadata url.
+    """
+    url = METADATA_URL
+    if paths:
+        url = urljoin(url, "/".join(paths))
+    response = requests.get(url)
+    if response.ok:
+        return response.content
+    else:
+        raise MetadataReadError
+
+
+def get_metadata_list(*paths):
+    """Get a list of metadata values.
+    Returns a list containing the values of the metadata key.
+    :param paths: A variable number of items to be joined together as segments
+    of the metadata url.
+    """
+    return get_metadata(*paths).split('\n')
+
+
+def get_metadata_dict(*paths):
+    """Get a dict of metadata values.
+    Returns a dict containing the values of the metadata sub-keys.
+    :param paths: A variable number of items to be joined together as segments
+    of the metadata url.
+    """
+    items = get_metadata_list(*paths)
+    return dict((item, get_metadata(*(list(paths) + [item]))) \
+			for item in items)
+
+def parse_block_device_mapping_arg(arg):
+    """Parses the bundle argument string for block device mappings.
+    Returns a dict of block device mappings.
+    :param arg: The argument string for block device mappings.
+    """
+    return dict(pair.split('=') for pair in mapping.split(','))
diff --git a/euca2ools/commands/bundle/imagecreator.py b/euca2ools/commands/bundle/imagecreator.py
new file mode 100644
index 0000000..b42be78
--- /dev/null
+++ b/euca2ools/commands/bundle/imagecreator.py
@@ -0,0 +1,390 @@
+# Software License Agreement (BSD License)
+#
+# Copyright (c) 2009-2013, Eucalyptus Systems, Inc.
+# All rights reserved.
+#
+# Redistribution and use of this software in source and binary forms, with or
+# without modification, are permitted provided that the following conditions
+# are met:
+#
+#   Redistributions of source code must retain the above
+#   copyright notice, this list of conditions and the
+#   following disclaimer.
+#
+#   Redistributions in binary form must reproduce the above
+#   copyright notice, this list of conditions and the
+#   following disclaimer in the documentation and/or other
+#   materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+
+from euca2ools.utils import execute, check_command, sanitize_path
+from euca2ools.utils import mkdtemp_for_large_files as mkdtemp
+import os
+import sys
+import platform
+import shutil
+
+
+NO_EXCLUDE_ENVAR = 'EUCA_BUNDLE_VOL_EMPTY_EXCLUDES'
+BLKID_TAGS = ('LABEL', 'TYPE', 'UUID')
+ALLOWED_FS_TYPES = ('ext2', 'ext3', 'xfs', 'jfs', 'reiserfs')
+EXCLUDED_DIRS = ('/dev', '/media', '/mnt', '/proc',
+                 '/sys', '/cdrom', '/tmp')
+SYSTEM_DIRS = ('proc', 'tmp', 'dev', 'mnt', 'sys')
+DEVICE_NODES = (('/dev/console', 'c', '5', '1'),
+                ('/dev/full', 'c', '1', '7'),
+                ('/dev/null', 'c', '1', '3'),
+                ('/dev/zero', 'c', '1', '5'),
+                ('/dev/tty', 'c', '5', '0'),
+                ('/dev/tty0', 'c', '4', '0'),
+                ('/dev/tty1', 'c', '4', '1'),
+                ('/dev/tty2', 'c', '4', '2'),
+                ('/dev/tty3', 'c', '4', '3'),
+                ('/dev/tty4', 'c', '4', '4'),
+                ('/dev/tty5', 'c', '4', '5'),
+                ('/dev/xvc0', 'c', '204', '191'))
+FSTAB_BODY_TEMPLATE = dict(
+    i386="""/dev/sda1\t/\text3\tdefaults 1 1
+/dev/sdb\t/mnt\text3\tdefaults 0 0
+none\t/dev/pts\tdevpts\tgid=5,mode=620 0 0
+none\t/proc\tproc\tdefaults 0 0
+none\t/sys\tsysfs\tdefaults 0 0""",
+    x86_64="""/dev/sda1\t/\text3\tdefaults,errors=remount-ro 0 0
+/dev/sda2\t/mnt\text3\tdefaults\t0 0
+/dev/sda3\tswap\tswap\tdefaults\t0 0
+proc\t/proc\tproc\tdefaults\t0 0
+devpts\t/dev/pts\tdevpts\tgid=5,mode=620 0 0""",
+    )
+FSTAB_HEADER_TEMPLATE = """#
+#
+# /etc/fstab
+#
+# Created by euca-bundle-vol on {0}
+#
+#"""
+FSTAB_TIME_FORMAT = "%a %b %d %H:%M:%S %Y"
+DEFAULT_PATTERN_EXCLUDES = [
+    '*/#*#',
+    '*/.#*',
+    '*.sw',
+    '*.swo',
+    '*.swp',
+    '*~',
+    '*.pem',
+    '*.priv',
+    '*id_rsa*',
+    '*id_dsa*',
+    '*.gpg',
+    '*.jks',
+    '*/.ssh/authorized_keys',
+    '*/.bash_history',
+]
+DEFAULT_FS_EXCLUDES = [
+    '/dev',
+    '/media',
+    '/mnt',
+    '/proc',
+    '/sys',
+]
+
+
+class VolumeSync(object):
+    def __init__(self, volume, image):
+        self.mount = mkdtemp()
+        self.excludes = []
+        self.filter = False
+        self.fstab = None
+        self.generate_fstab = False
+        self.image = image
+        self.includes = []
+        self.volume = volume
+        self.bundle_all_dirs = False
+
+    def run(self):
+        #
+        # This is where ALL the magic happens!
+        #
+        self._sync_files()
+        self._populate_system_dirs()
+        self._populate_device_nodes()
+        self._populate_tmpfs_mounts()
+
+        #
+        # If an fstab file was specified we will replace the current
+        # fstab on the created image with the file supplied. If the
+        # user has told us to generate a new fstab file, then we will
+        # create a new one based on the architecture of the target
+        # system. If neither are specified then we do nothing here and
+        # the user keeps their fstab from the original volume.
+        #
+        if self.fstab:
+            with open(fstab, 'r') as fp:
+                self._install_fstab(fp.read())
+        elif self.generate_fstab:
+            self._install_generated_fstab()
+
+    def exclude(self, exclude):
+        if exclude:
+            if isinstance(exclude, list):
+                self.excludes.extend(exclude)
+            else:
+                self.excludes.append(exclude)
+
+    def include(self, include):
+        if include:
+            if isinstance(include, list):
+                self.includes.extend(include)
+            else:
+                self.includes.append(include)
+
+    def bundle_all_dirs(self):
+        self.bundle_all_dirs = True
+
+    def filter_files(self):
+        self.filter = True
+
+    def install_fstab_from_file(self, fstab):
+        if not os.path.exists(fstab):
+            raise ArgumentError(
+                'fstab file '{0}' does not exist.'.format(fstab))
+        self.fstab = fstab
+
+    def generate_fstab(self):
+        self.generate_fstab = True
+
+    def _update_exclusions(self):
+        #
+        # Here we update the following sync exclusions:
+        #
+        # 1. Exclude our disk image we are syncing to.
+        # 2. Exclude our mount point for our image.
+        # 3. Exclude filesystems that are not allowed.
+        # 4. Exclude special system directories.
+        # 5. Exclude file patterns for privacy.
+        # 6. Exclude problematic udev rules.
+        #
+        if self.image.find(self.volume) == 0:
+            self.exclude(os.path.dirname(self.image))
+        if self.mount(self.volume) == 0:
+            self.exclude(self.mount)
+        if not self.bundle_all_dirs:
+            self._add_mtab_exclusions()
+        self.excludes.extend(EXCLUDED_DIRS)
+        if self.filter:
+            self.excludes.extend(DEFAULT_PATTERN_EXCLUDES)
+        if os.environ.get(NO_EXCLUDE_ENVAR, "0") == "0":
+            self.excludes.extend(
+                ['/etc/udev/rules.d/70-persistent-net.rules',
+                 '/etc/udev/rules.d/z25_persistent-net.rules'])
+
+    def _add_mtab_exclusions(self):
+        with open('/etc/mtab', 'r') as mtab:
+            for line in mtab.readlines():
+                (mount, type) = line.split()[1:3]
+                #
+                # If we find that a mount in our volume's mtab file is
+                # and shares a parent directory with the volume we will
+                # check if the filesystem for the mount is not allowed
+                # (e.g., NFS) and we will exclude it. This will not happen
+                # if you have chosen the 'all' option.
+                #
+                if mount.find(self.volume) == 0 and type \
+                    not in ALLOWED_FS_TYPES:
+                    self.excludes.append(mount)
+
+    def _populate_tmpfs_mounts(self):
+        with open('/etc/mtab', 'r') as mtab:
+            for line in mtab.readlines():
+                (mount, type) = line.split()[1:3]
+                if type == 'tmpfs':
+                    fullpath = os.path.join(self.mount, mount[1:])
+                    if not os.path.exists(fullpath):
+                        os.makedirs(fullpath)
+
+    def _populate_device_nodes(self):
+        template = 'mknod {0} {1} {2} {3}'
+        for node in DEVICE_NODES:
+            cmd = template.format(node[0], *node[1:])
+            execute(cmd)
+
+    def _populate_system_dirs(self):
+        for sysdir in SYSTEM_DIRS:
+            fullpath = os.path.join(self.mount, sysdir)
+            if not os.path.exists(fullpath):
+                os.makedirs(fullpath)
+                if sysdir == 'tmp':
+                    os.chmod(fullpath, 01777)
+
+    def _install_generated_fstab(self):
+        self._install_fstab(_generate_fstab_content())
+
+    def _install_fstab(self, content):
+        curr_fstab = os.path.join(self.mount, 'etc', 'fstab')
+        if os.path.exists(curr_fstab):
+            shutil.copyfile(curr_fstab, curr_fstab + '.old')
+            os.remove(curr_fstab)
+        with open(os.path.join(mount, 'etc', 'fstab'), 'wb') as fp:
+            fp.write(content)
+
+    def _sync_files(self):
+        check_command('rsync')
+        cmd = ['rsync', '-aXS']
+        self._update_exclusions()
+        for exclude in self.excludes:
+            cmd.extend(['--exclude', exclude])
+        for include in self.includes:
+            cmd.extend(['--include', include])
+        cmd.extend([os.path.join(self.volume, '*'), self.dest])
+        (out, _, retval) = execute(cmd)
+
+        #
+        # rsync return code 23: Partial transfer due to error
+        # rsync return code 24: Partial transfer due to vanished source files
+        #
+        if retval in (23, 24):
+            print >> sys.stderr, 'Warning: rsync reports files partially copied:'
+            print >> sys.stderr, out
+        else:
+            print >> sys.stderr, 'Error: rsync failed with return code {0}'.format(retval)
+            raise CopyError
+
+    def _sync_disks(self):
+        execute('sync')
+
+    def mount(self):
+        self._sync_disks()
+        if not os.path.exists(self.mount):
+            os.makedirs(self.mount)
+        check_command('mount')
+        cmd = ['mount', '-o', 'loop', self.image, self.mount]
+        execute(cmd)
+
+    def unmount(self):
+        self._sync_disks()
+        utils.check_command('umount')
+        cmd = ['umount', '-d', self.dest]
+        execute(cmd)
+        if os.path.exists(self.mount):
+            os.remove(self.mount)
+
+    def __enter__(self):
+        self.mount()
+        return self
+
+    def __exit__(self, type, value, traceback):
+        self.unmount()
+
+
+class ImageCreator(object):
+    def __init__(self, **kwargs):
+        self.volume = sanitize_path(kwargs.get('volume', '/'))
+        self.fstab = kwargs.get('fstab')
+        self.generate_fstab = kwargs.get('generate_fstab', False)
+        self.excludes = kwargs.get('exclude', [])
+        self.includes = kwargs.get('include', [])
+        self.bundle_all_dirs = kwargs.get('bundle_all_dirs', False)
+        self.size = kwargs.get('size')
+        self.prefix = kwargs.get('prefix')
+        self.image = os.path.join(mkdtemp(), '{0}.img'.format(self.prefix))
+        self.fs = {}
+
+    def run(self):
+        #
+        # Prepare a disk image
+        # 
+        self._create_raw_diskimage()
+        self._populate_filesystem_info()
+        self._make_filesystem(**self.fs)
+        #
+        # Inside the VolumeSync context we will mount our image
+        # as a loop device. If for any reason a failure occurs
+        # the device will automatically be unmounted and cleaned up.
+        #
+        with VolumeSync(self.volume, self.image) as volsync:
+            if self.fstab:
+                volsync.install_fstab_from_file(self.fstab)
+            elif self.generate_fstab:
+                volsync.generate_fstab()
+            if self.filter:
+                volsync.filter_files()
+            volsync.exclude(self.excludes)
+            volsync.include(self.includes)
+            volsync.run()
+
+        return self.image
+
+    def _create_raw_diskimage(self):
+        template = 'dd if=/dev/zero of={0} count=1 bs=1M seek {1}'
+        cmd = template.format(self.image, self.args.get('size') - 1)
+        execute(cmd)
+
+    def _populate_filesystem_info(self):
+        #
+        # Create a temporary device node for the volume we're going
+        # to copy. We'll use it to get information about the filesystem.
+        #
+        st_dev = os.stat(self.volume).st_dev
+        devid = os.makedev(os.major(st_dev), os.minor(st_dev))
+        directory = mkdtemp()
+        devnode = os.path.join(directory, 'rootdev')
+        os.mknod(devnode, 0400 | stat.S_IFBLK, devid)
+        template = 'blkid -s {0} -ovalue {1}'
+        try:
+            for tag in BLKID_TAGS:
+                cmd = template.format(tag, devnode)
+                try:
+                    (out, _, _) = execute(cmd)
+                    self.fs[tag.lower()] = out.rstrip()
+                except CommandFailed:
+                    pass
+        finally:
+            os.remove(devnode)
+            os.rmdir(directory)
+   
+    def _make_filesystem(self, type='ext3', uuid=None, label=None):
+        mkfs_cmd = 'mkfs.{0}'.format(type)
+        tunefs = None
+
+        if type.startswith('ext'):
+            mkfs = [mkfs_cmd, '-F', self.image]
+            if uuid:
+                tunefs = ['tune2fs', '-U', uuid, self.image]
+        elif type == 'xfs':
+            mkfs = [mkfs_cmd, self.image]
+            tunefs = ['xfs_admin', '-U', uuid, self.image]
+        elif type == 'btrfs':
+            mkfs = [mkfs_cmd, self.image]
+            if uuid:
+                raise UnsupportedException("btrfs with uuid not supported")
+        else:
+            raise UnsupportedException("unsupported fs {0}".format(type))
+
+        if label:
+            mkfs.extend(['-L', label])
+        utils.check_command(mkfs)
+        execute(mkfs)
+        if tunefs:
+            utils.check_command(tunefs)
+            execute(tunefs)
+
+
+def _generate_fstab_content(arch=platform.machine()):
+    if arch in FSTAB_BODY_TEMPLATE:
+        return "\n".join(FSTAB_HEADER_TEMPLATE.format(
+                time.strftime(FSTAB_TIME_FORMAT)),
+                         FSTAB_BODY_TEMPLATE.get(arch))
+    else:
+        raise UnsupportedException(
+            "platform architecture {0} not supported".format(arch))
diff --git a/euca2ools/exceptions.py b/euca2ools/exceptions.py
index d56bf57..499c4ed 100644
--- a/euca2ools/exceptions.py
+++ b/euca2ools/exceptions.py
@@ -99,8 +99,10 @@ class CopyError(EucaError):
 
 class MetadataReadError(EucaError):
 
-    def __init__(self):
+    def __init__(self, metadata_type=None):
         self._message = 'Unable to read metadata'
+        if metadata_type:
+            self._message += ' for {0}'.format(metadata_type)
 
 class NotFoundError(EucaError):
 
@@ -111,14 +113,23 @@ class UnsupportedException(EucaError):
 
     def __init__(self, msg=None):
         if msg:
-            self._message = 'Not supported: %s' % msg
+            self._message = 'Not supported: {0}'.format(msg)
         else:
             self._message = 'Not supported'
 
 class CommandFailed(EucaError):
 
-    def __init__(self):
+    def __init__(self, cmd=None, err=None):
         self._message = 'Command failed'
+        self.err = err
+        if cmd:
+            self._message += ': {0}'.format(cmd)
+
+    @property
+    def message(self):
+        if err:
+            return "\n".join(self._message, self.err)
+        return self._message
 
 class ConnectionFailed(EucaError):
 
diff --git a/euca2ools/image.py b/euca2ools/image.py
deleted file mode 100644
index 76120f0..0000000
--- a/euca2ools/image.py
+++ /dev/null
@@ -1,188 +0,0 @@
-# Software License Agreement (BSD License)
-#
-# Copyright (c) 2009-2011, Eucalyptus Systems, Inc.
-# All rights reserved.
-#
-# Redistribution and use of this software in source and binary forms, with or
-# without modification, are permitted provided that the following conditions
-# are met:
-#
-#   Redistributions of source code must retain the above
-#   copyright notice, this list of conditions and the
-#   following disclaimer.
-#
-#   Redistributions in binary form must reproduce the above
-#   copyright notice, this list of conditions and the
-#   following disclaimer in the documentation and/or other
-#   materials provided with the distribution.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
-# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-# POSSIBILITY OF SUCH DAMAGE.
-#
-# Author: Neil Soman neil at eucalyptus.com
-
-import sys
-import os
-import shutil
-import subprocess
-import utils
-
-IMAGE_IO_CHUNK = 10 * 1024
-IMAGE_SPLIT_CHUNK = IMAGE_IO_CHUNK * 1024
-MAX_LOOP_DEVS = 256
-
-class LinuxImage:
-
-    ALLOWED_FS_TYPES = ['ext2', 'ext3', 'xfs', 'jfs', 'reiserfs']
-    BANNED_MOUNTS = ['/dev', '/media', '/mnt', '/proc',
-                     '/sys', '/cdrom', '/tmp']
-    ESSENTIAL_DIRS = ['proc', 'tmp', 'dev', 'mnt', 'sys']
-    ESSENTIAL_DEVS = [[os.path.join('dev', 'console'), 'c', '5', '1'],
-                      [os.path.join('dev', 'full'), 'c', '1', '7'],
-                      [os.path.join('dev', 'null'), 'c', '1', '3'],
-                      [os.path.join('dev', 'zero'), 'c', '1', '5'],
-                      [os.path.join('dev', 'tty'), 'c', '5', '0'],
-                      [os.path.join('dev', 'tty0'), 'c', '4', '0'],
-                      [os.path.join('dev', 'tty1'), 'c', '4', '1'],
-                      [os.path.join('dev', 'tty2'), 'c', '4', '2'],
-                      [os.path.join('dev', 'tty3'), 'c', '4', '3'],
-                      [os.path.join('dev', 'tty4'), 'c', '4', '4'],
-                      [os.path.join('dev', 'tty5'), 'c', '4', '5'],
-                      [os.path.join('dev', 'xvc0'), 'c', '204', '191']]
-    MAKEFS_CMD = 'mkfs.ext3'
-    NEW_FSTAB = ['/dev/sda1\t/\text3\tdefaults 1 1',
-                 '/dev/sdb\t/mnt\text3\tdefaults 0 0',
-                 'none\t/dev/pts\tdevpts\tgid=5,mode=620 0 0',
-                 'none\t/proc\tproc\tdefaults 0 0',
-                 'none\t/sys\tsysfs\tdefaults 0 0']
-
-    OLD_FSTAB = ['/dev/sda1\t/\text3\tdefaults,errors=remount-ro 0 0',
-                 '/dev/sda2\t/mnt\text3\tdefaults\t0 0',
-                 '/dev/sda3\tswap\tswap\tdefaults\t0 0',
-                 'proc\t/proc\tproc\tdefaults\t0 0',
-                 'devpts\t/dev/pts\tdevpts\tgid=5,mode=620  0 0']
-
-    def __init__(self, debug=False):
-        self.debug = debug
-
-    def create_image(self, size_in_MB, image_path):
-        dd_cmd = ['dd']
-        dd_cmd.append('if=/dev/zero')
-        dd_cmd.append('of=%s' % image_path)
-        dd_cmd.append('count=1')
-        dd_cmd.append('bs=1M')
-        dd_cmd.append('seek=%s' % (size_in_MB - 1))
-        if self.debug:
-            print 'Creating disk image...', image_path
-        subprocess.Popen(dd_cmd, subprocess.PIPE).communicate()[0]
-
-    def make_fs(self, image_path, fs_type = None, uuid = None, label = None):
-        mkfs_prog = self.MAKEFS_CMD
-        if fs_type:
-            mkfs_prog = "mkfs.%s" % fs_type
-        else:
-            fs_type = "ext3"
-
-        tunecmd = [ ]
-        if fs_type.startswith("ext"):
-            mkfs = [ mkfs_prog , '-F', image_path ]
-            if uuid:
-                tunecmd = [ 'tune2fs', '-U', uuid, image_path ]
-            if label: mkfs.extend([ '-L', label ])
-        elif fs_type == "xfs":
-            mkfs = [ mkfs_prog , image_path ]
-            if label: mkfs.extend([ '-L', label ])
-            tunecmd = [ 'xfs_admin', '-U', uuid, image_path ]
-        elif fs_type == "btrfs":
-            if uuid: raise(UnsupportedException("btrfs with uuid not supported"))
-            if label: mkfs.extend([ '-L', label ])
-        else:
-            raise(UnsupportedException("unsupported fs %s" % fs_type))
-
-        utils.check_prerequisite_command(mkfs_prog)
-
-        if self.debug:
-            print 'Creating filesystem with %s' % mkfs
-
-        makefs_cmd = subprocess.Popen(mkfs,subprocess.PIPE).communicate()[0]
-
-        if len(tunecmd) > 0:
-            utils.check_prerequisite_command(tunecmd[0])
-            tune_cmd = subprocess.Popen(tunecmd,subprocess.PIPE).communicate()[0]
-
-    def add_fstab(self, mount_point, generate_fstab, fstab_path):
-        if not fstab_path:
-            return
-        fstab = None
-        if fstab_path == 'old':
-            if not generate_fstab:
-                return
-            fstab = '\n'.join(self.OLD_FSTAB)
-        elif fstab_path == 'new':
-            if not generate_fstab:
-                return
-            fstab = '\n'.join(self.NEW_FSTAB)
-
-        etc_file_path = os.path.join(mount_point, 'etc')
-        fstab_file_path = os.path.join(etc_file_path, 'fstab')
-        if not os.path.exists(etc_file_path):
-            os.mkdir(etc_file_path)
-        else:
-            if os.path.exists(fstab_file_path):
-                fstab_copy_path = fstab_file_path + '.old'
-                shutil.copyfile(fstab_file_path, fstab_copy_path)
-
-        if self.debug:
-            print 'Updating fstab entry'
-        fstab_file = open(fstab_file_path, 'w')
-        if fstab:
-            fstab_file.write(fstab)
-        else:
-            orig_fstab_file = open(fstab_path, 'r')
-            while 1:
-                data = orig_fstab_file.read(IMAGE_IO_CHUNK)
-                if not data:
-                    break
-                fstab_file.write(data)
-            orig_fstab_file.close()
-        fstab_file.close()
-
-    def make_essential_devs(self, image_path):
-        for entry in self.ESSENTIAL_DEVS:
-            cmd = ['mknod']
-            entry[0] = os.path.join(image_path, entry[0])
-            cmd.extend(entry)
-            subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()
-
-class SolarisImage:
-
-    ALLOWED_FS_TYPES = ['ext2', 'ext3', 'xfs', 'jfs', 'reiserfs']
-    BANNED_MOUNTS = ['/dev', '/media', '/mnt', '/proc',
-                     '/sys', '/cdrom', '/tmp']
-    ESSENTIAL_DIRS = ['proc', 'tmp', 'dev', 'mnt', 'sys']
-
-    def __init__(self, debug=False):
-        self.debug = debug
-
-    def create_image(self, size_in_MB, image_path):
-        print >> sys.stderr, 'Sorry. Solaris not supported yet'
-        raise UnsupportedException
-
-    def make_fs(self, image_path, fstype = None, uuid = None, label = None):
-        print >> sys.stderr, 'Sorry. Solaris not supported yet'
-        raise UnsupportedException
-
-    def make_essential_devs(self, image_path):
-        print >> sys.stderr, 'Sorry. Solaris not supported yet'
-        raise UnsupportedException
-
-
diff --git a/euca2ools/metadata.py b/euca2ools/metadata.py
deleted file mode 100644
index a3ae73f..0000000
--- a/euca2ools/metadata.py
+++ /dev/null
@@ -1,64 +0,0 @@
-# Software License Agreement (BSD License)
-#
-# Copyright (c) 2009-2011, Eucalyptus Systems, Inc.
-# All rights reserved.
-#
-# Redistribution and use of this software in source and binary forms, with or
-# without modification, are permitted provided that the following conditions
-# are met:
-#
-#   Redistributions of source code must retain the above
-#   copyright notice, this list of conditions and the
-#   following disclaimer.
-#
-#   Redistributions in binary form must reproduce the above
-#   copyright notice, this list of conditions and the
-#   following disclaimer in the documentation and/or other
-#   materials provided with the distribution.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
-# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-# POSSIBILITY OF SUCH DAMAGE.
-#
-# Author: Neil Soman neil at eucalyptus.com
-#         Mitch Garnaat mgarnaat at eucalyptus.com
-
-from boto.utils import get_instance_metadata
-from exceptions import MetadataReadError
-
-class MetaData(object):
-
-    def __init__(self):
-        self.md = get_instance_metadata()
-
-    def get_instance_metadata(self, key):
-        if self.md is None:
-            raise IOError('failed to contact metadata service')
-        try:
-            return self.md[key]
-        except KeyError:
-            raise MetadataReadError
-
-    def get_instance_ramdisk(self):
-        return self.get_instance_metadata('ramdisk-id')
-
-    def get_instance_kernel(self):
-        return self.get_instance_metadata('kernel-id')
-
-    def get_instance_product_codes(self):
-        return self.get_instance_metadata('product-codes')
-
-    def get_ancestor_ami_ids(self):
-        return self.get_instance_metadata('ancestor-ami-ids')
-
-    def get_instance_block_device_mappings(self):
-        return self.get_instance_metadata('block-device-mapping')
-
diff --git a/euca2ools/utils.py b/euca2ools/utils.py
index 1b046a4..099f431 100644
--- a/euca2ools/utils.py
+++ b/euca2ools/utils.py
@@ -32,24 +32,67 @@
 #         Mitch Garnaat mgarnaat at eucalyptus.com
 
 import base64
-import os.path
-import subprocess
+import os
+from subprocess import Popen, PIPE
 import sys
 import tempfile
 from euca2ools import exceptions, __version__
 
-def check_prerequisite_command(command):
-    cmd = [command]
+
+def execute(command, raise_exception=True, exception=None, success=0):
+    """Execute a process with optional arguments.
+    Returns a tuple containing stdout, stderr and return code.
+    :param command: A string or list of arguments to execute.
+    :param raise_exception: (optional) True if we should throw an exception
+    when the returncode is not equal to the success value.
+    :param exception: (optional) Exception object to use when raising an
+    exception. If this is not set a CommandFailed exception will be used.
+    :param success: (optional) Set the returncode that signifies success.
+    """
+    if isinstance(command, basestring):
+        command = command.split()
+    proc = Popen(command, stdout=PIPE, stderr=PIPE)
+    (out, err) = proc.communicate()
+    if proc.returncode != success:
+        if raise_exception:
+            if exception:
+                raise exception
+            else:
+                raise exceptions.CommandFailed(" ".join(command), err)
+    return (out, err, proc.returncode)
+
+
+def check_command(command, **kwargs):
+    """Check if an executable exists on the current system. If an exception
+    is not raised, it is assumed that the command exists.
+    :param command: The executable to check existence of.
+    :param kwargs: (optional) Arguments to pass along to the execute method.
+    """
     try:
-        output = subprocess.Popen(cmd, stdout=subprocess.PIPE,
-                                  stderr=subprocess.PIPE).communicate()
-    except OSError, e:
-        error_string = '%s' % e
-        if 'No such' in error_string:
-            print >> sys.stderr, 'Command %s not found. Is it installed?' % command
+        if isinstance(command, basestring):
+            command = command.split()
+        if command:
+            execute(command[0], **kwargs)
+        else:
+            raise ArgumentError("No executable supplied to check command.")
+    except OSError as err:
+        if 'No such' in err.message:
+            print >> sys.stderr, 'Command {0} not found. Is it installed?'.format(command)
             raise exceptions.NotFoundError
         else:
-            raise OSError(e)
+            raise
+
+
+def sanitize_path(path):
+    """Make a fully expanded and absolute path for us to work with.
+    Returns a santized path string.
+    :param path: The path string to sanitize.
+    """
+    return os.path.abspath(
+        os.path.expandvars(
+            os.path.expanduser(
+                os.path.normpath(path))))
+
 
 def parse_config(config, dict, keylist):
     fmt = ''
@@ -61,9 +104,9 @@ def parse_config(config, dict, keylist):
     cmd = ['bash', '-ec', ". '%s' >/dev/null; printf '%s' %s"
            % (config, fmt, str)]
 
-    handle = subprocess.Popen(cmd, stderr=subprocess.PIPE, stdout=subprocess.PIPE)
-    (stdout, stderr) = handle.communicate()
-    if handle.returncode != 0:
+    (out, err, retval) = execute(cmd, exception=False)
+
+    if retval != 0:
         raise exceptions.ParseError('Parsing config file %s failed:\n\t%s'
                          % (config, stderr))
 
@@ -72,6 +115,7 @@ def parse_config(config, dict, keylist):
         if values[i] != '':
             dict[keylist[i]] = values[i]
 
+
 def print_instances(instances, nil=""):
 
     # I was not able to correctly identify fields with an 'xx' below the
@@ -120,6 +164,7 @@ def print_instances(instances, nil=""):
                 print '\t'.join(('TAG', 'instance', instance.id, tag,
                                  instance.tags[tag]))
 
+
 def print_version_if_necessary():
     """
     If '--version' appears in sys.argv then print the version and exit

-- 
managing cloud instances for Eucalyptus



More information about the pkg-eucalyptus-commits mailing list