# Copyright (c) 2013-2016 Hewlett Packard Enterprise Development LP # # 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 os.path import tarfile from requestbuilder.command import BaseCommand from requestbuilder.exceptions import ClientError from requestbuilder.mixins import (FileTransferProgressBarMixin, RegionConfigurableMixin) import euca2ools.bundle.manifest from euca2ools.bundle.pipes.core import (create_bundle_pipeline, copy_with_progressbar) from euca2ools.bundle.pipes.fittings import (create_bundle_part_writer, create_mpconn_aggregator) import euca2ools.bundle.util from euca2ools.commands import Euca2ools from euca2ools.commands.bundle.mixins import BundleCreatingMixin from euca2ools.commands.bootstrap import BootstrapRequest from euca2ools.util import mkdtemp_for_large_files class BundleImage(BaseCommand, BundleCreatingMixin, FileTransferProgressBarMixin, RegionConfigurableMixin): SUITE = Euca2ools DESCRIPTION = 'Prepare an image for use in the cloud' REGION_ENVVAR = ('EUCA_DEFAULT_REGION', 'AWS_DEFAULT_REGION') # Needed because BundleImage has no auth class of its own ARGS = BootstrapRequest.AUTH_CLASS.ARGS # noinspection PyExceptionInherit def configure(self): self.update_config_view() BaseCommand.configure(self) # Set up access to bootstrap in case we need auto cert fetching. # # We would normally make short work of this using from_other # methods, but since BundleImage doesn't have service or # auth classes of its own we get to do this the hard way. if not self.args.get('bootstrap_service'): service = BootstrapRequest.SERVICE_CLASS( config=self.config, loglevel=self.log.level, url=self.args.get('bootstrap_url')) try: service.configure() except ClientError: self.log.debug('bootstrap service setup failed; auto cert ' 'fetching will be unavailable', exc_info=True) else: self.args['bootstrap_service'] = service if (not self.args.get('bootstrap_auth') and self.args.get('bootstrap_service')): auth = BootstrapRequest.AUTH_CLASS( config=self.config, loglevel=self.log.level, **self.args) try: auth.configure() except ClientError: self.log.debug('bootstrap auth setup failed; auto cert ' 'fetching will be unavailable', exc_info=True) else: self.args['bootstrap_auth'] = auth self.configure_bundle_creds() self.configure_bundle_properties() self.configure_bundle_output() self.generate_encryption_keys() def main(self): if self.args.get('destination'): path_prefix = os.path.join(self.args['destination'], self.args['prefix']) if not os.path.exists(self.args['destination']): os.mkdir(self.args['destination']) else: tempdir = mkdtemp_for_large_files(prefix='bundle-') path_prefix = os.path.join(tempdir, self.args['prefix']) self.log.debug('bundle path prefix: %s', path_prefix) # First create the bundle digest, partinfo = self.create_bundle(path_prefix) # All done; now build the manifest and write it to disk manifest = self.build_manifest(digest, partinfo) manifest_filename = '{0}.manifest.xml'.format(path_prefix) with open(manifest_filename, 'w') as manifest_file: manifest.dump_to_file(manifest_file, self.args['privatekey'], self.args['cert'], self.args['ec2cert']) # Then we just inform the caller of all the files we wrote. # Manifests are returned in a tuple for future expansion, where we # bundle for more than one region at a time. return (part.filename for part in partinfo), (manifest_filename,) # pylint: disable=no-self-use def print_result(self, result): for manifest_filename in result[1]: print 'Wrote manifest', manifest_filename # pylint: enable=no-self-use def create_bundle(self, path_prefix): # Fill out all the relevant info needed for a tarball tarinfo = tarfile.TarInfo(self.args['prefix']) tarinfo.size = self.args['image_size'] # The pipeline begins with self.args['image'] feeding a bundling pipe # segment through a progress meter, which has to happen on the main # thread, so we add that to the pipeline last. # meter --(bytes)--> bundler bundle_in_r, bundle_in_w = euca2ools.bundle.util.open_pipe_fileobjs() partwriter_in_r, partwriter_in_w = \ euca2ools.bundle.util.open_pipe_fileobjs() digest_result_mpconn = create_bundle_pipeline( bundle_in_r, partwriter_in_w, self.args['enc_key'], self.args['enc_iv'], tarinfo, debug=self.debug) bundle_in_r.close() partwriter_in_w.close() # bundler --(bytes)-> part writer bundle_partinfo_mpconn = create_bundle_part_writer( partwriter_in_r, path_prefix, self.args['part_size'], debug=self.debug) partwriter_in_r.close() # part writer --(part info)-> part info aggregator # (needed for building the manifest) bundle_partinfo_aggr_mpconn = create_mpconn_aggregator( bundle_partinfo_mpconn, debug=self.debug) bundle_partinfo_mpconn.close() # disk --(bytes)-> bundler # (synchronous) label = self.args.get('progressbar_label') or 'Bundling image' pbar = self.get_progressbar(label=label, maxval=self.args['image_size']) with self.args['image'] as image: try: read_size = copy_with_progressbar(image, bundle_in_w, progressbar=pbar) except ValueError: self.log.debug('error from copy_with_progressbar', exc_info=True) raise RuntimeError('corrupt bundle: input size was larger ' 'than expected image size of {0}' .format(self.args['image_size'])) bundle_in_w.close() if read_size != self.args['image_size']: raise RuntimeError('corrupt bundle: input size did not match ' 'expected image size (expected size: {0}, ' 'read: {1})' .format(self.args['image_size'], read_size)) # All done; now grab info about the bundle we just created try: digest = digest_result_mpconn.recv() partinfo = bundle_partinfo_aggr_mpconn.recv() except EOFError: self.log.debug('EOFError from reading bundle info', exc_info=True) raise RuntimeError( 'corrupt bundle: bundle process was interrupted') finally: digest_result_mpconn.close() bundle_partinfo_aggr_mpconn.close() self.log.info('%i bundle parts written to %s', len(partinfo), os.path.dirname(path_prefix)) self.log.debug('bundle digest: %s', digest) return digest, partinfo