Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
aws
GitHub Repository: aws/aws-cli
Path: blob/develop/awscli/customizations/codedeploy/push.py
1567 views
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
20
from botocore.exceptions import ClientError
21
22
from awscli.customizations.codedeploy.utils import validate_s3_location
23
from awscli.customizations.commands import BasicCommand
24
from awscli.compat import BytesIO, ZIP_COMPRESSION_MODE, get_current_datetime
25
from awscli.utils import create_nested_client
26
27
ONE_MB = 1 << 20
28
MULTIPART_LIMIT = 6 * ONE_MB
29
30
31
class Push(BasicCommand):
32
NAME = 'push'
33
34
DESCRIPTION = (
35
'Bundles and uploads to Amazon Simple Storage Service (Amazon S3) an '
36
'application revision, which is a zip archive file that contains '
37
'deployable content and an accompanying Application Specification '
38
'file (AppSpec file). If the upload is successful, a message is '
39
'returned that describes how to call the create-deployment command to '
40
'deploy the application revision from Amazon S3 to target Amazon '
41
'Elastic Compute Cloud (Amazon EC2) instances.'
42
)
43
44
ARG_TABLE = [
45
{
46
'name': 'application-name',
47
'synopsis': '--application-name <app-name>',
48
'required': True,
49
'help_text': (
50
'Required. The name of the AWS CodeDeploy application to be '
51
'associated with the application revision.'
52
)
53
},
54
{
55
'name': 's3-location',
56
'synopsis': '--s3-location s3://<bucket>/<key>',
57
'required': True,
58
'help_text': (
59
'Required. Information about the location of the application '
60
'revision to be uploaded to Amazon S3. You must specify both '
61
'a bucket and a key that represent the Amazon S3 bucket name '
62
'and the object key name. Content will be zipped before '
63
'uploading. Use the format s3://<bucket>/<key>'
64
),
65
},
66
{
67
'name': 'ignore-hidden-files',
68
'action': 'store_true',
69
'default': False,
70
'group_name': 'ignore-hidden-files',
71
'help_text': (
72
'Optional. Set the --ignore-hidden-files flag to not bundle '
73
'and upload hidden files to Amazon S3; otherwise, set the '
74
'--no-ignore-hidden-files flag (the default) to bundle and '
75
'upload hidden files to Amazon S3.'
76
)
77
},
78
{
79
'name': 'no-ignore-hidden-files',
80
'action': 'store_true',
81
'default': False,
82
'group_name': 'ignore-hidden-files'
83
},
84
{
85
'name': 'source',
86
'synopsis': '--source <path>',
87
'default': '.',
88
'help_text': (
89
'Optional. The location of the deployable content and the '
90
'accompanying AppSpec file on the development machine to be '
91
'zipped and uploaded to Amazon S3. If not specified, the '
92
'current directory is used.'
93
)
94
},
95
{
96
'name': 'description',
97
'synopsis': '--description <description>',
98
'help_text': (
99
'Optional. A comment that summarizes the application '
100
'revision. If not specified, the default string "Uploaded by '
101
'AWS CLI \'time\' UTC" is used, where \'time\' is the current '
102
'system time in Coordinated Universal Time (UTC).'
103
)
104
}
105
]
106
107
def _run_main(self, parsed_args, parsed_globals):
108
self._validate_args(parsed_args)
109
self.codedeploy = create_nested_client(
110
self._session,
111
'codedeploy',
112
region_name=parsed_globals.region,
113
endpoint_url=parsed_globals.endpoint_url,
114
verify=parsed_globals.verify_ssl
115
)
116
self.s3 = create_nested_client(
117
self._session,
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
get_current_datetime().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=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
289