Path: blob/develop/awscli/customizations/codedeploy/push.py
1567 views
# Copyright 2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.1#2# Licensed under the Apache License, Version 2.0 (the "License"). You3# may not use this file except in compliance with the License. A copy of4# the License is located at5#6# http://aws.amazon.com/apache2.0/7#8# or in the "license" file accompanying this file. This file is9# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF10# ANY KIND, either express or implied. See the License for the specific11# language governing permissions and limitations under the License.1213import os14import sys15import zipfile16import tempfile17import contextlib1819from botocore.exceptions import ClientError2021from awscli.customizations.codedeploy.utils import validate_s3_location22from awscli.customizations.commands import BasicCommand23from awscli.compat import BytesIO, ZIP_COMPRESSION_MODE, get_current_datetime24from awscli.utils import create_nested_client2526ONE_MB = 1 << 2027MULTIPART_LIMIT = 6 * ONE_MB282930class Push(BasicCommand):31NAME = 'push'3233DESCRIPTION = (34'Bundles and uploads to Amazon Simple Storage Service (Amazon S3) an '35'application revision, which is a zip archive file that contains '36'deployable content and an accompanying Application Specification '37'file (AppSpec file). If the upload is successful, a message is '38'returned that describes how to call the create-deployment command to '39'deploy the application revision from Amazon S3 to target Amazon '40'Elastic Compute Cloud (Amazon EC2) instances.'41)4243ARG_TABLE = [44{45'name': 'application-name',46'synopsis': '--application-name <app-name>',47'required': True,48'help_text': (49'Required. The name of the AWS CodeDeploy application to be '50'associated with the application revision.'51)52},53{54'name': 's3-location',55'synopsis': '--s3-location s3://<bucket>/<key>',56'required': True,57'help_text': (58'Required. Information about the location of the application '59'revision to be uploaded to Amazon S3. You must specify both '60'a bucket and a key that represent the Amazon S3 bucket name '61'and the object key name. Content will be zipped before '62'uploading. Use the format s3://<bucket>/<key>'63),64},65{66'name': 'ignore-hidden-files',67'action': 'store_true',68'default': False,69'group_name': 'ignore-hidden-files',70'help_text': (71'Optional. Set the --ignore-hidden-files flag to not bundle '72'and upload hidden files to Amazon S3; otherwise, set the '73'--no-ignore-hidden-files flag (the default) to bundle and '74'upload hidden files to Amazon S3.'75)76},77{78'name': 'no-ignore-hidden-files',79'action': 'store_true',80'default': False,81'group_name': 'ignore-hidden-files'82},83{84'name': 'source',85'synopsis': '--source <path>',86'default': '.',87'help_text': (88'Optional. The location of the deployable content and the '89'accompanying AppSpec file on the development machine to be '90'zipped and uploaded to Amazon S3. If not specified, the '91'current directory is used.'92)93},94{95'name': 'description',96'synopsis': '--description <description>',97'help_text': (98'Optional. A comment that summarizes the application '99'revision. If not specified, the default string "Uploaded by '100'AWS CLI \'time\' UTC" is used, where \'time\' is the current '101'system time in Coordinated Universal Time (UTC).'102)103}104]105106def _run_main(self, parsed_args, parsed_globals):107self._validate_args(parsed_args)108self.codedeploy = create_nested_client(109self._session,110'codedeploy',111region_name=parsed_globals.region,112endpoint_url=parsed_globals.endpoint_url,113verify=parsed_globals.verify_ssl114)115self.s3 = create_nested_client(116self._session,117's3',118region_name=parsed_globals.region119)120self._push(parsed_args)121122def _validate_args(self, parsed_args):123validate_s3_location(parsed_args, 's3_location')124if parsed_args.ignore_hidden_files \125and parsed_args.no_ignore_hidden_files:126raise RuntimeError(127'You cannot specify both --ignore-hidden-files and '128'--no-ignore-hidden-files.'129)130if not parsed_args.description:131parsed_args.description = (132'Uploaded by AWS CLI {0} UTC'.format(133get_current_datetime().isoformat()134)135)136137def _push(self, params):138with self._compress(139params.source,140params.ignore_hidden_files141) as bundle:142try:143upload_response = self._upload_to_s3(params, bundle)144params.eTag = upload_response['ETag'].replace('"', "")145if 'VersionId' in upload_response:146params.version = upload_response['VersionId']147except Exception as e:148raise RuntimeError(149'Failed to upload \'%s\' to \'%s\': %s' %150(params.source,151params.s3_location,152str(e))153)154self._register_revision(params)155156if 'version' in params:157version_string = ',version={0}'.format(params.version)158else:159version_string = ''160s3location_string = (161'--s3-location bucket={0},key={1},'162'bundleType=zip,eTag={2}{3}'.format(163params.bucket,164params.key,165params.eTag,166version_string167)168)169sys.stdout.write(170'To deploy with this revision, run:\n'171'aws deploy create-deployment '172'--application-name {0} {1} '173'--deployment-group-name <deployment-group-name> '174'--deployment-config-name <deployment-config-name> '175'--description <description>\n'.format(176params.application_name,177s3location_string178)179)180181@contextlib.contextmanager182def _compress(self, source, ignore_hidden_files=False):183source_path = os.path.abspath(source)184appspec_path = os.path.sep.join([source_path, 'appspec.yml'])185with tempfile.TemporaryFile('w+b') as tf:186zf = zipfile.ZipFile(tf, 'w', allowZip64=True)187# Using 'try'/'finally' instead of 'with' statement since ZipFile188# does not have support context manager in Python 2.6.189try:190contains_appspec = False191for root, dirs, files in os.walk(source, topdown=True):192if ignore_hidden_files:193files = [fn for fn in files if not fn.startswith('.')]194dirs[:] = [dn for dn in dirs if not dn.startswith('.')]195for fn in files:196filename = os.path.join(root, fn)197filename = os.path.abspath(filename)198arcname = filename[len(source_path) + 1:]199if filename == appspec_path:200contains_appspec = True201zf.write(filename, arcname, ZIP_COMPRESSION_MODE)202if not contains_appspec:203raise RuntimeError(204'{0} was not found'.format(appspec_path)205)206finally:207zf.close()208yield tf209210def _upload_to_s3(self, params, bundle):211size_remaining = self._bundle_size(bundle)212if size_remaining < MULTIPART_LIMIT:213return self.s3.put_object(214Bucket=params.bucket,215Key=params.key,216Body=bundle217)218else:219return self._multipart_upload_to_s3(220params,221bundle,222size_remaining223)224225def _bundle_size(self, bundle):226bundle.seek(0, 2)227size = bundle.tell()228bundle.seek(0)229return size230231def _multipart_upload_to_s3(self, params, bundle, size_remaining):232create_response = self.s3.create_multipart_upload(233Bucket=params.bucket,234Key=params.key235)236upload_id = create_response['UploadId']237try:238part_num = 1239multipart_list = []240bundle.seek(0)241while size_remaining > 0:242data = bundle.read(MULTIPART_LIMIT)243upload_response = self.s3.upload_part(244Bucket=params.bucket,245Key=params.key,246UploadId=upload_id,247PartNumber=part_num,248Body=BytesIO(data)249)250multipart_list.append({251'PartNumber': part_num,252'ETag': upload_response['ETag']253})254part_num += 1255size_remaining -= len(data)256return self.s3.complete_multipart_upload(257Bucket=params.bucket,258Key=params.key,259UploadId=upload_id,260MultipartUpload={'Parts': multipart_list}261)262except ClientError as e:263self.s3.abort_multipart_upload(264Bucket=params.bucket,265Key=params.key,266UploadId=upload_id267)268raise e269270def _register_revision(self, params):271revision = {272'revisionType': 'S3',273's3Location': {274'bucket': params.bucket,275'key': params.key,276'bundleType': 'zip',277'eTag': params.eTag278}279}280if 'version' in params:281revision['s3Location']['version'] = params.version282self.codedeploy.register_application_revision(283applicationName=params.application_name,284revision=revision,285description=params.description286)287288289