[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