# Software License Agreement (BSD License)
#
# Copyright (c) 2009, 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@eucalyptus.com
import boto
import getopt, sys, os, stat
import tarfile
import gzip
from xml.dom.minidom import Document
from xml.dom import minidom
from hashlib import sha1 as sha
from M2Crypto import BN, EVP, RSA, util, Rand, m2, X509
from binascii import hexlify, unhexlify
from subprocess import *
import platform
import urllib
import re
import shutil
from boto.ec2.regioninfo import RegionInfo
import logging
BUNDLER_NAME = "euca-tools"
BUNDLER_VERSION = "1.0"
VERSION = "2007-10-10"
RELEASE = "31337"
AES = 'AES-128-CBC'
IP_PROTOCOLS = ['tcp', 'udp', 'icmp']
IMAGE_IO_CHUNK = 10 * 1024
IMAGE_SPLIT_CHUNK = IMAGE_IO_CHUNK * 1024;
MAX_LOOP_DEVS = 256;
METADATA_URL = "http://169.254.169.254/latest/meta-data/"
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 / ext3 defaults 1 1
/dev/sdb /mnt ext3 defaults 0 0
none /dev/pts devpts gid=5,mode=620 0 0
none /proc proc defaults 0 0
none /sys sysfs defaults 0 0
"""
OLD_FSTAB = """/dev/sda1 / ext3 defaults,errors=remount-ro 0 0
/dev/sda2 /mnt ext3 defaults 0 0
/dev/sda3 swap swap defaults 0 0
proc /proc proc defaults 0 0
devpts /dev/pts devpts gid=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
Popen(dd_cmd, PIPE).communicate()[0]
def make_fs(self, image_path):
try:
Util().check_prerequisite_command(self.MAKEFS_CMD)
except NotFoundError:
sys.exit(1)
if self.debug:
print "Creating filesystem..."
makefs_cmd = Popen([self.MAKEFS_CMD, "-F", image_path], 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 = self.OLD_FSTAB
elif fstab_path == "new":
if not generate_fstab:
return
fstab = 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)
Popen(cmd, stdout=PIPE, stderr=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 "Sorry. Solaris not supported yet"
sys.exit(1)
def make_fs(self, image_path):
print "Sorry. Solaris not supported yet"
sys.exit(1)
def make_essential_devs(self, image_path):
print "Sorry. Solaris not supported yet"
sys.exit(1)
class Util:
usage_string = """
-a, --access-key User's Access Key ID.
-s, --secret-key User's Secret Key.
-U, --url URL of the Cloud to connect to.
--config Read credentials and cloud settings from the
specified config file (defaults to $HOME/.eucarc or /etc/euca2ools/eucarc).
-h, --help Display this help message.
--version Display the version of this tool.
--debug Turn on debugging.
Euca2ools will use the environment variables EC2_URL, EC2_ACCESS_KEY, EC2_SECRET_KEY, EC2_CERT, EC2_PRIVATE_KEY, S3_URL, EUCALYPTUS_CERT by default.
"""
def usage(self, compat=False):
if compat:
self.usage_string = self.usage_string.replace("-s,", "-S,")
self.usage_string = self.usage_string.replace("-a,", "-A,")
print self.usage_string
sys.exit(1)
def check_prerequisite_command(self, command):
cmd = [command]
try:
output = Popen(cmd, stdout=PIPE, stderr=PIPE).communicate()
except OSError, e:
error_string = "%s" % e
if "No such" in error_string:
print "Command %s not found. Is it installed?" % command
raise NotFoundError
else:
raise OSError(e)
class AddressValidationError:
def __init__(self):
self.message = 'Invalid address'
class InstanceValidationError:
def __init__(self):
self.message = 'Invalid instance id'
class VolumeValidationError:
def __init__(self):
self.message = 'Invalid volume id'
class SizeValidationError:
def __init__(self):
self.message = 'Invalid size'
class SnapshotValidationError:
def __init__(self):
self.message = 'Invalid snapshot id'
class ProtocolValidationError:
def __init__(self):
self.message = 'Invalid protocol'
class FileValidationError:
def __init__(self):
self.message = 'Invalid file'
class DirValidationError:
def __init__(self):
self.message = 'Invalid directory'
class CopyError:
def __init__(self):
self.message = 'Unable to copy'
class MetadataReadError:
def __init__(self):
self.message = "Unable to read metadata"
class NullHandler(logging.Handler):
def emit(self, record):
pass
class NotFoundError:
def __init__(self):
self.message = "Unable to find"
class Euca2ool:
def process_args(self):
ids = []
for arg in self.args:
ids.append(arg)
return ids
def __init__(self, short_opts=None, long_opts=None, is_s3=False, compat=False):
self.ec2_user_access_key = None
self.ec2_user_secret_key = None
self.ec2_url = None
self.s3_url = None
self.config_file_path = None
self.is_s3 = is_s3
if compat:
self.secret_key_opt = 'S'
self.access_key_opt = 'A'
else:
self.secret_key_opt = 's'
self.access_key_opt = 'a'
if not short_opts:
short_opts = ''
if not long_opts:
long_opts = ['']
short_opts += 'hU:'
short_opts += '%s:' % self.secret_key_opt
short_opts += '%s:' % self.access_key_opt
long_opts += ['access-key=', 'secret-key=', 'url=', 'help', 'version', 'debug', 'config=']
opts, args = getopt.gnu_getopt(sys.argv[1:], short_opts,
long_opts)
self.opts = opts
self.args = args
self.debug = False
for name, value in opts:
if name in ('-%s' % self.access_key_opt, '--access-key'):
self.ec2_user_access_key = value
elif name in ('-%s' % self.secret_key_opt, '--secret-key'):
try:
self.ec2_user_secret_key = int(value)
self.ec2_user_secret_key = None
except ValueError:
self.ec2_user_secret_key = value
elif name in ('-U', '--url'):
self.ec2_url = value
elif name == '--debug':
self.debug = True
elif name == '--config':
self.config_file_path = value
system_string = platform.system()
if system_string == "Linux":
self.img = LinuxImage(self.debug)
elif system_string == "SunOS":
self.img = SolarisImage(self.debug)
else:
self.img = "Unsupported"
self.setup_environ()
h = NullHandler()
logging.getLogger("boto").addHandler(h)
SYSTEM_EUCARC_PATH = os.path.join("/etc", "euca2ools", "eucarc")
def setup_environ(self):
self.environ = {}
user_eucarc = None
if 'HOME' in os.environ:
os.path.join(os.getenv('HOME'), ".eucarc")
base_path = None
read_config = False
if self.config_file_path and os.path.exists(self.config_file_path):
base_path = os.path.dirname(self.config_file_path)
eucarc = open(self.config_file_path, "r")
read_config = True
elif user_eucarc is not None and os.path.exists(user_eucarc):
base_path = os.path.dirname(user_eucarc)
eucarc = open(user_eucarc, "r")
read_config = True
elif os.path.exists(self.SYSTEM_EUCARC_PATH):
base_path = os.path.dirname(self.SYSTEM_EUCARC_PATH)
eucarc = open(self.SYSTEM_EUCARC_PATH, "r")
read_config = True
if read_config:
lines = eucarc.readlines()
comment = re.compile('^#')
for line in lines:
line = line.strip('export')
line = line.replace('\'', '')
line = line.strip()
line = line.replace('${EUCA_KEY_DIR}', base_path)
if not comment.match(line):
parts = line.split('=', 1)
if len(parts) == 2:
self.environ[parts[0]] = parts[1]
eucarc.close()
else:
self.environ['EC2_ACCESS_KEY'] = os.getenv('EC2_ACCESS_KEY')
self.environ['EC2_SECRET_KEY'] = os.getenv('EC2_SECRET_KEY')
self.environ['S3_URL'] = os.getenv('S3_URL')
self.environ['EC2_URL'] = os.getenv('EC2_URL')
self.environ['EC2_CERT'] = os.getenv('EC2_CERT')
self.environ['EC2_PRIVATE_KEY'] = os.getenv('EC2_PRIVATE_KEY')
self.environ['EUCALYPTUS_CERT'] = os.getenv('EUCALYPTUS_CERT')
self.environ['EC2_USER_ID'] = os.getenv('EC2_USER_ID')
def get_environ(self, name):
if self.environ.has_key(name):
return self.environ[name]
else:
print '%s not found' % name
sys.exit(1)
def make_connection(self):
if not self.ec2_user_access_key:
self.ec2_user_access_key = self.environ['EC2_ACCESS_KEY']
if not self.ec2_user_access_key:
print 'EC2_ACCESS_KEY environment variable must be set.'
sys.exit(1)
if not self.ec2_user_secret_key:
self.ec2_user_secret_key = self.environ['EC2_SECRET_KEY']
if not self.ec2_user_secret_key:
print 'EC2_SECRET_KEY environment variable must be set.'
sys.exit(1)
if not self.is_s3:
if not self.ec2_url:
self.ec2_url = self.environ['EC2_URL']
if not self.ec2_url:
self.ec2_url = 'http://localhost:8773/services/Eucalyptus'
print 'EC2_URL not specified. Trying %s' % (self.ec2_url)
else:
if not self.ec2_url:
self.ec2_url = self.environ['S3_URL']
if not self.ec2_url:
self.ec2_url = 'http://localhost:8773/services/Walrus'
print 'S3_URL not specified. Trying %s' % (self.ec2_url)
self.port = None
self.service_path = "/"
if (self.ec2_url.find('https://') >= 0):
self.ec2_url = self.ec2_url.replace('https://', '')
self.is_secure = True
else:
self.ec2_url = self.ec2_url.replace('http://', '')
self.is_secure = False
self.host = self.ec2_url
url_parts = self.ec2_url.split(':')
if (len(url_parts) > 1):
self.host = url_parts[0]
path_parts = url_parts[1].split('/', 1)
if (len(path_parts) > 1):
self.port = int(path_parts[0])
self.service_path = self.service_path + path_parts[1]
else:
self.port = int(url_parts[1])
if not self.is_s3:
return boto.connect_ec2(aws_access_key_id=self.ec2_user_access_key,
aws_secret_access_key=self.ec2_user_secret_key,
is_secure=self.is_secure,
region=RegionInfo(None, "eucalyptus", self.host),
port=self.port,
path=self.service_path)
else:
return boto.s3.Connection(aws_access_key_id=self.ec2_user_access_key,
aws_secret_access_key=self.ec2_user_secret_key,
is_secure=self.is_secure,
host=self.host,
port=self.port,
calling_format=boto.s3.connection.OrdinaryCallingFormat(),
path=self.service_path)
def validate_address(self, address):
if not re.match("[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+(\/[0-9]+)?$", address):
raise AddressValidationError
def validate_instance_id(self, id):
if not re.match("i-", id):
raise InstanceValidationError
def validate_volume_id(self, id):
if not re.match("vol-", id):
raise VolumeValidationError
def validate_volume_size(self, size):
if size < 0 or size > 1024:
raise SizeValidationError
def validate_snapshot_id(self, id):
if not re.match("snap-", id):
raise SnapshotValidationError
def validate_protocol(self, proto):
if not proto in IP_PROTOCOLS:
raise ProtocolValidationError
def validate_file(self, path):
if not os.path.exists(path) or not os.path.isfile(path):
raise FileValidationError
def validate_dir(self, path):
if not os.path.exists(path) or not os.path.isdir(path):
raise DirValidationError
def get_relative_filename(self, filename):
f_parts = filename.split('/')
return f_parts[len(f_parts) - 1]
def get_file_path(self, filename):
relative_filename = self.get_relative_filename(filename)
file_path = filename.replace(relative_filename, '')
if len(file_path) == 0:
file_path = "."
return file_path
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.%d' % (file, i)
part_digest = sha()
file_part = open(filename, "wb")
print "Part:", self.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.debug:
print "Image Size:", image_size, "bytes"
in_file = open(image_file, "rb")
sha_image = sha()
while 1:
buf=in_file.read(IMAGE_IO_CHUNK)
if not buf:
break
sha_image.update(buf)
return image_size, hexlify(sha_image.digest())
# def tarzip_image(self, prefix, file, path):
# print 'Tarring image'
# tar_file = '%s.tar.gz' % os.path.join(path, prefix)
# tar = tarfile.open(tar_file, "w|gz")
# tar.add(file, arcname=prefix)
# tar.close()
# return tar_file
def tarzip_image(self, prefix, file, path):
try:
Util().check_prerequisite_command('tar')
except NotFoundError:
sys.exit(1)
print 'Tarring image'
tar_file = '%s.tar.gz' % os.path.join(path, prefix)
outfile = open(tar_file, "wb")
file_path = self.get_file_path(file)
tar_cmd = ["tar", "c", "-S"]
if file_path:
tar_cmd.append("-C")
tar_cmd.append(file_path)
tar_cmd.append(self.get_relative_filename(file))
else:
tar_cmd.append(file)
p1 = Popen(tar_cmd, stdout=PIPE)
p2 = Popen(["gzip"], stdin=p1.stdout, stdout=outfile)
p2.communicate()
outfile.close
if os.path.getsize(tar_file) <= 0:
print "Could not tar image"
sys.exit(1)
return tar_file
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', ''))
key = (hex(BN.rand(16 * 8))[2:34]).replace('L', 'c')
if self.debug:
print 'Key: %s' % (key)
iv = (hex(BN.rand(16 * 8))[2:34]).replace('L', 'c')
if self.debug:
print 'IV: %s' % (iv)
k=EVP.Cipher(alg='aes_128_cbc', key=unhexlify(key), iv=unhexlify(iv), op=1)
in_file = open(file)
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('')
end_mc = manifest_string.find('')
mc_config_string = manifest_string[start_mc:end_mc + len('')]
start_image = manifest_string.find('')
end_image = manifest_string.find('')
image_string = manifest_string[start_image:end_image + len('')]
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.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.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 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 get_block_devs(self, mapping):
virtual = []
devices = []
vname = None
for m in mapping:
if not vname:
vname = m
virtual.append(vname)
else:
devices.append(m)
vname = None
return virtual, devices
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 = RSA.load_key(private_key_path)
manifest_file = '%s.manifest.xml' % os.path.join(path, prefix)
if self.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")
virtual_names, device_names = self.get_block_devs(mapping)
vname_index = 0
for vname in virtual_names:
dname = device_names[vname_index]
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)
vname_index = vname_index + 1
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.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.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()
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()
def add_excludes(self, path, excludes):
if self.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.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):
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 "Platform not fully supported."
sys.exit(1)
self.img.create_image(size_in_MB, image_path)
self.img.make_fs(image_path)
return image_path
def create_loopback(self, image_path):
try:
Util().check_prerequisite_command('losetup')
except NotFoundError:
sys.exit(1)
tries = 0
while tries < MAX_LOOP_DEVS:
loop_dev = Popen(["losetup", "-f"], stdout=PIPE).communicate()[0].replace('\n', '')
if loop_dev:
output = Popen(["losetup", "%s" % loop_dev, "%s" % image_path], stdout=PIPE, stderr=PIPE).communicate()
if not output[1]:
return loop_dev
else:
print "Could not create loopback device. Aborting"
sys.exit(1)
tries += 1
def mount_image(self, image_path):
try:
Util().check_prerequisite_command('mount')
except NotFoundError:
sys.exit(1)
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.debug:
print "Creating loopback device..."
loop_dev = self.create_loopback(image_path)
if self.debug:
print "Mounting image..."
Popen(["mount", loop_dev, tmp_mnt_point], stdout=PIPE).communicate()
return tmp_mnt_point, loop_dev
def copy_to_image(self, mount_point, volume_path, excludes):
try:
Util().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.debug:
print "Copying files..."
for exclude in excludes:
print "Excluding:", exclude
output = Popen(rsync_cmd, stdout=PIPE, stderr=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.debug:
print 'Making essential directory %s' % mount_location
os.makedirs(dir_path)
mtab_file.close()
if output[1]:
raise CopyError
def unmount_image(self, mount_point):
try:
Util().check_prerequisite_command('umount')
except NotFoundError:
sys.exit(1)
if self.debug:
print "Unmounting image..."
Popen(["umount", "-d", mount_point], stdout=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 "Platform not fully supported."
sys.exit(1)
self.img.add_fstab(mount_point, generate_fstab, fstab_path)
except CopyError:
raise CopyError
finally:
self.unmount_image(mount_point)
def can_read_instance_metadata(self):
meta_data = urllib.urlopen(METADATA_URL)
def get_instance_metadata(self, type):
if self.debug:
print "Reading instance metadata", type
metadata = urllib.urlopen(METADATA_URL + type).read()
if "Not" in metadata and "Found" in metadata and "404" in metadata:
raise MetadataReadError
return metadata
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):
keys = self.get_instance_metadata('block-device-mapping').split('\n')
mapping = []
for k in keys:
mapping.append(k)
mapping.append(self.get_instance_metadata(os.path.join('block-device-mapping', k)))
return mapping
def display_error_and_exit(self, msg):
code = None
message = None
index = msg.find("<")
if (index < 0):
print 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 "%s:" % code, message
except Exception:
print msg
sys.exit(1)