We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[aws deploy push] Destination bundle type does not match extension
When specifying a destination with a suffix of ".zip" or ".tar" or ".tar.gz" for use with the `aws deploy push` command the resulting bundle is always only zipped.

```
$ aws deploy push --application-name test --s3-location s3://$AWS_DEPLOY_BUCKET/test/latest.tar --no-ignore-hidden-files --source .
To deploy with this revision, run:
aws deploy create-deployment --application-name test --s3-location bucket=BUCKET_NAME,key=test/latest.tar,bundleType=zip,eTag=... --deployment-group-name <deployment-group-name> --deployment-config-name <deployment-config-name> --description <description>
```

I downloaded and check the file it come back as a zip file.

```
$ file latest.tar 
latest.tar: Zip archive data, at least v2.0 to extract
```


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `awscli/customizations/codedeploy/push.py`
Content:
```
1 # Copyright 2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License"). You
4 # may not use this file except in compliance with the License. A copy of
5 # the License is located at
6 #
7 # http://aws.amazon.com/apache2.0/
8 #
9 # or in the "license" file accompanying this file. This file is
10 # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
11 # ANY KIND, either express or implied. See the License for the specific
12 # language governing permissions and limitations under the License.
13 
14 import os
15 import sys
16 import zipfile
17 import tempfile
18 import contextlib
19 from datetime import datetime
20 
21 from botocore.exceptions import ClientError
22 
23 from awscli.compat import six
24 from awscli.customizations.codedeploy.utils import validate_s3_location
25 from awscli.customizations.commands import BasicCommand
26 from awscli.compat import ZIP_COMPRESSION_MODE
27 
28 
29 ONE_MB = 1 << 20
30 MULTIPART_LIMIT = 6 * ONE_MB
31 
32 
33 class Push(BasicCommand):
34     NAME = 'push'
35 
36     DESCRIPTION = (
37         'Bundles and uploads to Amazon Simple Storage Service (Amazon S3) an '
38         'application revision, which is an archive file that contains '
39         'deployable content and an accompanying Application Specification '
40         'file (AppSpec file). If the upload is successful, a message is '
41         'returned that describes how to call the create-deployment command to '
42         'deploy the application revision from Amazon S3 to target Amazon '
43         'Elastic Compute Cloud (Amazon EC2) instances.'
44     )
45 
46     ARG_TABLE = [
47         {
48             'name': 'application-name',
49             'synopsis': '--application-name <app-name>',
50             'required': True,
51             'help_text': (
52                 'Required. The name of the AWS CodeDeploy application to be '
53                 'associated with the application revision.'
54             )
55         },
56         {
57             'name': 's3-location',
58             'synopsis': '--s3-location s3://<bucket>/<key>',
59             'required': True,
60             'help_text': (
61                 'Required. Information about the location of the application '
62                 'revision to be uploaded to Amazon S3. You must specify both '
63                 'a bucket and a key that represent the Amazon S3 bucket name '
64                 'and the object key name. Use the format '
65                 's3://\<bucket\>/\<key\>'
66             )
67         },
68         {
69             'name': 'ignore-hidden-files',
70             'action': 'store_true',
71             'default': False,
72             'group_name': 'ignore-hidden-files',
73             'help_text': (
74                 'Optional. Set the --ignore-hidden-files flag to not bundle '
75                 'and upload hidden files to Amazon S3; otherwise, set the '
76                 '--no-ignore-hidden-files flag (the default) to bundle and '
77                 'upload hidden files to Amazon S3.'
78             )
79         },
80         {
81             'name': 'no-ignore-hidden-files',
82             'action': 'store_true',
83             'default': False,
84             'group_name': 'ignore-hidden-files'
85         },
86         {
87             'name': 'source',
88             'synopsis': '--source <path>',
89             'default': '.',
90             'help_text': (
91                 'Optional. The location of the deployable content and the '
92                 'accompanying AppSpec file on the development machine to be '
93                 'bundled and uploaded to Amazon S3. If not specified, the '
94                 'current directory is used.'
95             )
96         },
97         {
98             'name': 'description',
99             'synopsis': '--description <description>',
100             'help_text': (
101                 'Optional. A comment that summarizes the application '
102                 'revision. If not specified, the default string "Uploaded by '
103                 'AWS CLI \'time\' UTC" is used, where \'time\' is the current '
104                 'system time in Coordinated Universal Time (UTC).'
105             )
106         }
107     ]
108 
109     def _run_main(self, parsed_args, parsed_globals):
110         self._validate_args(parsed_args)
111         self.codedeploy = self._session.create_client(
112             'codedeploy',
113             region_name=parsed_globals.region,
114             endpoint_url=parsed_globals.endpoint_url,
115             verify=parsed_globals.verify_ssl
116         )
117         self.s3 = self._session.create_client(
118             's3',
119             region_name=parsed_globals.region
120         )
121         self._push(parsed_args)
122 
123     def _validate_args(self, parsed_args):
124         validate_s3_location(parsed_args, 's3_location')
125         if parsed_args.ignore_hidden_files \
126                 and parsed_args.no_ignore_hidden_files:
127             raise RuntimeError(
128                 'You cannot specify both --ignore-hidden-files and '
129                 '--no-ignore-hidden-files.'
130             )
131         if not parsed_args.description:
132             parsed_args.description = (
133                 'Uploaded by AWS CLI {0} UTC'.format(
134                     datetime.utcnow().isoformat()
135                 )
136             )
137 
138     def _push(self, params):
139         with self._compress(
140                 params.source,
141                 params.ignore_hidden_files
142         ) as bundle:
143             try:
144                 upload_response = self._upload_to_s3(params, bundle)
145                 params.eTag = upload_response['ETag'].replace('"', "")
146                 if 'VersionId' in upload_response:
147                     params.version = upload_response['VersionId']
148             except Exception as e:
149                 raise RuntimeError(
150                     'Failed to upload \'%s\' to \'%s\': %s' %
151                     (params.source,
152                      params.s3_location,
153                      str(e))
154                 )
155         self._register_revision(params)
156 
157         if 'version' in params:
158             version_string = ',version={0}'.format(params.version)
159         else:
160             version_string = ''
161         s3location_string = (
162             '--s3-location bucket={0},key={1},'
163             'bundleType=zip,eTag={2}{3}'.format(
164                 params.bucket,
165                 params.key,
166                 params.eTag,
167                 version_string
168             )
169         )
170         sys.stdout.write(
171             'To deploy with this revision, run:\n'
172             'aws deploy create-deployment '
173             '--application-name {0} {1} '
174             '--deployment-group-name <deployment-group-name> '
175             '--deployment-config-name <deployment-config-name> '
176             '--description <description>\n'.format(
177                 params.application_name,
178                 s3location_string
179             )
180         )
181 
182     @contextlib.contextmanager
183     def _compress(self, source, ignore_hidden_files=False):
184         source_path = os.path.abspath(source)
185         appspec_path = os.path.sep.join([source_path, 'appspec.yml'])
186         with tempfile.TemporaryFile('w+b') as tf:
187             zf = zipfile.ZipFile(tf, 'w', allowZip64=True)
188             # Using 'try'/'finally' instead of 'with' statement since ZipFile
189             # does not have support context manager in Python 2.6.
190             try:
191                 contains_appspec = False
192                 for root, dirs, files in os.walk(source, topdown=True):
193                     if ignore_hidden_files:
194                         files = [fn for fn in files if not fn.startswith('.')]
195                         dirs[:] = [dn for dn in dirs if not dn.startswith('.')]
196                     for fn in files:
197                         filename = os.path.join(root, fn)
198                         filename = os.path.abspath(filename)
199                         arcname = filename[len(source_path) + 1:]
200                         if filename == appspec_path:
201                             contains_appspec = True
202                         zf.write(filename, arcname, ZIP_COMPRESSION_MODE)
203                 if not contains_appspec:
204                     raise RuntimeError(
205                         '{0} was not found'.format(appspec_path)
206                     )
207             finally:
208                 zf.close()
209             yield tf
210 
211     def _upload_to_s3(self, params, bundle):
212         size_remaining = self._bundle_size(bundle)
213         if size_remaining < MULTIPART_LIMIT:
214             return self.s3.put_object(
215                 Bucket=params.bucket,
216                 Key=params.key,
217                 Body=bundle
218             )
219         else:
220             return self._multipart_upload_to_s3(
221                 params,
222                 bundle,
223                 size_remaining
224             )
225 
226     def _bundle_size(self, bundle):
227         bundle.seek(0, 2)
228         size = bundle.tell()
229         bundle.seek(0)
230         return size
231 
232     def _multipart_upload_to_s3(self, params, bundle, size_remaining):
233         create_response = self.s3.create_multipart_upload(
234             Bucket=params.bucket,
235             Key=params.key
236         )
237         upload_id = create_response['UploadId']
238         try:
239             part_num = 1
240             multipart_list = []
241             bundle.seek(0)
242             while size_remaining > 0:
243                 data = bundle.read(MULTIPART_LIMIT)
244                 upload_response = self.s3.upload_part(
245                     Bucket=params.bucket,
246                     Key=params.key,
247                     UploadId=upload_id,
248                     PartNumber=part_num,
249                     Body=six.BytesIO(data)
250                 )
251                 multipart_list.append({
252                     'PartNumber': part_num,
253                     'ETag': upload_response['ETag']
254                 })
255                 part_num += 1
256                 size_remaining -= len(data)
257             return self.s3.complete_multipart_upload(
258                 Bucket=params.bucket,
259                 Key=params.key,
260                 UploadId=upload_id,
261                 MultipartUpload={'Parts': multipart_list}
262             )
263         except ClientError as e:
264             self.s3.abort_multipart_upload(
265                 Bucket=params.bucket,
266                 Key=params.key,
267                 UploadId=upload_id
268             )
269             raise e
270 
271     def _register_revision(self, params):
272         revision = {
273             'revisionType': 'S3',
274             's3Location': {
275                 'bucket': params.bucket,
276                 'key': params.key,
277                 'bundleType': 'zip',
278                 'eTag': params.eTag
279             }
280         }
281         if 'version' in params:
282             revision['s3Location']['version'] = params.version
283         self.codedeploy.register_application_revision(
284             applicationName=params.application_name,
285             revision=revision,
286             description=params.description
287         )
288
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/awscli/customizations/codedeploy/push.py b/awscli/customizations/codedeploy/push.py
--- a/awscli/customizations/codedeploy/push.py
+++ b/awscli/customizations/codedeploy/push.py
@@ -35,7 +35,7 @@
 
     DESCRIPTION = (
         'Bundles and uploads to Amazon Simple Storage Service (Amazon S3) an '
-        'application revision, which is an archive file that contains '
+        'application revision, which is a zip archive file that contains '
         'deployable content and an accompanying Application Specification '
         'file (AppSpec file). If the upload is successful, a message is '
         'returned that describes how to call the create-deployment command to '
@@ -61,8 +61,8 @@
                 'Required. Information about the location of the application '
                 'revision to be uploaded to Amazon S3. You must specify both '
                 'a bucket and a key that represent the Amazon S3 bucket name '
-                'and the object key name. Use the format '
-                's3://\<bucket\>/\<key\>'
+                'and the object key name. Content will be zipped before '
+                'uploading. Use the format s3://\<bucket\>/\<key\>'
             )
         },
         {
@@ -90,7 +90,7 @@
             'help_text': (
                 'Optional. The location of the deployable content and the '
                 'accompanying AppSpec file on the development machine to be '
-                'bundled and uploaded to Amazon S3. If not specified, the '
+                'zipped and uploaded to Amazon S3. If not specified, the '
                 'current directory is used.'
             )
         },

{"golden_diff": "diff --git a/awscli/customizations/codedeploy/push.py b/awscli/customizations/codedeploy/push.py\n--- a/awscli/customizations/codedeploy/push.py\n+++ b/awscli/customizations/codedeploy/push.py\n@@ -35,7 +35,7 @@\n \n     DESCRIPTION = (\n         'Bundles and uploads to Amazon Simple Storage Service (Amazon S3) an '\n-        'application revision, which is an archive file that contains '\n+        'application revision, which is a zip archive file that contains '\n         'deployable content and an accompanying Application Specification '\n         'file (AppSpec file). If the upload is successful, a message is '\n         'returned that describes how to call the create-deployment command to '\n@@ -61,8 +61,8 @@\n                 'Required. Information about the location of the application '\n                 'revision to be uploaded to Amazon S3. You must specify both '\n                 'a bucket and a key that represent the Amazon S3 bucket name '\n-                'and the object key name. Use the format '\n-                's3://\\<bucket\\>/\\<key\\>'\n+                'and the object key name. Content will be zipped before '\n+                'uploading. Use the format s3://\\<bucket\\>/\\<key\\>'\n             )\n         },\n         {\n@@ -90,7 +90,7 @@\n             'help_text': (\n                 'Optional. The location of the deployable content and the '\n                 'accompanying AppSpec file on the development machine to be '\n-                'bundled and uploaded to Amazon S3. If not specified, the '\n+                'zipped and uploaded to Amazon S3. If not specified, the '\n                 'current directory is used.'\n             )\n         },\n", "issue": "[aws deploy push] Destination bundle type does not match extension\nWhen specifying a destination with a suffix of \".zip\" or \".tar\" or \".tar.gz\" for use with the `aws deploy push` command the resulting bundle is always only zipped.\n\n```\n$ aws deploy push --application-name test --s3-location s3://$AWS_DEPLOY_BUCKET/test/latest.tar --no-ignore-hidden-files --source .\nTo deploy with this revision, run:\naws deploy create-deployment --application-name test --s3-location bucket=BUCKET_NAME,key=test/latest.tar,bundleType=zip,eTag=... --deployment-group-name <deployment-group-name> --deployment-config-name <deployment-config-name> --description <description>\n```\n\nI downloaded and check the file it come back as a zip file.\n\n```\n$ file latest.tar \nlatest.tar: Zip archive data, at least v2.0 to extract\n```\n\n", "before_files": [{"content": "# Copyright 2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\"). You\n# may not use this file except in compliance with the License. A copy of\n# the License is located at\n#\n# http://aws.amazon.com/apache2.0/\n#\n# or in the \"license\" file accompanying this file. This file is\n# distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF\n# ANY KIND, either express or implied. See the License for the specific\n# language governing permissions and limitations under the License.\n\nimport os\nimport sys\nimport zipfile\nimport tempfile\nimport contextlib\nfrom datetime import datetime\n\nfrom botocore.exceptions import ClientError\n\nfrom awscli.compat import six\nfrom awscli.customizations.codedeploy.utils import validate_s3_location\nfrom awscli.customizations.commands import BasicCommand\nfrom awscli.compat import ZIP_COMPRESSION_MODE\n\n\nONE_MB = 1 << 20\nMULTIPART_LIMIT = 6 * ONE_MB\n\n\nclass Push(BasicCommand):\n    NAME = 'push'\n\n    DESCRIPTION = (\n        'Bundles and uploads to Amazon Simple Storage Service (Amazon S3) an '\n        'application revision, which is an archive file that contains '\n        'deployable content and an accompanying Application Specification '\n        'file (AppSpec file). If the upload is successful, a message is '\n        'returned that describes how to call the create-deployment command to '\n        'deploy the application revision from Amazon S3 to target Amazon '\n        'Elastic Compute Cloud (Amazon EC2) instances.'\n    )\n\n    ARG_TABLE = [\n        {\n            'name': 'application-name',\n            'synopsis': '--application-name <app-name>',\n            'required': True,\n            'help_text': (\n                'Required. The name of the AWS CodeDeploy application to be '\n                'associated with the application revision.'\n            )\n        },\n        {\n            'name': 's3-location',\n            'synopsis': '--s3-location s3://<bucket>/<key>',\n            'required': True,\n            'help_text': (\n                'Required. Information about the location of the application '\n                'revision to be uploaded to Amazon S3. You must specify both '\n                'a bucket and a key that represent the Amazon S3 bucket name '\n                'and the object key name. Use the format '\n                's3://\\<bucket\\>/\\<key\\>'\n            )\n        },\n        {\n            'name': 'ignore-hidden-files',\n            'action': 'store_true',\n            'default': False,\n            'group_name': 'ignore-hidden-files',\n            'help_text': (\n                'Optional. Set the --ignore-hidden-files flag to not bundle '\n                'and upload hidden files to Amazon S3; otherwise, set the '\n                '--no-ignore-hidden-files flag (the default) to bundle and '\n                'upload hidden files to Amazon S3.'\n            )\n        },\n        {\n            'name': 'no-ignore-hidden-files',\n            'action': 'store_true',\n            'default': False,\n            'group_name': 'ignore-hidden-files'\n        },\n        {\n            'name': 'source',\n            'synopsis': '--source <path>',\n            'default': '.',\n            'help_text': (\n                'Optional. The location of the deployable content and the '\n                'accompanying AppSpec file on the development machine to be '\n                'bundled and uploaded to Amazon S3. If not specified, the '\n                'current directory is used.'\n            )\n        },\n        {\n            'name': 'description',\n            'synopsis': '--description <description>',\n            'help_text': (\n                'Optional. A comment that summarizes the application '\n                'revision. If not specified, the default string \"Uploaded by '\n                'AWS CLI \\'time\\' UTC\" is used, where \\'time\\' is the current '\n                'system time in Coordinated Universal Time (UTC).'\n            )\n        }\n    ]\n\n    def _run_main(self, parsed_args, parsed_globals):\n        self._validate_args(parsed_args)\n        self.codedeploy = self._session.create_client(\n            'codedeploy',\n            region_name=parsed_globals.region,\n            endpoint_url=parsed_globals.endpoint_url,\n            verify=parsed_globals.verify_ssl\n        )\n        self.s3 = self._session.create_client(\n            's3',\n            region_name=parsed_globals.region\n        )\n        self._push(parsed_args)\n\n    def _validate_args(self, parsed_args):\n        validate_s3_location(parsed_args, 's3_location')\n        if parsed_args.ignore_hidden_files \\\n                and parsed_args.no_ignore_hidden_files:\n            raise RuntimeError(\n                'You cannot specify both --ignore-hidden-files and '\n                '--no-ignore-hidden-files.'\n            )\n        if not parsed_args.description:\n            parsed_args.description = (\n                'Uploaded by AWS CLI {0} UTC'.format(\n                    datetime.utcnow().isoformat()\n                )\n            )\n\n    def _push(self, params):\n        with self._compress(\n                params.source,\n                params.ignore_hidden_files\n        ) as bundle:\n            try:\n                upload_response = self._upload_to_s3(params, bundle)\n                params.eTag = upload_response['ETag'].replace('\"', \"\")\n                if 'VersionId' in upload_response:\n                    params.version = upload_response['VersionId']\n            except Exception as e:\n                raise RuntimeError(\n                    'Failed to upload \\'%s\\' to \\'%s\\': %s' %\n                    (params.source,\n                     params.s3_location,\n                     str(e))\n                )\n        self._register_revision(params)\n\n        if 'version' in params:\n            version_string = ',version={0}'.format(params.version)\n        else:\n            version_string = ''\n        s3location_string = (\n            '--s3-location bucket={0},key={1},'\n            'bundleType=zip,eTag={2}{3}'.format(\n                params.bucket,\n                params.key,\n                params.eTag,\n                version_string\n            )\n        )\n        sys.stdout.write(\n            'To deploy with this revision, run:\\n'\n            'aws deploy create-deployment '\n            '--application-name {0} {1} '\n            '--deployment-group-name <deployment-group-name> '\n            '--deployment-config-name <deployment-config-name> '\n            '--description <description>\\n'.format(\n                params.application_name,\n                s3location_string\n            )\n        )\n\n    @contextlib.contextmanager\n    def _compress(self, source, ignore_hidden_files=False):\n        source_path = os.path.abspath(source)\n        appspec_path = os.path.sep.join([source_path, 'appspec.yml'])\n        with tempfile.TemporaryFile('w+b') as tf:\n            zf = zipfile.ZipFile(tf, 'w', allowZip64=True)\n            # Using 'try'/'finally' instead of 'with' statement since ZipFile\n            # does not have support context manager in Python 2.6.\n            try:\n                contains_appspec = False\n                for root, dirs, files in os.walk(source, topdown=True):\n                    if ignore_hidden_files:\n                        files = [fn for fn in files if not fn.startswith('.')]\n                        dirs[:] = [dn for dn in dirs if not dn.startswith('.')]\n                    for fn in files:\n                        filename = os.path.join(root, fn)\n                        filename = os.path.abspath(filename)\n                        arcname = filename[len(source_path) + 1:]\n                        if filename == appspec_path:\n                            contains_appspec = True\n                        zf.write(filename, arcname, ZIP_COMPRESSION_MODE)\n                if not contains_appspec:\n                    raise RuntimeError(\n                        '{0} was not found'.format(appspec_path)\n                    )\n            finally:\n                zf.close()\n            yield tf\n\n    def _upload_to_s3(self, params, bundle):\n        size_remaining = self._bundle_size(bundle)\n        if size_remaining < MULTIPART_LIMIT:\n            return self.s3.put_object(\n                Bucket=params.bucket,\n                Key=params.key,\n                Body=bundle\n            )\n        else:\n            return self._multipart_upload_to_s3(\n                params,\n                bundle,\n                size_remaining\n            )\n\n    def _bundle_size(self, bundle):\n        bundle.seek(0, 2)\n        size = bundle.tell()\n        bundle.seek(0)\n        return size\n\n    def _multipart_upload_to_s3(self, params, bundle, size_remaining):\n        create_response = self.s3.create_multipart_upload(\n            Bucket=params.bucket,\n            Key=params.key\n        )\n        upload_id = create_response['UploadId']\n        try:\n            part_num = 1\n            multipart_list = []\n            bundle.seek(0)\n            while size_remaining > 0:\n                data = bundle.read(MULTIPART_LIMIT)\n                upload_response = self.s3.upload_part(\n                    Bucket=params.bucket,\n                    Key=params.key,\n                    UploadId=upload_id,\n                    PartNumber=part_num,\n                    Body=six.BytesIO(data)\n                )\n                multipart_list.append({\n                    'PartNumber': part_num,\n                    'ETag': upload_response['ETag']\n                })\n                part_num += 1\n                size_remaining -= len(data)\n            return self.s3.complete_multipart_upload(\n                Bucket=params.bucket,\n                Key=params.key,\n                UploadId=upload_id,\n                MultipartUpload={'Parts': multipart_list}\n            )\n        except ClientError as e:\n            self.s3.abort_multipart_upload(\n                Bucket=params.bucket,\n                Key=params.key,\n                UploadId=upload_id\n            )\n            raise e\n\n    def _register_revision(self, params):\n        revision = {\n            'revisionType': 'S3',\n            's3Location': {\n                'bucket': params.bucket,\n                'key': params.key,\n                'bundleType': 'zip',\n                'eTag': params.eTag\n            }\n        }\n        if 'version' in params:\n            revision['s3Location']['version'] = params.version\n        self.codedeploy.register_application_revision(\n            applicationName=params.application_name,\n            revision=revision,\n            description=params.description\n        )\n", "path": "awscli/customizations/codedeploy/push.py"}], "after_files": [{"content": "# Copyright 2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\"). You\n# may not use this file except in compliance with the License. A copy of\n# the License is located at\n#\n# http://aws.amazon.com/apache2.0/\n#\n# or in the \"license\" file accompanying this file. This file is\n# distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF\n# ANY KIND, either express or implied. See the License for the specific\n# language governing permissions and limitations under the License.\n\nimport os\nimport sys\nimport zipfile\nimport tempfile\nimport contextlib\nfrom datetime import datetime\n\nfrom botocore.exceptions import ClientError\n\nfrom awscli.compat import six\nfrom awscli.customizations.codedeploy.utils import validate_s3_location\nfrom awscli.customizations.commands import BasicCommand\nfrom awscli.compat import ZIP_COMPRESSION_MODE\n\n\nONE_MB = 1 << 20\nMULTIPART_LIMIT = 6 * ONE_MB\n\n\nclass Push(BasicCommand):\n    NAME = 'push'\n\n    DESCRIPTION = (\n        'Bundles and uploads to Amazon Simple Storage Service (Amazon S3) an '\n        'application revision, which is a zip archive file that contains '\n        'deployable content and an accompanying Application Specification '\n        'file (AppSpec file). If the upload is successful, a message is '\n        'returned that describes how to call the create-deployment command to '\n        'deploy the application revision from Amazon S3 to target Amazon '\n        'Elastic Compute Cloud (Amazon EC2) instances.'\n    )\n\n    ARG_TABLE = [\n        {\n            'name': 'application-name',\n            'synopsis': '--application-name <app-name>',\n            'required': True,\n            'help_text': (\n                'Required. The name of the AWS CodeDeploy application to be '\n                'associated with the application revision.'\n            )\n        },\n        {\n            'name': 's3-location',\n            'synopsis': '--s3-location s3://<bucket>/<key>',\n            'required': True,\n            'help_text': (\n                'Required. Information about the location of the application '\n                'revision to be uploaded to Amazon S3. You must specify both '\n                'a bucket and a key that represent the Amazon S3 bucket name '\n                'and the object key name. Content will be zipped before '\n                'uploading. Use the format s3://\\<bucket\\>/\\<key\\>'\n            )\n        },\n        {\n            'name': 'ignore-hidden-files',\n            'action': 'store_true',\n            'default': False,\n            'group_name': 'ignore-hidden-files',\n            'help_text': (\n                'Optional. Set the --ignore-hidden-files flag to not bundle '\n                'and upload hidden files to Amazon S3; otherwise, set the '\n                '--no-ignore-hidden-files flag (the default) to bundle and '\n                'upload hidden files to Amazon S3.'\n            )\n        },\n        {\n            'name': 'no-ignore-hidden-files',\n            'action': 'store_true',\n            'default': False,\n            'group_name': 'ignore-hidden-files'\n        },\n        {\n            'name': 'source',\n            'synopsis': '--source <path>',\n            'default': '.',\n            'help_text': (\n                'Optional. The location of the deployable content and the '\n                'accompanying AppSpec file on the development machine to be '\n                'zipped and uploaded to Amazon S3. If not specified, the '\n                'current directory is used.'\n            )\n        },\n        {\n            'name': 'description',\n            'synopsis': '--description <description>',\n            'help_text': (\n                'Optional. A comment that summarizes the application '\n                'revision. If not specified, the default string \"Uploaded by '\n                'AWS CLI \\'time\\' UTC\" is used, where \\'time\\' is the current '\n                'system time in Coordinated Universal Time (UTC).'\n            )\n        }\n    ]\n\n    def _run_main(self, parsed_args, parsed_globals):\n        self._validate_args(parsed_args)\n        self.codedeploy = self._session.create_client(\n            'codedeploy',\n            region_name=parsed_globals.region,\n            endpoint_url=parsed_globals.endpoint_url,\n            verify=parsed_globals.verify_ssl\n        )\n        self.s3 = self._session.create_client(\n            's3',\n            region_name=parsed_globals.region\n        )\n        self._push(parsed_args)\n\n    def _validate_args(self, parsed_args):\n        validate_s3_location(parsed_args, 's3_location')\n        if parsed_args.ignore_hidden_files \\\n                and parsed_args.no_ignore_hidden_files:\n            raise RuntimeError(\n                'You cannot specify both --ignore-hidden-files and '\n                '--no-ignore-hidden-files.'\n            )\n        if not parsed_args.description:\n            parsed_args.description = (\n                'Uploaded by AWS CLI {0} UTC'.format(\n                    datetime.utcnow().isoformat()\n                )\n            )\n\n    def _push(self, params):\n        with self._compress(\n                params.source,\n                params.ignore_hidden_files\n        ) as bundle:\n            try:\n                upload_response = self._upload_to_s3(params, bundle)\n                params.eTag = upload_response['ETag'].replace('\"', \"\")\n                if 'VersionId' in upload_response:\n                    params.version = upload_response['VersionId']\n            except Exception as e:\n                raise RuntimeError(\n                    'Failed to upload \\'%s\\' to \\'%s\\': %s' %\n                    (params.source,\n                     params.s3_location,\n                     str(e))\n                )\n        self._register_revision(params)\n\n        if 'version' in params:\n            version_string = ',version={0}'.format(params.version)\n        else:\n            version_string = ''\n        s3location_string = (\n            '--s3-location bucket={0},key={1},'\n            'bundleType=zip,eTag={2}{3}'.format(\n                params.bucket,\n                params.key,\n                params.eTag,\n                version_string\n            )\n        )\n        sys.stdout.write(\n            'To deploy with this revision, run:\\n'\n            'aws deploy create-deployment '\n            '--application-name {0} {1} '\n            '--deployment-group-name <deployment-group-name> '\n            '--deployment-config-name <deployment-config-name> '\n            '--description <description>\\n'.format(\n                params.application_name,\n                s3location_string\n            )\n        )\n\n    @contextlib.contextmanager\n    def _compress(self, source, ignore_hidden_files=False):\n        source_path = os.path.abspath(source)\n        appspec_path = os.path.sep.join([source_path, 'appspec.yml'])\n        with tempfile.TemporaryFile('w+b') as tf:\n            zf = zipfile.ZipFile(tf, 'w', allowZip64=True)\n            # Using 'try'/'finally' instead of 'with' statement since ZipFile\n            # does not have support context manager in Python 2.6.\n            try:\n                contains_appspec = False\n                for root, dirs, files in os.walk(source, topdown=True):\n                    if ignore_hidden_files:\n                        files = [fn for fn in files if not fn.startswith('.')]\n                        dirs[:] = [dn for dn in dirs if not dn.startswith('.')]\n                    for fn in files:\n                        filename = os.path.join(root, fn)\n                        filename = os.path.abspath(filename)\n                        arcname = filename[len(source_path) + 1:]\n                        if filename == appspec_path:\n                            contains_appspec = True\n                        zf.write(filename, arcname, ZIP_COMPRESSION_MODE)\n                if not contains_appspec:\n                    raise RuntimeError(\n                        '{0} was not found'.format(appspec_path)\n                    )\n            finally:\n                zf.close()\n            yield tf\n\n    def _upload_to_s3(self, params, bundle):\n        size_remaining = self._bundle_size(bundle)\n        if size_remaining < MULTIPART_LIMIT:\n            return self.s3.put_object(\n                Bucket=params.bucket,\n                Key=params.key,\n                Body=bundle\n            )\n        else:\n            return self._multipart_upload_to_s3(\n                params,\n                bundle,\n                size_remaining\n            )\n\n    def _bundle_size(self, bundle):\n        bundle.seek(0, 2)\n        size = bundle.tell()\n        bundle.seek(0)\n        return size\n\n    def _multipart_upload_to_s3(self, params, bundle, size_remaining):\n        create_response = self.s3.create_multipart_upload(\n            Bucket=params.bucket,\n            Key=params.key\n        )\n        upload_id = create_response['UploadId']\n        try:\n            part_num = 1\n            multipart_list = []\n            bundle.seek(0)\n            while size_remaining > 0:\n                data = bundle.read(MULTIPART_LIMIT)\n                upload_response = self.s3.upload_part(\n                    Bucket=params.bucket,\n                    Key=params.key,\n                    UploadId=upload_id,\n                    PartNumber=part_num,\n                    Body=six.BytesIO(data)\n                )\n                multipart_list.append({\n                    'PartNumber': part_num,\n                    'ETag': upload_response['ETag']\n                })\n                part_num += 1\n                size_remaining -= len(data)\n            return self.s3.complete_multipart_upload(\n                Bucket=params.bucket,\n                Key=params.key,\n                UploadId=upload_id,\n                MultipartUpload={'Parts': multipart_list}\n            )\n        except ClientError as e:\n            self.s3.abort_multipart_upload(\n                Bucket=params.bucket,\n                Key=params.key,\n                UploadId=upload_id\n            )\n            raise e\n\n    def _register_revision(self, params):\n        revision = {\n            'revisionType': 'S3',\n            's3Location': {\n                'bucket': params.bucket,\n                'key': params.key,\n                'bundleType': 'zip',\n                'eTag': params.eTag\n            }\n        }\n        if 'version' in params:\n            revision['s3Location']['version'] = params.version\n        self.codedeploy.register_application_revision(\n            applicationName=params.application_name,\n            revision=revision,\n            description=params.description\n        )\n", "path": "awscli/customizations/codedeploy/push.py"}]}