1# Copyright (c) 2012-2013 Mitch Garnaat http://garnaat.org/
2# Copyright 2012-2014 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License"). You
5# may not use this file except in compliance with the License. A copy of
6# the License is located at
7#
8# http://aws.amazon.com/apache2.0/
9#
10# or in the "license" file accompanying this file. This file is
11# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
12# ANY KIND, either express or implied. See the License for the specific
13# language governing permissions and limitations under the License.
14from __future__ import unicode_literals
15from botocore.vendored import requests
16from botocore.vendored.requests.packages import urllib3
17
18
19def _exception_from_packed_args(exception_cls, args=None, kwargs=None):
20    # This is helpful for reducing Exceptions that only accept kwargs as
21    # only positional arguments can be provided for __reduce__
22    # Ideally, this would also be a class method on the BotoCoreError
23    # but instance methods cannot be pickled.
24    if args is None:
25        args = ()
26    if kwargs is None:
27        kwargs = {}
28    return exception_cls(*args, **kwargs)
29
30
31class BotoCoreError(Exception):
32    """
33    The base exception class for BotoCore exceptions.
34
35    :ivar msg: The descriptive message associated with the error.
36    """
37    fmt = 'An unspecified error occurred'
38
39    def __init__(self, **kwargs):
40        msg = self.fmt.format(**kwargs)
41        Exception.__init__(self, msg)
42        self.kwargs = kwargs
43
44    def __reduce__(self):
45        return _exception_from_packed_args, (self.__class__, None, self.kwargs)
46
47
48class DataNotFoundError(BotoCoreError):
49    """
50    The data associated with a particular path could not be loaded.
51
52    :ivar data_path: The data path that the user attempted to load.
53    """
54    fmt = 'Unable to load data for: {data_path}'
55
56
57class UnknownServiceError(DataNotFoundError):
58    """Raised when trying to load data for an unknown service.
59
60    :ivar service_name: The name of the unknown service.
61
62    """
63    fmt = (
64        "Unknown service: '{service_name}'. Valid service names are: "
65        "{known_service_names}")
66
67
68class ApiVersionNotFoundError(BotoCoreError):
69    """
70    The data associated with either the API version or a compatible one
71    could not be loaded.
72
73    :ivar data_path: The data path that the user attempted to load.
74    :ivar api_version: The API version that the user attempted to load.
75    """
76    fmt = 'Unable to load data {data_path} for: {api_version}'
77
78
79class HTTPClientError(BotoCoreError):
80    fmt = 'An HTTP Client raised an unhandled exception: {error}'
81    def __init__(self, request=None, response=None, **kwargs):
82        self.request = request
83        self.response = response
84        super(HTTPClientError, self).__init__(**kwargs)
85
86    def __reduce__(self):
87        return _exception_from_packed_args, (
88            self.__class__, (self.request, self.response), self.kwargs)
89
90
91class ConnectionError(BotoCoreError):
92    fmt = 'An HTTP Client failed to establish a connection: {error}'
93
94
95class EndpointConnectionError(ConnectionError):
96    fmt = 'Could not connect to the endpoint URL: "{endpoint_url}"'
97
98
99class SSLError(ConnectionError, requests.exceptions.SSLError):
100    fmt = 'SSL validation failed for {endpoint_url} {error}'
101
102
103class ConnectionClosedError(HTTPClientError):
104    fmt = (
105        'Connection was closed before we received a valid response '
106        'from endpoint URL: "{endpoint_url}".')
107
108
109class ReadTimeoutError(HTTPClientError, requests.exceptions.ReadTimeout,
110                       urllib3.exceptions.ReadTimeoutError):
111    fmt = 'Read timeout on endpoint URL: "{endpoint_url}"'
112
113
114class ConnectTimeoutError(ConnectionError, requests.exceptions.ConnectTimeout):
115    fmt = 'Connect timeout on endpoint URL: "{endpoint_url}"'
116
117
118class ProxyConnectionError(ConnectionError, requests.exceptions.ProxyError):
119    fmt = 'Failed to connect to proxy URL: "{proxy_url}"'
120
121
122class NoCredentialsError(BotoCoreError):
123    """
124    No credentials could be found.
125    """
126    fmt = 'Unable to locate credentials'
127
128
129class PartialCredentialsError(BotoCoreError):
130    """
131    Only partial credentials were found.
132
133    :ivar cred_var: The missing credential variable name.
134
135    """
136    fmt = 'Partial credentials found in {provider}, missing: {cred_var}'
137
138
139class CredentialRetrievalError(BotoCoreError):
140    """
141    Error attempting to retrieve credentials from a remote source.
142
143    :ivar provider: The name of the credential provider.
144    :ivar error_msg: The msg explaining why credentials could not be
145        retrieved.
146
147    """
148    fmt = 'Error when retrieving credentials from {provider}: {error_msg}'
149
150
151class UnknownSignatureVersionError(BotoCoreError):
152    """
153    Requested Signature Version is not known.
154
155    :ivar signature_version: The name of the requested signature version.
156    """
157    fmt = 'Unknown Signature Version: {signature_version}.'
158
159
160class ServiceNotInRegionError(BotoCoreError):
161    """
162    The service is not available in requested region.
163
164    :ivar service_name: The name of the service.
165    :ivar region_name: The name of the region.
166    """
167    fmt = 'Service {service_name} not available in region {region_name}'
168
169
170class BaseEndpointResolverError(BotoCoreError):
171    """Base error for endpoint resolving errors.
172
173    Should never be raised directly, but clients can catch
174    this exception if they want to generically handle any errors
175    during the endpoint resolution process.
176
177    """
178
179
180class NoRegionError(BaseEndpointResolverError):
181    """No region was specified."""
182    fmt = 'You must specify a region.'
183
184
185class UnknownEndpointError(BaseEndpointResolverError, ValueError):
186    """
187    Could not construct an endpoint.
188
189    :ivar service_name: The name of the service.
190    :ivar region_name: The name of the region.
191    """
192    fmt = (
193        'Unable to construct an endpoint for '
194        '{service_name} in region {region_name}')
195
196
197class ProfileNotFound(BotoCoreError):
198    """
199    The specified configuration profile was not found in the
200    configuration file.
201
202    :ivar profile: The name of the profile the user attempted to load.
203    """
204    fmt = 'The config profile ({profile}) could not be found'
205
206
207class ConfigParseError(BotoCoreError):
208    """
209    The configuration file could not be parsed.
210
211    :ivar path: The path to the configuration file.
212    """
213    fmt = 'Unable to parse config file: {path}'
214
215
216class ConfigNotFound(BotoCoreError):
217    """
218    The specified configuration file could not be found.
219
220    :ivar path: The path to the configuration file.
221    """
222    fmt = 'The specified config file ({path}) could not be found.'
223
224
225class MissingParametersError(BotoCoreError):
226    """
227    One or more required parameters were not supplied.
228
229    :ivar object: The object that has missing parameters.
230        This can be an operation or a parameter (in the
231        case of inner params).  The str() of this object
232        will be used so it doesn't need to implement anything
233        other than str().
234    :ivar missing: The names of the missing parameters.
235    """
236    fmt = ('The following required parameters are missing for '
237           '{object_name}: {missing}')
238
239
240class ValidationError(BotoCoreError):
241    """
242    An exception occurred validating parameters.
243
244    Subclasses must accept a ``value`` and ``param``
245    argument in their ``__init__``.
246
247    :ivar value: The value that was being validated.
248    :ivar param: The parameter that failed validation.
249    :ivar type_name: The name of the underlying type.
250    """
251    fmt = ("Invalid value ('{value}') for param {param} "
252           "of type {type_name} ")
253
254
255class ParamValidationError(BotoCoreError):
256    fmt = 'Parameter validation failed:\n{report}'
257
258
259# These exceptions subclass from ValidationError so that code
260# can just 'except ValidationError' to catch any possibly validation
261# error.
262class UnknownKeyError(ValidationError):
263    """
264    Unknown key in a struct parameter.
265
266    :ivar value: The value that was being checked.
267    :ivar param: The name of the parameter.
268    :ivar choices: The valid choices the value can be.
269    """
270    fmt = ("Unknown key '{value}' for param '{param}'.  Must be one "
271           "of: {choices}")
272
273
274class RangeError(ValidationError):
275    """
276    A parameter value was out of the valid range.
277
278    :ivar value: The value that was being checked.
279    :ivar param: The parameter that failed validation.
280    :ivar min_value: The specified minimum value.
281    :ivar max_value: The specified maximum value.
282    """
283    fmt = ('Value out of range for param {param}: '
284           '{min_value} <= {value} <= {max_value}')
285
286
287class UnknownParameterError(ValidationError):
288    """
289    Unknown top level parameter.
290
291    :ivar name: The name of the unknown parameter.
292    :ivar operation: The name of the operation.
293    :ivar choices: The valid choices the parameter name can be.
294    """
295    fmt = (
296        "Unknown parameter '{name}' for operation {operation}.  Must be one "
297        "of: {choices}"
298    )
299
300
301class AliasConflictParameterError(ValidationError):
302    """
303    Error when an alias is provided for a parameter as well as the original.
304
305    :ivar original: The name of the original parameter.
306    :ivar alias: The name of the alias
307    :ivar operation: The name of the operation.
308    """
309    fmt = (
310        "Parameter '{original}' and its alias '{alias}' were provided "
311        "for operation {operation}.  Only one of them may be used."
312    )
313
314
315class UnknownServiceStyle(BotoCoreError):
316    """
317    Unknown style of service invocation.
318
319    :ivar service_style: The style requested.
320    """
321    fmt = 'The service style ({service_style}) is not understood.'
322
323
324class PaginationError(BotoCoreError):
325    fmt = 'Error during pagination: {message}'
326
327
328class OperationNotPageableError(BotoCoreError):
329    fmt = 'Operation cannot be paginated: {operation_name}'
330
331
332class ChecksumError(BotoCoreError):
333    """The expected checksum did not match the calculated checksum.
334
335    """
336    fmt = ('Checksum {checksum_type} failed, expected checksum '
337           '{expected_checksum} did not match calculated checksum '
338           '{actual_checksum}.')
339
340
341class UnseekableStreamError(BotoCoreError):
342    """Need to seek a stream, but stream does not support seeking.
343
344    """
345    fmt = ('Need to rewind the stream {stream_object}, but stream '
346           'is not seekable.')
347
348
349class WaiterError(BotoCoreError):
350    """Waiter failed to reach desired state."""
351    fmt = 'Waiter {name} failed: {reason}'
352
353    def __init__(self, name, reason, last_response):
354        super(WaiterError, self).__init__(name=name, reason=reason)
355        self.last_response = last_response
356
357
358class IncompleteReadError(BotoCoreError):
359    """HTTP response did not return expected number of bytes."""
360    fmt = ('{actual_bytes} read, but total bytes '
361           'expected is {expected_bytes}.')
362
363
364class InvalidExpressionError(BotoCoreError):
365    """Expression is either invalid or too complex."""
366    fmt = 'Invalid expression {expression}: Only dotted lookups are supported.'
367
368
369class UnknownCredentialError(BotoCoreError):
370    """Tried to insert before/after an unregistered credential type."""
371    fmt = 'Credential named {name} not found.'
372
373
374class WaiterConfigError(BotoCoreError):
375    """Error when processing waiter configuration."""
376    fmt = 'Error processing waiter config: {error_msg}'
377
378
379class UnknownClientMethodError(BotoCoreError):
380    """Error when trying to access a method on a client that does not exist."""
381    fmt = 'Client does not have method: {method_name}'
382
383
384class UnsupportedSignatureVersionError(BotoCoreError):
385    """Error when trying to use an unsupported Signature Version."""
386    fmt = 'Signature version is not supported: {signature_version}'
387
388
389class ClientError(Exception):
390    MSG_TEMPLATE = (
391        'An error occurred ({error_code}) when calling the {operation_name} '
392        'operation{retry_info}: {error_message}')
393
394    def __init__(self, error_response, operation_name):
395        retry_info = self._get_retry_info(error_response)
396        error = error_response.get('Error', {})
397        msg = self.MSG_TEMPLATE.format(
398            error_code=error.get('Code', 'Unknown'),
399            error_message=error.get('Message', 'Unknown'),
400            operation_name=operation_name,
401            retry_info=retry_info,
402        )
403        super(ClientError, self).__init__(msg)
404        self.response = error_response
405        self.operation_name = operation_name
406
407    def _get_retry_info(self, response):
408        retry_info = ''
409        if 'ResponseMetadata' in response:
410            metadata = response['ResponseMetadata']
411            if metadata.get('MaxAttemptsReached', False):
412                if 'RetryAttempts' in metadata:
413                    retry_info = (' (reached max retries: %s)' %
414                                  metadata['RetryAttempts'])
415        return retry_info
416
417    def __reduce__(self):
418        # Subclasses of ClientError's are dynamically generated and
419        # cannot be pickled unless they are attributes of a
420        # module. So at the very least return a ClientError back.
421        return ClientError, (self.response, self.operation_name)
422
423
424class EventStreamError(ClientError):
425    pass
426
427
428class UnsupportedTLSVersionWarning(Warning):
429    """Warn when an openssl version that uses TLS 1.2 is required"""
430    pass
431
432
433class ImminentRemovalWarning(Warning):
434    pass
435
436
437class InvalidDNSNameError(BotoCoreError):
438    """Error when virtual host path is forced on a non-DNS compatible bucket"""
439    fmt = (
440        'Bucket named {bucket_name} is not DNS compatible. Virtual '
441        'hosted-style addressing cannot be used. The addressing style '
442        'can be configured by removing the addressing_style value '
443        'or setting that value to \'path\' or \'auto\' in the AWS Config '
444        'file or in the botocore.client.Config object.'
445    )
446
447
448class InvalidS3AddressingStyleError(BotoCoreError):
449    """Error when an invalid path style is specified"""
450    fmt = (
451        'S3 addressing style {s3_addressing_style} is invalid. Valid options '
452        'are: \'auto\', \'virtual\', and \'path\''
453    )
454
455
456class UnsupportedS3ArnError(BotoCoreError):
457    """Error when S3 ARN provided to Bucket parameter is not supported"""
458    fmt = (
459        'S3 ARN {arn} provided to "Bucket" parameter is invalid. Only '
460        'ARNs for S3 access-points are supported.'
461    )
462
463
464class UnsupportedS3AccesspointConfigurationError(BotoCoreError):
465    """Error when an unsupported configuration is used with access-points"""
466    fmt = (
467        'Unsupported configuration when using S3 access-points: {msg}'
468    )
469
470
471class InvalidRetryConfigurationError(BotoCoreError):
472    """Error when invalid retry configuration is specified"""
473    fmt = (
474        'Cannot provide retry configuration for "{retry_config_option}". '
475        'Valid retry configuration options are: \'max_attempts\''
476    )
477
478
479class InvalidMaxRetryAttemptsError(InvalidRetryConfigurationError):
480    """Error when invalid retry configuration is specified"""
481    fmt = (
482        'Value provided to "max_attempts": {provided_max_attempts} must '
483        'be an integer greater than or equal to {min_value}.'
484    )
485
486
487class InvalidRetryModeError(InvalidRetryConfigurationError):
488    """Error when invalid retry mode configuration is specified"""
489    fmt = (
490        'Invalid value provided to "mode": "{provided_retry_mode}" must '
491        'be one of: "legacy", "standard", "adaptive"'
492    )
493
494
495class InvalidS3UsEast1RegionalEndpointConfigError(BotoCoreError):
496    """Error for invalid s3 us-east-1 regional endpoints configuration"""
497    fmt = (
498        'S3 us-east-1 regional endpoint option '
499        '{s3_us_east_1_regional_endpoint_config} is '
500        'invalid. Valid options are: "legacy", "regional"'
501    )
502
503
504class InvalidSTSRegionalEndpointsConfigError(BotoCoreError):
505    """Error when invalid sts regional endpoints configuration is specified"""
506    fmt = (
507        'STS regional endpoints option {sts_regional_endpoints_config} is '
508        'invalid. Valid options are: "legacy", "regional"'
509    )
510
511
512class StubResponseError(BotoCoreError):
513    fmt = 'Error getting response stub for operation {operation_name}: {reason}'
514
515
516class StubAssertionError(StubResponseError, AssertionError):
517    pass
518
519
520class UnStubbedResponseError(StubResponseError):
521    pass
522
523
524class InvalidConfigError(BotoCoreError):
525    fmt = '{error_msg}'
526
527
528class InfiniteLoopConfigError(InvalidConfigError):
529    fmt = (
530        'Infinite loop in credential configuration detected. Attempting to '
531        'load from profile {source_profile} which has already been visited. '
532        'Visited profiles: {visited_profiles}'
533    )
534
535
536class RefreshWithMFAUnsupportedError(BotoCoreError):
537    fmt = 'Cannot refresh credentials: MFA token required.'
538
539
540class MD5UnavailableError(BotoCoreError):
541    fmt = "This system does not support MD5 generation."
542
543
544class MetadataRetrievalError(BotoCoreError):
545    fmt = "Error retrieving metadata: {error_msg}"
546
547
548class UndefinedModelAttributeError(Exception):
549    pass
550
551
552class MissingServiceIdError(UndefinedModelAttributeError):
553    fmt = (
554        "The model being used for the service {service_name} is missing the "
555        "serviceId metadata property, which is required."
556    )
557
558    def __init__(self, **kwargs):
559        msg = self.fmt.format(**kwargs)
560        Exception.__init__(self, msg)
561        self.kwargs = kwargs
562
563
564class SSOError(BotoCoreError):
565    fmt = "An unspecified error happened when resolving SSO credentials"
566
567
568class SSOTokenLoadError(SSOError):
569    fmt = "Error loading SSO Token: {error_msg}"
570
571
572class UnauthorizedSSOTokenError(SSOError):
573    fmt = (
574        "The SSO session associated with this profile has expired or is "
575        "otherwise invalid. To refresh this SSO session run aws sso login "
576        "with the corresponding profile."
577    )
578
579
580class CapacityNotAvailableError(BotoCoreError):
581    fmt = (
582        'Insufficient request capacity available.'
583    )
584