Skip to content
This repository was archived by the owner on Aug 31, 2021. It is now read-only.

Commit e2126ce

Browse files
authored
chore: Wrap botocore ClientError into ServerlessRepoClientError (#25)
1 parent 81b159b commit e2126ce

File tree

4 files changed

+66
-45
lines changed

4 files changed

+66
-45
lines changed

serverlessrepo/__version__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""Serverlessrepo version and package meta-data."""
22

33
__title__ = 'serverlessrepo'
4-
__version__ = '0.1.7'
4+
__version__ = '0.1.8'
55
__license__ = 'Apache 2.0'
66
__description__ = (
77
'A Python library with convenience helpers for working '

serverlessrepo/exceptions.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,15 @@ class S3PermissionsRequired(ServerlessRepoError):
3737
"permissions to the application artifacts you have uploaded to your S3 bucket. See " \
3838
"https://docs.aws.amazon.com/serverlessrepo/latest/devguide/serverless-app-publishing-applications.html" \
3939
" for more details."
40+
41+
42+
class InvalidS3UriError(ServerlessRepoError):
43+
"""Raised when the template contains invalid S3 URIs."""
44+
45+
MESSAGE = "{message}"
46+
47+
48+
class ServerlessRepoClientError(ServerlessRepoError):
49+
"""Wrapper for botocore ClientError."""
50+
51+
MESSAGE = "{message}"

serverlessrepo/publish.py

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
yaml_dump, parse_template, get_app_metadata,
1212
parse_application_id, strip_app_metadata
1313
)
14-
from .exceptions import S3PermissionsRequired
14+
from .exceptions import ServerlessRepoClientError, S3PermissionsRequired, InvalidS3UriError
1515

1616
CREATE_APPLICATION = 'CREATE_APPLICATION'
1717
UPDATE_APPLICATION = 'UPDATE_APPLICATION'
@@ -47,7 +47,7 @@ def publish_application(template, sar_client=None):
4747
actions = [CREATE_APPLICATION]
4848
except ClientError as e:
4949
if not _is_conflict_exception(e):
50-
raise _wrap_s3_exception(e)
50+
raise _wrap_client_error(e)
5151

5252
# Update the application if it already exists
5353
error_message = e.response['Error']['Message']
@@ -57,7 +57,7 @@ def publish_application(template, sar_client=None):
5757
sar_client.update_application(**request)
5858
actions = [UPDATE_APPLICATION]
5959
except ClientError as e:
60-
raise _wrap_s3_exception(e)
60+
raise _wrap_client_error(e)
6161

6262
# Create application version if semantic version is specified
6363
if app_metadata.semantic_version:
@@ -67,7 +67,7 @@ def publish_application(template, sar_client=None):
6767
actions.append(CREATE_APPLICATION_VERSION)
6868
except ClientError as e:
6969
if not _is_conflict_exception(e):
70-
raise _wrap_s3_exception(e)
70+
raise _wrap_client_error(e)
7171

7272
return {
7373
'application_id': application_id,
@@ -195,33 +195,36 @@ def _create_application_version_request(app_metadata, application_id, template):
195195

196196
def _is_conflict_exception(e):
197197
"""
198-
Check whether the boto3 ClientError is ConflictException.
198+
Check whether the botocore ClientError is ConflictException.
199199
200-
:param e: boto3 exception
200+
:param e: botocore exception
201201
:type e: ClientError
202202
:return: True if e is ConflictException
203203
"""
204204
error_code = e.response['Error']['Code']
205205
return error_code == 'ConflictException'
206206

207207

208-
def _wrap_s3_exception(e):
208+
def _wrap_client_error(e):
209209
"""
210-
Wrap S3 access denied exception with a better error message.
210+
Wrap botocore ClientError exception into ServerlessRepoClientError.
211211
212-
:param e: boto3 exception
212+
:param e: botocore exception
213213
:type e: ClientError
214-
:return: S3PermissionsRequired if S3 access denied or the original exception
214+
:return: S3PermissionsRequired or InvalidS3UriError or general ServerlessRepoClientError
215215
"""
216216
error_code = e.response['Error']['Code']
217217
message = e.response['Error']['Message']
218218

219-
if error_code == 'BadRequestException' and "Failed to copy S3 object. Access denied:" in message:
220-
match = re.search('bucket=(.+?), key=(.+?)$', message)
221-
if match:
222-
return S3PermissionsRequired(bucket=match.group(1), key=match.group(2))
219+
if error_code == 'BadRequestException':
220+
if "Failed to copy S3 object. Access denied:" in message:
221+
match = re.search('bucket=(.+?), key=(.+?)$', message)
222+
if match:
223+
return S3PermissionsRequired(bucket=match.group(1), key=match.group(2))
224+
if "Invalid S3 URI" in message:
225+
return InvalidS3UriError(message=message)
223226

224-
return e
227+
return ServerlessRepoClientError(message=message)
225228

226229

227230
def _get_publish_details(actions, app_metadata_template):

tests/unit/test_publish.py

Lines changed: 35 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,12 @@
55
from botocore.exceptions import ClientError
66

77
from serverlessrepo import publish_application, update_application_metadata
8-
from serverlessrepo.exceptions import InvalidApplicationMetadataError, S3PermissionsRequired
8+
from serverlessrepo.exceptions import (
9+
InvalidApplicationMetadataError,
10+
S3PermissionsRequired,
11+
InvalidS3UriError,
12+
ServerlessRepoClientError
13+
)
914
from serverlessrepo.parser import get_app_metadata, strip_app_metadata, yaml_dump
1015
from serverlessrepo.publish import (
1116
CREATE_APPLICATION,
@@ -70,6 +75,15 @@ def setUp(self):
7075
},
7176
'create_application'
7277
)
78+
self.invalid_s3_uri_exception = ClientError(
79+
{
80+
'Error': {
81+
'Code': 'BadRequestException',
82+
'Message': 'Invalid S3 URI'
83+
}
84+
},
85+
'create_application'
86+
)
7387

7488
def test_publish_raise_value_error_for_empty_template(self):
7589
with self.assertRaises(ValueError) as context:
@@ -137,18 +151,18 @@ def test_publish_raise_metadata_error_for_invalid_create_application_request(sel
137151
# create_application shouldn't be called if application metadata is invalid
138152
self.serverlessrepo_mock.create_application.assert_not_called()
139153

140-
def test_publish_raise_client_error_when_create_application(self):
154+
def test_publish_raise_serverlessrepo_client_error_when_create_application(self):
141155
self.serverlessrepo_mock.create_application.side_effect = self.not_conflict_exception
142156

143157
# should raise exception if it's not ConflictException
144-
with self.assertRaises(ClientError):
158+
with self.assertRaises(ServerlessRepoClientError):
145159
publish_application(self.template)
146160

147161
# shouldn't call the following APIs if the exception isn't application already exists
148162
self.serverlessrepo_mock.update_application.assert_not_called()
149163
self.serverlessrepo_mock.create_application_version.assert_not_called()
150164

151-
def test_publish_raise_s3_error_when_create_application(self):
165+
def test_publish_raise_s3_permission_error_when_create_application(self):
152166
self.serverlessrepo_mock.create_application.side_effect = self.s3_denied_exception
153167
with self.assertRaises(S3PermissionsRequired) as context:
154168
publish_application(self.template)
@@ -157,9 +171,13 @@ def test_publish_raise_s3_error_when_create_application(self):
157171
self.assertIn("The AWS Serverless Application Repository does not have read access to bucket "
158172
"'test-bucket', key 'test-file'.", message)
159173

160-
# shouldn't call the following APIs if the exception isn't application already exists
161-
self.serverlessrepo_mock.update_application.assert_not_called()
162-
self.serverlessrepo_mock.create_application_version.assert_not_called()
174+
def test_publish_raise_invalid_s3_uri_when_create_application(self):
175+
self.serverlessrepo_mock.create_application.side_effect = self.invalid_s3_uri_exception
176+
with self.assertRaises(InvalidS3UriError) as context:
177+
publish_application(self.template)
178+
179+
message = str(context.exception)
180+
self.assertIn("Invalid S3 URI", message)
163181

164182
def test_publish_existing_application_should_update_application_if_version_not_specified(self):
165183
self.serverlessrepo_mock.create_application.side_effect = self.application_exists_error
@@ -187,23 +205,21 @@ def test_publish_existing_application_should_update_application_if_version_not_s
187205
# create_application_version shouldn't be called if version is not provided
188206
self.serverlessrepo_mock.create_application_version.assert_not_called()
189207

190-
def test_publish_raise_s3_error_when_update_application(self):
208+
@patch('serverlessrepo.publish._wrap_client_error')
209+
def test_publish_wrap_client_error_when_update_application(self, wrap_client_error_mock):
191210
self.serverlessrepo_mock.create_application.side_effect = self.application_exists_error
192-
self.serverlessrepo_mock.update_application.side_effect = self.s3_denied_exception
193-
with self.assertRaises(S3PermissionsRequired) as context:
211+
self.serverlessrepo_mock.update_application.side_effect = self.not_conflict_exception
212+
wrap_client_error_mock.return_value = ServerlessRepoClientError(message="client error")
213+
with self.assertRaises(ServerlessRepoClientError):
194214
publish_application(self.template)
195215

196-
message = str(context.exception)
197-
self.assertIn("The AWS Serverless Application Repository does not have read access to bucket "
198-
"'test-bucket', key 'test-file'.", message)
199-
200216
# create_application_version shouldn't be called if update_application fails
201217
self.serverlessrepo_mock.create_application_version.assert_not_called()
202218

203219
def test_publish_existing_application_should_update_application_if_version_exists(self):
204220
self.serverlessrepo_mock.create_application.side_effect = self.application_exists_error
205221
self.serverlessrepo_mock.create_application_version.side_effect = ClientError(
206-
{'Error': {'Code': 'ConflictException'}},
222+
{'Error': {'Code': 'ConflictException', 'Message': 'Random'}},
207223
'create_application_version'
208224
)
209225

@@ -257,24 +273,14 @@ def test_publish_new_version_should_create_application_version(self):
257273
}
258274
self.serverlessrepo_mock.create_application_version.assert_called_once_with(**expected_request)
259275

260-
def test_publish_raise_client_error_when_create_application_version(self):
276+
@patch('serverlessrepo.publish._wrap_client_error')
277+
def test_publish_wrap_client_error_when_create_application_version(self, wrap_client_error_mock):
261278
self.serverlessrepo_mock.create_application.side_effect = self.application_exists_error
262279
self.serverlessrepo_mock.create_application_version.side_effect = self.not_conflict_exception
263-
264-
# should raise exception if it's not ConflictException
265-
with self.assertRaises(ClientError):
266-
publish_application(self.template)
267-
268-
def test_publish_raise_s3_error_when_create_application_version(self):
269-
self.serverlessrepo_mock.create_application.side_effect = self.application_exists_error
270-
self.serverlessrepo_mock.create_application_version.side_effect = self.s3_denied_exception
271-
with self.assertRaises(S3PermissionsRequired) as context:
280+
wrap_client_error_mock.return_value = ServerlessRepoClientError(message="client error")
281+
with self.assertRaises(ServerlessRepoClientError):
272282
publish_application(self.template)
273283

274-
message = str(context.exception)
275-
self.assertIn("The AWS Serverless Application Repository does not have read access to bucket "
276-
"'test-bucket', key 'test-file'.", message)
277-
278284
def test_create_application_with_passed_in_sar_client(self):
279285
sar_client = Mock()
280286
sar_client.create_application.return_value = {

0 commit comments

Comments
 (0)