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

Garrett Holmstrom gholms at fedoraproject.org
Sun Jun 16 02:31:00 UTC 2013


The following commit has been merged in the master branch:
commit 0445c7051ccf0c34c5ce48d0aa38a4e1e4e4f428
Author: Garrett Holmstrom <gholms at fedoraproject.org>
Date:   Fri Apr 12 15:20:21 2013 -0700

    WIP on bundler (getting close, but no EC2 support)

diff --git a/bin/euca-bundle-image b/bin/euca-bundle-image
index 59ff2dc..8dd046b 100755
--- a/bin/euca-bundle-image
+++ b/bin/euca-bundle-image
@@ -1,42 +1,6 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-
-# 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
+#!/usr/bin/python -tt
 
 import euca2ools.commands.bundle.bundleimage
 
 if __name__ == '__main__':
-    cmd = euca2ools.commands.bundle.bundleimage.BundleImage()
-    cmd.main_cli()
-
+    euca2ools.commands.bundle.bundleimage.BundleImage.run()
diff --git a/euca2ools/commands/argtypes.py b/euca2ools/commands/argtypes.py
index da0900a..bdffa1d 100644
--- a/euca2ools/commands/argtypes.py
+++ b/euca2ools/commands/argtypes.py
@@ -99,6 +99,17 @@ def ec2_block_device_mapping(map_as_str):
     return map_dict
 
 
