diff --git a/awscli/customizations/s3errormsg.py b/awscli/customizations/s3errormsg.py new file mode 100644 index 000000000000..3c42ebb0b395 --- /dev/null +++ b/awscli/customizations/s3errormsg.py @@ -0,0 +1,56 @@ +# Copyright 2014 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. +"""Give better S3 error messages. +""" +from awscli.customizations import utils + + +REGION_ERROR_MSG = ( + 'You can fix this issue by explicity providing the correct region ' + 'location using the --region argument, the AWS_DEFAULT_REGION ' + 'environment variable, or the region variable in the AWS CLI ' + "configuration file. You can get the bucket's location by " + 'running "aws s3api get-bucket-location --bucket BUCKET".' +) + + +def register_s3_error_msg(event_handlers): + event_handlers.register('after-call.s3', enhance_error_msg) + + +def enhance_error_msg(parsed, **kwargs): + if parsed is None or 'Error' not in parsed: + # There's no error message to enhance so we can continue. + return + if _is_sigv4_error_message(parsed): + message = ( + 'You are attempting to operate on a bucket in a region ' + 'that requires Signature Version 4. ' + ) + message += REGION_ERROR_MSG + parsed['Error']['Message'] = message + elif _is_permanent_redirect_message(parsed): + endpoint = parsed['Error']['Endpoint'] + message = parsed['Error']['Message'] + new_message = message[:-1] + ': %s\n' % endpoint + new_message += REGION_ERROR_MSG + parsed['Error']['Message'] = new_message + + +def _is_sigv4_error_message(parsed): + return ('Please use AWS4-HMAC-SHA256' in + parsed.get('Error', {}).get('Message', '')) + + +def _is_permanent_redirect_message(parsed): + return parsed.get('Error', {}).get('Code', '') == 'PermanentRedirect' diff --git a/awscli/handlers.py b/awscli/handlers.py index 4d1d2aaaac42..7eceb4ebcda3 100644 --- a/awscli/handlers.py +++ b/awscli/handlers.py @@ -47,14 +47,18 @@ from awscli.customizations.emr.emr import emr_initialize from awscli.customizations.cloudsearchdomain import register_cloudsearchdomain from awscli.customizations.s3endpoint import register_s3_endpoint +from awscli.customizations.s3errormsg import register_s3_error_msg def awscli_initialize(event_handlers): event_handlers.register('load-cli-arg', uri_param) param_shorthand = ParamShorthand() event_handlers.register('process-cli-arg', param_shorthand) + # The s3 error mesage needs to registered before the + # generic error handler. + register_s3_error_msg(event_handlers) error_handler = ErrorHandler() - event_handlers.register('after-call.*.*', error_handler) + event_handlers.register('after-call', error_handler) # # The following will get fired for every option we are # # documenting. It will attempt to add an example_fn on to # # the parameter object if the parameter supports shorthand diff --git a/tests/unit/customizations/test_s3errormsg.py b/tests/unit/customizations/test_s3errormsg.py new file mode 100644 index 000000000000..d901385a1649 --- /dev/null +++ b/tests/unit/customizations/test_s3errormsg.py @@ -0,0 +1,69 @@ +# Copyright 2014 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0e +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. +import copy + +from awscli.testutils import unittest +from awscli.customizations import s3errormsg + + +class TestGetRegionFromEndpoint(unittest.TestCase): + + def test_sigv4_error_message(self): + parsed = { + 'Error': { + 'Message': 'Please use AWS4-HMAC-SHA256' + } + } + s3errormsg.enhance_error_msg(parsed) + # We should say how to fix the issue. + self.assertIn('You can fix this issue', + parsed['Error']['Message']) + # We should mention the --region argument. + self.assertIn('--region', parsed['Error']['Message']) + # We should mention get-bucket-location + self.assertIn('get-bucket-location', parsed['Error']['Message']) + + def test_301_error_message(self): + parsed = { + 'Error': { + 'Code': 'PermanentRedirect', + 'Message': "Please use the correct endpoint.", + 'Endpoint': "myendpoint", + } + } + s3errormsg.enhance_error_msg(parsed) + # We should include the endpoint in the error message. + error_message = parsed['Error']['Message'] + self.assertIn('myendpoint', error_message) + + def test_error_message_not_enhanced(self): + parsed = { + 'Error': { + 'Message': 'This is a different error message.', + 'Code': 'Other Message' + } + } + expected = copy.deepcopy(parsed) + s3errormsg.enhance_error_msg(parsed) + # Nothing should have changed + self.assertEqual(parsed, expected) + + def test_not_an_error_message(self): + parsed = { + 'Success': 'response', + 'ResponseMetadata': {} + } + expected = copy.deepcopy(parsed) + s3errormsg.enhance_error_msg(parsed) + # Nothing should have changed + self.assertEqual(parsed, expected)