[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