+def filesize(size):
+    suffixes = 'kmgt'
+    s_size = size.lower().rstrip('b')
+    if len(s_size) > 0 and s_size[-1] in suffixes:
+        multiplier = 1024 ** (suffixes.find(s_size[-1]) + 1)
+        s_size = s_size[:-1]
+    else:
+        multiplier = 1
+    return multiplier * int(s_size)
+
+
 def vpc_interface(iface_as_str):
     '''
     Nine-part VPC network interface definition:
diff --git a/euca2ools/commands/bundle/bundleimage2.py b/euca2ools/commands/bundle/bundle.py
similarity index 67%
rename from euca2ools/commands/bundle/bundleimage2.py
rename to euca2ools/commands/bundle/bundle.py
index 692ec75..d5ed85f 100644
--- a/euca2ools/commands/bundle/bundleimage2.py
+++ b/euca2ools/commands/bundle/bundle.py
@@ -1,12 +1,37 @@
-#!/usr/bin/python -tt
+# Software License Agreement (BSD License)
+#
+# Copyright (c) 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.
 
-import argparse
-## TODO:  remove the stuff above and add the regular license header
 import hashlib
 import multiprocessing
 import itertools
 import os.path
-import progressbar
 import random
 import subprocess
 import sys
@@ -14,20 +39,28 @@ import tarfile
 import threading
 import time
 
-
-## TODO:  make the progress bar optional
+# Note:  show_progress=True in a couple methods imports progressbar
+## TODO:  document that fact
 
 
 class Bundle(object):
-    DEFAULT_PART_SIZE = 1024 * 1024  ## FIXME
+    DEFAULT_PART_SIZE = 10 * 1024 * 1024
 
     def __init__(self):
+        self.digest = None
+        self.digest_algorithm = 'SHA1'
+        self.enc_algorithm = 'AES-128-CBC'
         self.enc_key = None
         self.enc_iv = None
+        self.image_filename = None
+        self.image_size = None
         self.parts = None
-        self.tarball_sha1sum = None
         self._lock = threading.Lock()
 
+    @property
+    def bundled_size(self):
+        return sum(part['size'] for part in self.parts)
+
     @classmethod
     def create_from_image(cls, image_filename, part_prefix, part_size=None,
                           show_progress=False):
@@ -41,6 +74,9 @@ class Bundle(object):
                             show_progress=False):
         if part_size is None:
             part_size = self.DEFAULT_PART_SIZE
+        with self._lock:
+            self.image_filename = image_filename
+            self.image_size = os.path.getsize(image_filename)
         # pipe for getting the digest from sha1sum
         digest_pipe_out, digest_pipe_in = multiprocessing.Pipe(duplex=False)
         # pipe for tar --> sha1sum
@@ -79,13 +115,13 @@ class Bundle(object):
 
         # gzip --> openssl
         srand = random.SystemRandom()
-        key = format(srand.getrandbits(128), 'x')
-        iv = format(srand.getrandbits(128), 'x')
+        enc_key = format(srand.getrandbits(128), 'x')
+        enc_iv = format(srand.getrandbits(128), 'x')
         with self._lock:
-            self.key = key
-            self.iv = iv
+            self.enc_key = enc_key
+            self.enc_iv = enc_iv
         openssl = subprocess.Popen(['openssl', 'enc', '-e', '-aes-128-cbc',
-                                    '-K', key, '-iv', iv],
+                                    '-K', enc_key, '-iv', enc_iv],
                                    stdin=gzip.stdout, stdout=subprocess.PIPE,
                                    close_fds=True, bufsize=-1)
 
@@ -101,10 +137,10 @@ class Bundle(object):
                 _write_tarball(image, tar_input, show_progress=show_progress)
             writer_thread.join()
 
-            digest = digest_pipe_out.recv()
+            overall_digest = digest_pipe_out.recv()
             digest_pipe_out.close()
             with self._lock:
-                self.digest = digest
+                self.digest = overall_digest
 
     def _write_parts(self, infile, part_prefix, part_size):
         with self._lock:
@@ -119,15 +155,18 @@ class Bundle(object):
                 return
 
 
+### BUNDLE CREATION ###
 def _write_tarball(infile, outfile, show_progress=False):
-    widgets = [progressbar.Percentage(), ' ', progressbar.Bar(marker='='), ' ',
-               progressbar.FileTransferSpeed(), ' ', progressbar.AdaptiveETA()]
-    bar = progressbar.ProgressBar(maxval=os.path.getsize(infile.name),
-                                  widgets=widgets)
     tar_thread = threading.Thread(target=_add_fileobj_to_tarball,
                                   args=(infile, outfile))
     tar_thread.start()
     if show_progress:
+        import progressbar
+        widgets = [progressbar.Percentage(), ' ', progressbar.Bar(marker='='),
+                   ' ', progressbar.FileTransferSpeed(), ' ',
+                   progressbar.AdaptiveETA()]
+        bar = progressbar.ProgressBar(maxval=os.path.getsize(infile.name),
+                                      widgets=widgets)
         bar.start()
         while tar_thread.is_alive():
             bar.update(infile.tell())
@@ -178,14 +217,3 @@ def _write_single_part(infile, part_fname, part_size):
                 break
         return {'path': part_fname, 'digest': part_digest.hexdigest(),
                 'size': part.tell()}
-
-
-if __name__ == '__main__':
-    parser = argparse.ArgumentParser()
-    parser.add_argument('image_filename')
-    parser.add_argument('part_prefix')
-    args = parser.parse_args()
-    bundle = Bundle.create_from_image(args.image_filename, args.part_prefix,
-                                      part_size=None, show_progress=True)
-    from pprint import pprint
-    pprint(vars(bundle))
diff --git a/euca2ools/commands/bundle/bundleimage.py b/euca2ools/commands/bundle/bundleimage.py
index 7b278ac..199feaf 100644
--- a/euca2ools/commands/bundle/bundleimage.py
+++ b/euca2ools/commands/bundle/bundleimage.py
@@ -1,4 +1,6 @@
-# Copyright (c) 2009-2011, Eucalyptus Systems, Inc.
+# Software License Agreement (BSD License)
+#
+# Copyright (c) 2013, Eucalyptus Systems, Inc.
 # All rights reserved.
 #
 # Redistribution and use of this software in source and binary forms, with or
@@ -25,129 +27,299 @@
 # 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 os
-import euca2ools.commands.eucacommand
-from boto.roboto.param import Param
-import euca2ools.bundler
-from euca2ools.exceptions import NotFoundError, CommandFailed
-import sys
-
-class BundleImage(euca2ools.commands.eucacommand.EucaCommand):
-
-    Description = 'Bundles an image for use with Eucalyptus or Amazon EC2.'
-    Options = [Param(name='image_path', short_name='i', long_name='image',
-                     optional=False, ptype='file',
-                     doc='Path to the image file to bundle.'),
-               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='string',
-                     doc='Path to users PEM-encoded private key.'),
-               Param(name='prefix', short_name='p', long_name='prefix',
-                     optional=True, ptype='string',
-                     doc="""The prefix for the bundle image files.
-                     (default: image name)."""),
-               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',
-                     doc="""Default block device mapping for the image
-                     (comma-separated list of key=value pairs)."""),
-               Param(name='destination',
-                     short_name='d', long_name='destination',
-                     optional=True, ptype='string', default='/tmp',
-                     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_arch',
-                     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='batch', long_name='batch',
-                     optional=True, ptype='boolean',
-                     doc='Run in batch mode.  Compatibility only, has no effect')]
-
-    def get_block_devs(self):
-        mapping_str = self.block_device_mapping
-        mapping = {}
-        mapping_pairs = mapping_str.split(',')
-        for m in mapping_pairs:
-            m_parts = m.split('=')
-            if len(m_parts) > 1:
-                mapping[m_parts[0]] = m_parts[1]
-        return mapping
-
-    def add_product_codes(self):
-        product_codes = []
-        product_code_values = self.product_codes.split(',')
-
-        for p in product_code_values:
-            product_codes.append(p)
-
-        return product_codes
+
+import argparse
+import binascii
+import euca2ools
+from euca2ools.commands import Euca2ools
+from euca2ools.commands.argtypes import delimited_list, filesize
+from euca2ools.commands.bundle.bundle import Bundle
+import hashlib
+import lxml.etree
+import lxml.objectify
+import os.path
+from requestbuilder import Arg
+from requestbuilder.command import BaseCommand
+from requestbuilder.exceptions import ArgumentError
+import subprocess
+import tempfile
+
+
+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):
+    DESCRIPTION = ('Create a bundled form of an image suitable for uploading '
+                   'to a cloud')
+    SUITE = Euca2ools
+    ARGS = [Arg('-k', '--privatekey', metavar='FILE', help='''file containing
+                the private key to sign the bundle's manifest with'''),
+            Arg('-c', '--cert', metavar='FILE', help='''file containing your
+                X.509 certificate.  This certificate will be required to
+                unbundle the image in the future.'''),
+            Arg('-u', '--user', metavar='ACCOUNT', help='your account ID'),
+            Arg('-i', '--image', metavar='FILE', required=True,
+                help='file containing the image to bundle (required)'),
+            Arg('-r', '--arch', choices=('i386', 'x86_64', 'armhf'),
+                required=True,
+                help="the image's processor architecture (required)"),
+            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('-p', '--prefix', help='''the file name prefix to give the
+                bundle's files (default: the image's file name)'''),
+            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,
+                default=argparse.SUPPRESS,
+                help='''[machine image only] default block device mapping
+                scheme with which to launch instances of this image'''),
+            Arg('--progress', action='store_true', help='show progress'),
+            Arg('--part-size', type=filesize, default=10485760,  # 10m
+                help=argparse.SUPPRESS),
+            Arg('--image-type', choices=('machine', 'kernel', 'ramdisk'),
+                default='machine', help=argparse.SUPPRESS)]
+
+    ## TODO:  add --region support
+
+    def configure(self):
+        BaseCommand.configure(self)
+        # User's private key (user-level)
+        if not self.args.get('privatekey'):
+            privatekey = self.config.get_user_option('private-key')
+            if privatekey is not None:
+                self.args['privatekey'] = privatekey
+            else:
+                raise ArgumentError(
+                    'missing private key; please supply one with -k')
+        if not os.path.exists(self.args['privatekey']):
+            raise ArgumentError("private key file '{0}' does not exist"
+                                .format(self.args['privatekey']))
+        if not os.path.isfile(self.args['privatekey']):
+            raise ArgumentError("private key file '{0}' is not a file"
+                                .format(self.args['privatekey']))
+        # User's X.509 cert (user-level)
+        if not self.args.get('cert'):
+            cert = self.config.get_user_option('certificate')
+            if cert is not None:
+                self.args['cert'] = cert
+            else:
+                raise ArgumentError(
+                    'missing certificate; please supply one with -c')
+        if not os.path.exists(self.args['cert']):
+            raise ArgumentError("certificate file '{0}' does not exist"
+                                .format(self.args['cert']))
+        if not os.path.isfile(self.args['cert']):
+            raise ArgumentError("certificate file '{0}' is not a file"
+                                .format(self.args['cert']))
+        # Cloud's X.509 cert (region-level)
+        if not self.args.get('ec2cert'):
+            ec2cert = self.config.get_region_option('ec2-certificate')
+            if ec2cert is not None:
+                self.args['ec2cert'] = ec2cert
+            else:
+                raise ArgumentError('missing cloud certificate; please '
+                                    'supply one with --ec2cert')
+        if not os.path.exists(self.args['ec2cert']):
+            raise ArgumentError("cloud certificate file '{0}' does not exist"
+                                .format(self.args['ec2cert']))
+        if not os.path.isfile(self.args['ec2cert']):
+            raise ArgumentError("cloud certificate file '{0}' is not a file"
+                                .format(self.args['ec2cert']))
+        # User's account ID (user-level)
+        if not self.args.get('user'):
+            account_id = self.config.get_user_option('account-id')
+            if account_id is not None:
+                self.args['user'] = account_id
+            else:
+                raise ArgumentError('missing account ID; please supply one '
+                                    'with --user')
+        # kernel/ramdisk image IDs
+        if self.args.get('kernel') == 'true':
+            self.args['image_type'] = 'kernel'
+        if self.args.get('ramdisk') == 'true':
+            self.args['image_type'] = 'ramdisk'
+        if self.args['image_type'] == 'kernel':
+            if self.args.get('kernel') and self.args['kernel'] != 'true':
+                raise ArgumentError("argument --kernel: not compatible with "
+                                    "image type 'kernel'")
+            if self.args.get('ramdisk'):
+                raise ArgumentError("argument --ramdisk: not compatible with "
+                                    "image type 'kernel'")
+        if self.args['image_type'] == 'ramdisk':
+            if self.args.get('kernel'):
+                raise ArgumentError("argument --kernel: not compatible with "
+                                    "image type 'ramdisk'")
+            if self.args.get('ramdisk') and self.args['ramdisk'] != 'true':
+                raise ArgumentError("argument --ramdisk: not compatible with "
+                                    "image type 'ramdisk'")
+        if (self.args.get('destination') and
+            os.path.exists(self.args['destination']) and not
+            os.path.isdir(self.args['destination'])):
+            raise ArgumentError("argument -d/--destination: '{0}' is not a "
+                                "directory".format(self.args['destination']))
 
     def main(self):
