# Copyright 2014-2015 Eucalyptus Systems, Inc.
#
# Redistribution and use of this software in source and binary forms,
# with or without modification, are permitted provided that the following
# conditions are met:
#
# Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import argparse
import hashlib
import tempfile
from requestbuilder import Arg
from requestbuilder.auth.aws import HmacV4Auth
from requestbuilder.mixins import FileTransferProgressBarMixin, TabifyingMixin
import euca2ools
from euca2ools.commands.ec2 import EC2
from euca2ools.commands.euimage.pack import ImagePack
from euca2ools.commands.s3 import S3Request
class InstallPackedImage(S3Request, FileTransferProgressBarMixin,
TabifyingMixin):
DESCRIPTION = '***TECH PREVIEW***\n\nInstall a packed image into the cloud'
ARGS = [Arg('pack_filename', metavar='FILE',
help='the pack to install (required)'),
Arg('--profile', help='''which of the image's profiles to
install (default: "default")'''),
# Upload stuff (for bundle pieces or imported disk image pieces)
Arg('-b', '--bucket', metavar='BUCKET[/PREFIX]',
help='bucket to upload the image to (required)'),
Arg('--location', help='''location constraint of the destination
bucket (default: inferred from s3-location-constraint in
configuration, or otherwise none'''),
# Bundle stuff
Arg('--privatekey', metavar='FILE', help='''file containing
your private key to sign the bundle's manifest with. This
private key will also be required to unbundle the bundle in
the future. (instance-store only)'''),
Arg('--cert', metavar='FILE', help='''file containing your
X.509 certificate (instance-store only)'''),
Arg('--ec2cert', metavar='FILE', help='''file containing the
cloud's X.509 certificate (instance-store only)'''),
Arg('--user', metavar='ACCOUNT',
help='your account ID (instance-store only)'),
# Registration stuff
Arg('--kernel', metavar='IMAGE', help='''ID of the kernel image to
associate with this machine image (paravirtual only)'''),
Arg('--ramdisk', metavar='IMAGE', help='''ID of the ramdisk image
to associate with this machine image (paravirtual only)'''),
Arg('--ec2-url', help='compute service endpoint URL'),
Arg('--ec2-auth', help=argparse.SUPPRESS),
Arg('--ec2-service', help=argparse.SUPPRESS)]
def configure(self):
S3Request.configure(self)
if not self.args.get('ec2_service'):
self.args['ec2_service'] = EC2.from_other(
self.service, url=self.args.get('ec2_url'))
if not self.args.get('ec2_auth'):
self.args['ec2_auth'] = HmacV4Auth.from_other(self.auth)
if not self.args.get('profile'):
self.args['profile'] = 'default'
def main(self):
services = {'s3': {'service': self.service, 'auth': self.auth},
'ec2': {'service': self.args['ec2_service'],
'auth': self.args['ec2_auth']}}
unpacked_image = tempfile.TemporaryFile()
with ImagePack.open(self.args['pack_filename']) as pack:
if self.args['profile'] not in pack.image_md.profiles:
raise ValueError(
'no such profile: "{0}" (choose from {1})'.format(
self.args['profile'],
', '.join(pack.image_md.profiles.keys())))
with pack.open_image() as image:
# We could technically hand the image file object
# directly to the installation process and calculate
# checksums on fly, but that would mean we error out
# only after everything finishes and force people to
# clean up after if the checksum happens to be bad.
# We thus do this in two steps instead.
digest = hashlib.sha256()
bytes_written = 0
pbar = self.get_progressbar(label='Decompressing',
maxval=pack.pack_md.image_size)
pbar.start()
while True:
chunk = image.read(euca2ools.BUFSIZE)
if not chunk:
break
digest.update(chunk)
unpacked_image.write(chunk)
bytes_written += len(chunk)
pbar.update(bytes_written)
pbar.finish()
if digest.hexdigest() != pack.pack_md.image_sha256sum:
raise RuntimeError('image appears to be corrupt '
'(expected SHA256: {0}, actual: {1})',
pack.pack_md.image_sha256sum,
digest.hexdigest())
unpacked_image.seek(0)
image_id = pack.image_md.install_profile(
self.args['profile'], services, unpacked_image,
pack.pack_md.image_size, self.args)
unpacked_image.close()
return image_id
def print_result(self, image_id):
print self.tabify(('IMAGE', image_id))