-        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')
-        
-        bundler = euca2ools.bundler.Bundler(self)
-        
-        self.user = self.user.replace('-', '')
-
-        image_size = bundler.check_image(self.image_path, self.destination)
-        if not self.prefix:
-            self.prefix = self.get_relative_filename(self.image_path)
-        try:
-            (tgz_file, sha_tar_digest) = bundler.tarzip_image(self.prefix,
-                                                              self.image_path,
-                                                              self.destination)
-        except (NotFoundError, CommandFailed):
-            sys.exit(1)
-
-        (encrypted_file, key, iv, bundled_size) = bundler.encrypt_image(tgz_file)
-        os.remove(tgz_file)
-        (parts, parts_digest) = bundler.split_image(encrypted_file)
-        if self.block_device_mapping:
-            self.block_device_mapping = self.get_block_devs()
-        if self.product_codes:
-            self.product_codes = self.add_product_codes(self.product_codes)
-        bundler.generate_manifest(self.destination, self.prefix,
-                                  parts, parts_digest,
-                                  self.image_path, key, iv,
-                                  self.cert_path, self.ec2cert_path,
-                                  self.private_key_path,
-                                  self.target_arch, image_size,
-                                  bundled_size, sha_tar_digest,
-                                  self.user, self.kernel_id, self.ramdisk_id,
-                                  self.block_device_mapping, self.product_codes)
-        os.remove(encrypted_file)
-
-    main_cli = main
+        prefix = (self.args.get('prefix') or
+                  os.path.basename(self.args['image']))
+        if self.args.get('destination'):
+            path_prefix = os.path.join(self.args['destination'], prefix)
+            if not os.path.exists(self.args['destination']):
+                os.mkdir(self.args['destination'])
+        else:
+            tempdir_base = (os.getenv('TMPDIR') or os.getenv('TEMP') or
+                            os.getenv('TMP') or '/var/tmp')
+            tempdir = tempfile.mkdtemp(prefix='bundle-', dir=tempdir_base)
+            path_prefix = os.path.join(tempdir, prefix)
+        self.log.debug('bundle path prefix: %s', path_prefix)
+
+        bundle = Bundle.create_from_image(
+            self.args['image'], path_prefix, part_size=self.args['part_size'],
+            show_progress=self.args['progress'])
+        manifest = self.generate_manifest_xml(bundle)
+        manifest_filename = path_prefix + '.manifest.xml'
+        with open(manifest_filename, 'w') as manifest_file:
+            manifest_file.write(manifest)
+        return (part['path'] for part in bundle.parts), manifest_filename
+
+    def print_result(self, result):
+        for part_filename in result[0]:
+            print 'Wrote', part_filename
+        print 'Wrote', result[1]  # manifest
+
+    def generate_manifest_xml(self, bundle):
+        manifest = lxml.objectify.Element('manifest')
+
+        # Manifest version
+        manifest.version = '2007-10-10'
+
+        # Our version
+        manifest.bundler = None
+        manifest.bundler.name = 'euca2ools'
+        manifest.bundler.version = euca2ools.__version__
+        manifest.bundler.release = None
+
+        # Target hardware
+        manifest.machine_configuration = None
+        manifest.machine_configuration.architecture = self.args['arch']
+        if self.args.get('kernel'):
+            manifest.machine_configuration.kernel_id = self.args['kernel']
+        if self.args.get('ramdisk'):
+            manifest.machine_configuration.ramdisk_id = self.args['ramdisk']
+        if self.args['image_type'] == 'machine':
+            bd_mappings = self.args.get('block_device_mappings', {})
+            if bd_mappings:
+                manifest.machine_configuration.block_device_mapping = None
+                for virtual, device in sorted(bd_mappings.iteritems()):
+                    bd_elem = lxml.objectify.Element('mapping')
+                    bd_elem.virtual = virtual
+                    bd_elem.device = device
+                    manifest.machine_configuration.block_device_mapping.append(
+                        bd_elem)
+            if self.args.get('product_codes'):
+                manifest.machine_configuration.product_codes = None
+                for code in self.args['product_codes']:
+                    code_elem = lxml.objectify.Element('product_code')
+                    manifest.machine_configuration.product_codes.append(
+                        code_elem)
+                    (manifest.machine_configuration.product_codes
+                     .product_code[-1]) = code
+
+        # Image info
+        manifest.image = None
+        manifest.image.name = os.path.basename(bundle.image_filename)
+        manifest.image.user = self.args['user']  # user's account ID
+        manifest.image.type = self.args['image_type']  # machine/kernel/ramdisk
+        if self.args['image_type'] == 'kernel':
+            # This might need something else
+            manifest.image.kernel_name = os.path.basename(
+                bundle.image_filename)
+        manifest.image.digest = bundle.digest
+        manifest.image.digest.set('algorithm', bundle.digest_algorithm)
+        manifest.image.size = bundle.image_size
+        manifest.image.bundled_size = bundle.bundled_size
+
+        # Bundle encryption keys (these are cloud-specific)
+        manifest.image.ec2_encrypted_key = public_encrypt(bundle.enc_key,
+                                                          self.args['ec2cert'])
+        manifest.image.ec2_encrypted_key.set('algorithm', bundle.enc_algorithm)
+        manifest.image.user_encrypted_key = public_encrypt(bundle.enc_key,
+                                                           self.args['cert'])
+        manifest.image.user_encrypted_key.set('algorithm',
+                                              bundle.enc_algorithm)
+        manifest.image.ec2_encrypted_iv = public_encrypt(bundle.enc_iv,
+                                                         self.args['ec2cert'])
+        manifest.image.user_encrypted_iv = public_encrypt(bundle.enc_iv,
+                                                          self.args['cert'])
+
+        # Bundle parts
+        manifest.image.parts = None
+        manifest.image.parts.set('count', str(len(bundle.parts)))
+        for index, part in enumerate(bundle.parts):
+            part_elem = lxml.objectify.SubElement(manifest.image.parts, 'part')
+            part_elem.set('index', str(index))
+            part_elem.filename = os.path.basename(part['path'])
+            part_elem.digest = part['digest']
+            part_elem.digest.set('algorithm', bundle.digest_algorithm)
+
+        # Parent image IDs
+        if self.args['image_type'] == 'machine':
+            manifest.image.ancestry = None
+            ancestor_image_ids = self.args.get('ancestor_image_ids', [])
+            for ancestor_image_id in ancestor_image_ids:
+                ancestor_elem = lxml.objectify.Element('ancestor_ami_id')
+                manifest.image.ancestry.append(ancestor_elem)
+                manifest.image.ancestry.ancestor_ami_id[-1] = ancestor_image_id
+
+        lxml.objectify.deannotate(manifest, xsi_nil=True,
+                                  cleanup_namespaces=True)
+        to_sign = (lxml.etree.tostring(manifest.machine_configuration) +
+                   lxml.etree.tostring(manifest.image))
+        self.log.debug('string to sign: %s', repr(to_sign))
+        signature = rsa_sha1_sign(to_sign, self.args['privatekey'])
+        manifest.signature = signature
+        self.log.debug('hex-encoded signature: %s', signature)
+        lxml.objectify.deannotate(manifest, xsi_nil=True,
+                                  cleanup_namespaces=True)
+        self.log.debug('-- manifest content --\n', extra={'append': True})
+        pretty_manifest = lxml.etree.tostring(manifest,
+                                              pretty_print=True).strip()
+        self.log.debug(pretty_manifest, extra={'append': True})
+        self.log.debug('-- end of manifest content --')
+        return lxml.etree.tostring(manifest)
+
+
+def public_encrypt(content, cert_filename):
+    popen = subprocess.Popen(['openssl', 'rsautl', '-encrypt', '-pkcs',
+                              '-inkey', cert_filename, '-certin'],
+                             stdin=subprocess.PIPE, stdout=subprocess.PIPE)
+    (stdout, __) = popen.communicate(content)
+    return binascii.hexlify(stdout)
+
 
+def rsa_sha1_sign(content, privkey_filename):
+    digest = hashlib.sha1()
+    digest.update(content)
+    popen = subprocess.Popen(['openssl', 'pkeyutl', '-sign', '-inkey',
+                              privkey_filename, '-pkeyopt', 'digest:sha1'],
+                             stdin=subprocess.PIPE, stdout=subprocess.PIPE)
+    (stdout, __) = popen.communicate(digest.digest())
+    return binascii.hexlify(stdout)

-- 
managing cloud instances for Eucalyptus



More information about the pkg-eucalyptus-commits mailing list