diff options
Diffstat (limited to 'boto')
32 files changed, 1189 insertions, 272 deletions
diff --git a/boto/__init__.py b/boto/__init__.py index d76b67cc..1737f45c 100644 --- a/boto/__init__.py +++ b/boto/__init__.py @@ -882,6 +882,52 @@ def connect_route53domains(aws_access_key_id=None, ) +def connect_cognito_identity(aws_access_key_id=None, + aws_secret_access_key=None, + **kwargs): + """ + Connect to Amazon Cognito Identity + + :type aws_access_key_id: string + :param aws_access_key_id: Your AWS Access Key ID + + :type aws_secret_access_key: string + :param aws_secret_access_key: Your AWS Secret Access Key + + rtype: :class:`boto.cognito.identity.layer1.CognitoIdentityConnection` + :return: A connection to the Amazon Cognito Identity service + """ + from boto.cognito.identity.layer1 import CognitoIdentityConnection + return CognitoIdentityConnection( + aws_access_key_id=aws_access_key_id, + aws_secret_access_key=aws_secret_access_key, + **kwargs + ) + + +def connect_cognito_sync(aws_access_key_id=None, + aws_secret_access_key=None, + **kwargs): + """ + Connect to Amazon Cognito Sync + + :type aws_access_key_id: string + :param aws_access_key_id: Your AWS Access Key ID + + :type aws_secret_access_key: string + :param aws_secret_access_key: Your AWS Secret Access Key + + rtype: :class:`boto.cognito.sync.layer1.CognitoSyncConnection` + :return: A connection to the Amazon Cognito Sync service + """ + from boto.cognito.sync.layer1 import CognitoSyncConnection + return CognitoSyncConnection( + aws_access_key_id=aws_access_key_id, + aws_secret_access_key=aws_secret_access_key, + **kwargs + ) + + def storage_uri(uri_str, default_scheme='file', debug=0, validate=True, bucket_storage_uri_class=BucketStorageUri, suppress_consec_slashes=True, is_latest=False): diff --git a/boto/auth.py b/boto/auth.py index df8dcccd..f05bb275 100644 --- a/boto/auth.py +++ b/boto/auth.py @@ -37,8 +37,6 @@ import datetime from email.utils import formatdate import hmac import os -import sys -import time import posixpath from boto.compat import urllib, encodebytes @@ -378,7 +376,7 @@ class HmacAuthV4Handler(AuthHandler, HmacKeys): path = http_request.auth_path # Normalize the path # in windows normpath('/') will be '\\' so we chane it back to '/' - normalized = posixpath.normpath(path).replace('\\','/') + normalized = posixpath.normpath(path).replace('\\', '/') # Then urlencode whatever's left. encoded = urllib.parse.quote(normalized) if len(path) > 1 and path.endswith('/'): @@ -474,7 +472,7 @@ class HmacAuthV4Handler(AuthHandler, HmacKeys): def signature(self, http_request, string_to_sign): key = self._provider.secret_key k_date = self._sign(('AWS4' + key).encode('utf-8'), - http_request.timestamp) + http_request.timestamp) k_region = self._sign(k_date, http_request.region_name) k_service = self._sign(k_region, http_request.service_name) k_signing = self._sign(k_service, 'aws4_request') @@ -570,7 +568,7 @@ class S3HmacAuthV4Handler(HmacAuthV4Handler, AuthHandler): # Hooray for the only difference! The main SigV4 signer only does # ``Host`` + ``x-amz-*``. But S3 wants pretty much everything # signed, except for authorization itself. - if not lname in ['authorization']: + if lname not in ['authorization']: headers_to_sign[name] = value return headers_to_sign @@ -667,7 +665,7 @@ class S3HmacAuthV4Handler(HmacAuthV4Handler, AuthHandler): return super(S3HmacAuthV4Handler, self).payload(http_request) def add_auth(self, req, **kwargs): - if not 'x-amz-content-sha256' in req.headers: + if 'x-amz-content-sha256' not in req.headers: if '_sha256' in req.headers: req.headers['x-amz-content-sha256'] = req.headers.pop('_sha256') else: @@ -705,6 +703,10 @@ class S3HmacAuthV4Handler(HmacAuthV4Handler, AuthHandler): if self._provider.security_token: params['X-Amz-Security-Token'] = self._provider.security_token + headers_to_sign = self.headers_to_sign(req) + l = sorted(['%s' % n.lower().strip() for n in headers_to_sign]) + params['X-Amz-SignedHeaders'] = ';'.join(l) + req.params.update(params) cr = self.canonical_request(req) @@ -751,7 +753,6 @@ class QueryAuthHandler(AuthHandler): def add_auth(self, http_request, **kwargs): headers = http_request.headers - params = http_request.params qs = self._build_query_string( http_request.params ) @@ -899,7 +900,7 @@ class POSTPathQSV2AuthHandler(QuerySignatureV2AuthHandler, AuthHandler): # already be there, we need to get rid of that and rebuild it req.path = req.path.split('?')[0] req.path = (req.path + '?' + qs + - '&Signature=' + urllib.parse.quote_plus(signature)) + '&Signature=' + urllib.parse.quote_plus(signature)) def get_auth_handler(host, config, provider, requested_capability=None): @@ -925,7 +926,6 @@ def get_auth_handler(host, config, provider, requested_capability=None): """ ready_handlers = [] auth_handlers = boto.plugin.get_plugin(AuthHandler, requested_capability) - total_handlers = len(auth_handlers) for handler in auth_handlers: try: ready_handlers.append(handler(host, config, provider)) @@ -936,9 +936,9 @@ def get_auth_handler(host, config, provider, requested_capability=None): checked_handlers = auth_handlers names = [handler.__name__ for handler in checked_handlers] raise boto.exception.NoAuthHandlerFound( - 'No handler was ready to authenticate. %d handlers were checked.' - ' %s ' - 'Check your credentials' % (len(names), str(names))) + 'No handler was ready to authenticate. %d handlers were checked.' + ' %s ' + 'Check your credentials' % (len(names), str(names))) # We select the last ready auth handler that was loaded, to allow users to # customize how auth works in environments where there are shared boto diff --git a/boto/auth_handler.py b/boto/auth_handler.py index e6d131af..a8583f8a 100644 --- a/boto/auth_handler.py +++ b/boto/auth_handler.py @@ -14,7 +14,7 @@ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL- # ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT -# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS # IN THE SOFTWARE. @@ -25,8 +25,10 @@ Defines an interface which all Auth handlers need to implement. from boto.plugin import Plugin + class NotReadyToAuthenticate(Exception): - pass + pass + class AuthHandler(Plugin): @@ -37,10 +39,10 @@ class AuthHandler(Plugin): :type host: string :param host: The host to which the request is being sent. - :type config: boto.pyami.Config + :type config: boto.pyami.Config :param config: Boto configuration. - :type provider: boto.provider.Provider + :type provider: boto.provider.Provider :param provider: Provider details. Raises: diff --git a/boto/cognito/__init__.py b/boto/cognito/__init__.py new file mode 100644 index 00000000..70cc23fe --- /dev/null +++ b/boto/cognito/__init__.py @@ -0,0 +1,21 @@ +# Copyright (c) 2014 Amazon.com, Inc. or its affiliates. All Rights Reserved +# +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, dis- +# tribute, sublicense, and/or sell copies of the Software, and to permit +# persons to whom the Software is furnished to do so, subject to the fol- +# lowing conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL- +# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +# IN THE SOFTWARE. +# diff --git a/boto/cognito/identity/__init__.py b/boto/cognito/identity/__init__.py new file mode 100644 index 00000000..e58b480b --- /dev/null +++ b/boto/cognito/identity/__init__.py @@ -0,0 +1,42 @@ +# Copyright (c) 2014 Amazon.com, Inc. or its affiliates. +# All Rights Reserved +# +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, dis- +# tribute, sublicense, and/or sell copies of the Software, and to permit +# persons to whom the Software is furnished to do so, subject to the fol- +# lowing conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL- +# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +# IN THE SOFTWARE. +# +from boto.regioninfo import RegionInfo, get_regions + + +def regions(): + """ + Get all available regions for the Amazon Cognito Identity service. + + :rtype: list + :return: A list of :class:`boto.regioninfo.RegionInfo` + """ + from boto.cognito.identity.layer1 import CognitoIdentityConnection + return get_regions('cognito-identity', + connection_cls=CognitoIdentityConnection) + + +def connect_to_region(region_name, **kw_params): + for region in regions(): + if region.name == region_name: + return region.connect(**kw_params) + return None diff --git a/boto/cognito/identity/exceptions.py b/boto/cognito/identity/exceptions.py new file mode 100644 index 00000000..179089df --- /dev/null +++ b/boto/cognito/identity/exceptions.py @@ -0,0 +1,40 @@ +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL- +# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +# IN THE SOFTWARE. +# +from boto.exception import BotoServerError + + +class LimitExceededException(BotoServerError): + pass + + +class ResourceConflictException(BotoServerError): + pass + + +class TooManyRequestsException(BotoServerError): + pass + + +class InvalidParameterException(BotoServerError): + pass + + +class ResourceNotFoundException(BotoServerError): + pass + + +class InternalErrorException(BotoServerError): + pass + + +class NotAuthorizedException(BotoServerError): + pass diff --git a/boto/cognito/identity/layer1.py b/boto/cognito/identity/layer1.py new file mode 100644 index 00000000..0a9c8e4e --- /dev/null +++ b/boto/cognito/identity/layer1.py @@ -0,0 +1,303 @@ +# Copyright (c) 2014 Amazon.com, Inc. or its affiliates. All Rights Reserved +# +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, dis- +# tribute, sublicense, and/or sell copies of the Software, and to permit +# persons to whom the Software is furnished to do so, subject to the fol- +# lowing conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL- +# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +# IN THE SOFTWARE. +# + +import boto +from boto.compat import json +from boto.connection import AWSQueryConnection +from boto.regioninfo import RegionInfo +from boto.exception import JSONResponseError +from boto.cognito.identity import exceptions + + +class CognitoIdentityConnection(AWSQueryConnection): + """ + Amazon Cognito + Amazon Cognito is a web service that facilitates the delivery of + scoped, temporary credentials to mobile devices or other untrusted + environments. Amazon Cognito uniquely identifies a device or user + and supplies the user with a consistent identity throughout the + lifetime of an application. + + Amazon Cognito lets users authenticate with third-party identity + providers (Facebook, Google, or Login with Amazon). As a + developer, you decide which identity providers to trust. You can + also choose to support unauthenticated access from your + application. Your users are provided with Cognito tokens that + uniquely identify their device and any information provided about + third-party logins. + """ + APIVersion = "2014-06-30" + DefaultRegionName = "us-east-1" + DefaultRegionEndpoint = "cognito-identity.us-east-1.amazonaws.com" + ServiceName = "CognitoIdentity" + TargetPrefix = "AWSCognitoIdentityService" + ResponseError = JSONResponseError + + _faults = { + "LimitExceededException": exceptions.LimitExceededException, + "ResourceConflictException": exceptions.ResourceConflictException, + "TooManyRequestsException": exceptions.TooManyRequestsException, + "InvalidParameterException": exceptions.InvalidParameterException, + "ResourceNotFoundException": exceptions.ResourceNotFoundException, + "InternalErrorException": exceptions.InternalErrorException, + "NotAuthorizedException": exceptions.NotAuthorizedException, + } + + + def __init__(self, **kwargs): + region = kwargs.pop('region', None) + if not region: + region = RegionInfo(self, self.DefaultRegionName, + self.DefaultRegionEndpoint) + + if 'host' not in kwargs or kwargs['host'] is None: + kwargs['host'] = region.endpoint + + super(CognitoIdentityConnection, self).__init__(**kwargs) + self.region = region + + def _required_auth_capability(self): + return ['hmac-v4'] + + def create_identity_pool(self, identity_pool_name, + allow_unauthenticated_identities, + supported_login_providers=None): + """ + Creates a new identity pool. The identity pool is a store of + user identity information that is specific to your AWS + account. + + :type identity_pool_name: string + :param identity_pool_name: A string that you provide. + + :type allow_unauthenticated_identities: boolean + :param allow_unauthenticated_identities: TRUE if the identity pool + supports unauthenticated logins. + + :type supported_login_providers: map + :param supported_login_providers: Optional key:value pairs mapping + provider names to provider app IDs. + + """ + params = { + 'IdentityPoolName': identity_pool_name, + 'AllowUnauthenticatedIdentities': allow_unauthenticated_identities, + } + if supported_login_providers is not None: + params['SupportedLoginProviders'] = supported_login_providers + return self.make_request(action='CreateIdentityPool', + body=json.dumps(params)) + + def delete_identity_pool(self, identity_pool_id): + """ + Deletes a user pool. Once a pool is deleted, users will not be + able to authenticate with the pool. + + :type identity_pool_id: string + :param identity_pool_id: An identity pool ID in the format REGION:GUID. + + """ + params = {'IdentityPoolId': identity_pool_id, } + return self.make_request(action='DeleteIdentityPool', + body=json.dumps(params)) + + def describe_identity_pool(self, identity_pool_id): + """ + Gets details about a particular identity pool, including the + pool name, ID description, creation date, and current number + of users. + + :type identity_pool_id: string + :param identity_pool_id: An identity pool ID in the format REGION:GUID. + + """ + params = {'IdentityPoolId': identity_pool_id, } + return self.make_request(action='DescribeIdentityPool', + body=json.dumps(params)) + + def get_id(self, account_id, identity_pool_id, logins=None): + """ + Generates (or retrieves) a Cognito ID. Supplying multiple + logins will create an implicit linked account. + + :type account_id: string + :param account_id: A standard AWS account ID (9+ digits). + + :type identity_pool_id: string + :param identity_pool_id: An identity pool ID in the format REGION:GUID. + + :type logins: map + :param logins: A set of optional name/value pairs that map provider + names to provider tokens. + + """ + params = { + 'AccountId': account_id, + 'IdentityPoolId': identity_pool_id, + } + if logins is not None: + params['Logins'] = logins + return self.make_request(action='GetId', + body=json.dumps(params)) + + def get_open_id_token(self, identity_id, logins=None): + """ + Gets an OpenID token, using a known Cognito ID. This known + Cognito ID is returned from GetId. You can optionally add + additional logins for the identity. Supplying multiple logins + creates an implicit link. + + :type identity_id: string + :param identity_id: A unique identifier in the format REGION:GUID. + + :type logins: map + :param logins: A set of optional name/value pairs that map provider + names to provider tokens. + + """ + params = {'IdentityId': identity_id, } + if logins is not None: + params['Logins'] = logins + return self.make_request(action='GetOpenIdToken', + body=json.dumps(params)) + + def list_identities(self, identity_pool_id, max_results, next_token=None): + """ + Lists the identities in a pool. + + :type identity_pool_id: string + :param identity_pool_id: An identity pool ID in the format REGION:GUID. + + :type max_results: integer + :param max_results: The maximum number of identities to return. + + :type next_token: string + :param next_token: A pagination token. + + """ + params = { + 'IdentityPoolId': identity_pool_id, + 'MaxResults': max_results, + } + if next_token is not None: + params['NextToken'] = next_token + return self.make_request(action='ListIdentities', + body=json.dumps(params)) + + def list_identity_pools(self, max_results, next_token=None): + """ + Lists all of the Cognito identity pools registered for your + account. + + :type max_results: integer + :param max_results: The maximum number of identities to return. + + :type next_token: string + :param next_token: A pagination token. + + """ + params = {'MaxResults': max_results, } + if next_token is not None: + params['NextToken'] = next_token + return self.make_request(action='ListIdentityPools', + body=json.dumps(params)) + + def unlink_identity(self, identity_id, logins, logins_to_remove): + """ + Unlinks a federated identity from an existing account. + Unlinked logins will be considered new identities next time + they are seen. Removing the last linked login will make this + identity inaccessible. + + :type identity_id: string + :param identity_id: A unique identifier in the format REGION:GUID. + + :type logins: map + :param logins: A set of optional name/value pairs that map provider + names to provider tokens. + + :type logins_to_remove: list + :param logins_to_remove: Provider names to unlink from this identity. + + """ + params = { + 'IdentityId': identity_id, + 'Logins': logins, + 'LoginsToRemove': logins_to_remove, + } + return self.make_request(action='UnlinkIdentity', + body=json.dumps(params)) + + def update_identity_pool(self, identity_pool_id, identity_pool_name, + allow_unauthenticated_identities, + supported_login_providers=None): + """ + Updates a user pool. + + :type identity_pool_id: string + :param identity_pool_id: An identity pool ID in the format REGION:GUID. + + :type identity_pool_name: string + :param identity_pool_name: A string that you provide. + + :type allow_unauthenticated_identities: boolean + :param allow_unauthenticated_identities: TRUE if the identity pool + supports unauthenticated logins. + + :type supported_login_providers: map + :param supported_login_providers: Optional key:value pairs mapping + provider names to provider app IDs. + + """ + params = { + 'IdentityPoolId': identity_pool_id, + 'IdentityPoolName': identity_pool_name, + 'AllowUnauthenticatedIdentities': allow_unauthenticated_identities, + } + if supported_login_providers is not None: + params['SupportedLoginProviders'] = supported_login_providers + return self.make_request(action='UpdateIdentityPool', + body=json.dumps(params)) + + def make_request(self, action, body): + headers = { + 'X-Amz-Target': '%s.%s' % (self.TargetPrefix, action), + 'Host': self.region.endpoint, + 'Content-Type': 'application/x-amz-json-1.1', + 'Content-Length': str(len(body)), + } + http_request = self.build_base_http_request( + method='POST', path='/', auth_path='/', params={}, + headers=headers, data=body) + response = self._mexe(http_request, sender=None, + override_num_retries=10) + response_body = response.read().decode('utf-8') + boto.log.debug(response_body) + if response.status == 200: + if response_body: + return json.loads(response_body) + else: + json_body = json.loads(response_body) + fault_name = json_body.get('__type', None) + exception_class = self._faults.get(fault_name, self.ResponseError) + raise exception_class(response.status, response.reason, + body=json_body) diff --git a/boto/cognito/sync/__init__.py b/boto/cognito/sync/__init__.py new file mode 100644 index 00000000..3d48bd18 --- /dev/null +++ b/boto/cognito/sync/__init__.py @@ -0,0 +1,41 @@ +# Copyright (c) 2014 Amazon.com, Inc. or its affiliates. +# All Rights Reserved +# +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, dis- +# tribute, sublicense, and/or sell copies of the Software, and to permit +# persons to whom the Software is furnished to do so, subject to the fol- +# lowing conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL- +# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +# IN THE SOFTWARE. +# +from boto.regioninfo import RegionInfo, get_regions + + +def regions(): + """ + Get all available regions for the Amazon Cognito Sync service. + + :rtype: list + :return: A list of :class:`boto.regioninfo.RegionInfo` + """ + from boto.cognito.sync.layer1 import CognitoSyncConnection + return get_regions('cognito-sync', connection_cls=CognitoSyncConnection) + + +def connect_to_region(region_name, **kw_params): + for region in regions(): + if region.name == region_name: + return region.connect(**kw_params) + return None diff --git a/boto/cognito/sync/exceptions.py b/boto/cognito/sync/exceptions.py new file mode 100644 index 00000000..d64fb278 --- /dev/null +++ b/boto/cognito/sync/exceptions.py @@ -0,0 +1,50 @@ +# Copyright (c) 2014 Amazon.com, Inc. or its affiliates. All Rights Reserved +# +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, dis- +# tribute, sublicense, and/or sell copies of the Software, and to permit +# persons to whom the Software is furnished to do so, subject to the fol- +# lowing conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL- +# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +# IN THE SOFTWARE. +# +from boto.exception import BotoServerError + + +class LimitExceededException(BotoServerError): + pass + + +class ResourceConflictException(BotoServerError): + pass + + +class TooManyRequestsException(BotoServerError): + pass + + +class InvalidParameterException(BotoServerError): + pass + + +class ResourceNotFoundException(BotoServerError): + pass + + +class InternalErrorException(BotoServerError): + pass + + +class NotAuthorizedException(BotoServerError): + pass diff --git a/boto/cognito/sync/layer1.py b/boto/cognito/sync/layer1.py new file mode 100644 index 00000000..545af5eb --- /dev/null +++ b/boto/cognito/sync/layer1.py @@ -0,0 +1,307 @@ +# Copyright (c) 2014 Amazon.com, Inc. or its affiliates. All Rights Reserved +# +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, dis- +# tribute, sublicense, and/or sell copies of the Software, and to permit +# persons to whom the Software is furnished to do so, subject to the fol- +# lowing conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL- +# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +# IN THE SOFTWARE. +# +from boto.compat import json +from boto.exception import JSONResponseError +from boto.connection import AWSAuthConnection +from boto.regioninfo import RegionInfo +from boto.cognito.sync import exceptions + + +class CognitoSyncConnection(AWSAuthConnection): + """ + Amazon Cognito Sync + Amazon Cognito Sync provides an AWS service and client library + that enable cross-device syncing of application-related user data. + High-level client libraries are available for both iOS and + Android. You can use these libraries to persist data locally so + that it's available even if the device is offline. Developer + credentials don't need to be stored on the mobile device to access + the service. You can use Amazon Cognito to obtain a normalized + user ID and credentials. User data is persisted in a dataset that + can store up to 1 MB of key-value pairs, and you can have up to 20 + datasets per user identity. + """ + APIVersion = "2014-06-30" + DefaultRegionName = "us-east-1" + DefaultRegionEndpoint = "cognito-sync.us-east-1.amazonaws.com" + ResponseError = JSONResponseError + + _faults = { + "LimitExceededException": exceptions.LimitExceededException, + "ResourceConflictException": exceptions.ResourceConflictException, + "TooManyRequestsException": exceptions.TooManyRequestsException, + "InvalidParameterException": exceptions.InvalidParameterException, + "ResourceNotFoundException": exceptions.ResourceNotFoundException, + "InternalErrorException": exceptions.InternalErrorException, + "NotAuthorizedException": exceptions.NotAuthorizedException, + } + + + def __init__(self, **kwargs): + region = kwargs.get('region') + if not region: + region = RegionInfo(self, self.DefaultRegionName, + self.DefaultRegionEndpoint) + else: + del kwargs['region'] + kwargs['host'] = region.endpoint + super(CognitoSyncConnection, self).__init__(**kwargs) + self.region = region + + def _required_auth_capability(self): + return ['hmac-v4'] + + def delete_dataset(self, identity_pool_id, identity_id, dataset_name): + """ + Deletes the specific dataset. The dataset will be deleted + permanently, and the action can't be undone. Datasets that + this dataset was merged with will no longer report the merge. + Any consequent operation on this dataset will result in a + ResourceNotFoundException. + + :type identity_pool_id: string + :param identity_pool_id: A name-spaced GUID (for example, us- + east-1:23EC4050-6AEA-7089-A2DD-08002EXAMPLE) created by Amazon + Cognito. GUID generation is unique within a region. + + :type identity_id: string + :param identity_id: A name-spaced GUID (for example, us- + east-1:23EC4050-6AEA-7089-A2DD-08002EXAMPLE) created by Amazon + Cognito. GUID generation is unique within a region. + + :type dataset_name: string + :param dataset_name: A string of up to 128 characters. Allowed + characters are a-z, A-Z, 0-9, '_' (underscore), '-' (dash), and '.' + (dot). + + """ + uri = '/identitypools/{0}/identities/{1}/datasets/{2}'.format( + identity_pool_id, identity_id, dataset_name) + return self.make_request('DELETE', uri, expected_status=200) + + def describe_dataset(self, identity_pool_id, identity_id, dataset_name): + """ + Gets metadata about a dataset by identity and dataset name. + + :type identity_pool_id: string + :param identity_pool_id: A name-spaced GUID (for example, us- + east-1:23EC4050-6AEA-7089-A2DD-08002EXAMPLE) created by Amazon + Cognito. GUID generation is unique within a region. + + :type identity_id: string + :param identity_id: A name-spaced GUID (for example, us- + east-1:23EC4050-6AEA-7089-A2DD-08002EXAMPLE) created by Amazon + Cognito. GUID generation is unique within a region. + + :type dataset_name: string + :param dataset_name: A string of up to 128 characters. Allowed + characters are a-z, A-Z, 0-9, '_' (underscore), '-' (dash), and '.' + (dot). + + """ + uri = '/identitypools/{0}/identities/{1}/datasets/{2}'.format( + identity_pool_id, identity_id, dataset_name) + return self.make_request('GET', uri, expected_status=200) + + def describe_identity_pool_usage(self, identity_pool_id): + """ + Gets usage details (for example, data storage) about a + particular identity pool. + + :type identity_pool_id: string + :param identity_pool_id: A name-spaced GUID (for example, us- + east-1:23EC4050-6AEA-7089-A2DD-08002EXAMPLE) created by Amazon + Cognito. GUID generation is unique within a region. + + """ + uri = '/identitypools/{0}'.format(identity_pool_id) + return self.make_request('GET', uri, expected_status=200) + + def describe_identity_usage(self, identity_pool_id, identity_id): + """ + Gets usage information for an identity, including number of + datasets and data usage. + + :type identity_pool_id: string + :param identity_pool_id: A name-spaced GUID (for example, us- + east-1:23EC4050-6AEA-7089-A2DD-08002EXAMPLE) created by Amazon + Cognito. GUID generation is unique within a region. + + :type identity_id: string + :param identity_id: A name-spaced GUID (for example, us- + east-1:23EC4050-6AEA-7089-A2DD-08002EXAMPLE) created by Amazon + Cognito. GUID generation is unique within a region. + + """ + uri = '/identitypools/{0}/identities/{1}'.format( + identity_pool_id, identity_id) + return self.make_request('GET', uri, expected_status=200) + + def list_datasets(self, identity_pool_id, identity_id, next_token=None, + max_results=None): + """ + Lists datasets for an identity. + + :type identity_pool_id: string + :param identity_pool_id: A name-spaced GUID (for example, us- + east-1:23EC4050-6AEA-7089-A2DD-08002EXAMPLE) created by Amazon + Cognito. GUID generation is unique within a region. + + :type identity_id: string + :param identity_id: A name-spaced GUID (for example, us- + east-1:23EC4050-6AEA-7089-A2DD-08002EXAMPLE) created by Amazon + Cognito. GUID generation is unique within a region. + + :type next_token: string + :param next_token: A pagination token for obtaining the next page of + results. + + :type max_results: integer + :param max_results: The maximum number of results to be returned. + + """ + uri = '/identitypools/{0}/identities/{1}/datasets'.format( + identity_pool_id, identity_id) + params = {} + headers = {} + return self.make_request('GET', uri, expected_status=200, + data=json.dumps(params), headers=headers) + + def list_identity_pool_usage(self, next_token=None, max_results=None): + """ + Gets a list of identity pools registered with Cognito. + + :type next_token: string + :param next_token: A pagination token for obtaining the next page of + results. + + :type max_results: integer + :param max_results: The maximum number of results to be returned. + + """ + uri = '/identitypools' + params = {} + headers = {} + return self.make_request('GET', uri, expected_status=200, + data=json.dumps(params), headers=headers) + + def list_records(self, identity_pool_id, identity_id, dataset_name, + last_sync_count=None, next_token=None, max_results=None, + sync_session_token=None): + """ + Gets paginated records, optionally changed after a particular + sync count for a dataset and identity. + + :type identity_pool_id: string + :param identity_pool_id: A name-spaced GUID (for example, us- + east-1:23EC4050-6AEA-7089-A2DD-08002EXAMPLE) created by Amazon + Cognito. GUID generation is unique within a region. + + :type identity_id: string + :param identity_id: A name-spaced GUID (for example, us- + east-1:23EC4050-6AEA-7089-A2DD-08002EXAMPLE) created by Amazon + Cognito. GUID generation is unique within a region. + + :type dataset_name: string + :param dataset_name: A string of up to 128 characters. Allowed + characters are a-z, A-Z, 0-9, '_' (underscore), '-' (dash), and '.' + (dot). + + :type last_sync_count: long + :param last_sync_count: The last server sync count for this record. + + :type next_token: string + :param next_token: A pagination token for obtaining the next page of + results. + + :type max_results: integer + :param max_results: The maximum number of results to be returned. + + :type sync_session_token: string + :param sync_session_token: A token containing a session ID, identity + ID, and expiration. + + """ + uri = '/identitypools/{0}/identities/{1}/datasets/{2}/records'.format( + identity_pool_id, identity_id, dataset_name) + params = {} + headers = {} + return self.make_request('GET', uri, expected_status=200, + data=json.dumps(params), headers=headers) + + def update_records(self, identity_pool_id, identity_id, dataset_name, + sync_session_token, record_patches=None, + client_context=None): + """ + Posts updates to records and add and delete records for a + dataset and user. + + :type identity_pool_id: string + :param identity_pool_id: A name-spaced GUID (for example, us- + east-1:23EC4050-6AEA-7089-A2DD-08002EXAMPLE) created by Amazon + Cognito. GUID generation is unique within a region. + + :type identity_id: string + :param identity_id: A name-spaced GUID (for example, us- + east-1:23EC4050-6AEA-7089-A2DD-08002EXAMPLE) created by Amazon + Cognito. GUID generation is unique within a region. + + :type dataset_name: string + :param dataset_name: A string of up to 128 characters. Allowed + characters are a-z, A-Z, 0-9, '_' (underscore), '-' (dash), and '.' + (dot). + + :type record_patches: list + :param record_patches: + + :type sync_session_token: string + :param sync_session_token: The SyncSessionToken returned by a previous + call to ListRecords for this dataset and identity. + + :type client_context: string + :param client_context: + + """ + uri = '/identitypools/{0}/identities/{1}/datasets/{2}'.format( + identity_pool_id, identity_id, dataset_name) + params = {'SyncSessionToken': sync_session_token, } + headers = {} + if record_patches is not None: + params['RecordPatches'] = record_patches + if client_context is not None: + headers['x-amz-Client-Context'] = client_context + return self.make_request('POST', uri, expected_status=200, + data=json.dumps(params), headers=headers) + + def make_request(self, verb, resource, headers=None, data='', + expected_status=None, params=None): + if headers is None: + headers = {} + response = AWSAuthConnection.make_request( + self, verb, resource, headers=headers, data=data, params=params) + body = json.loads(response.read().decode('utf-8')) + if response.status == expected_status: + return body + else: + error_type = response.getheader('x-amzn-ErrorType').split(':')[0] + error_class = self._faults.get(error_type, self.ResponseError) + raise error_class(response.status, response.reason, body) diff --git a/boto/connection.py b/boto/connection.py index 8640ec48..40db69a7 100644 --- a/boto/connection.py +++ b/boto/connection.py @@ -90,7 +90,7 @@ ON_APP_ENGINE = all(key in os.environ for key in ( PORTS_BY_SECURITY = {True: 443, False: 80} -DEFAULT_CA_CERTS_FILE = os.path.join(os.path.dirname(os.path.abspath(boto.cacerts.__file__ )), "cacerts.txt") +DEFAULT_CA_CERTS_FILE = os.path.join(os.path.dirname(os.path.abspath(boto.cacerts.__file__)), "cacerts.txt") class HostConnectionPool(object): @@ -372,9 +372,10 @@ class HTTPRequest(object): self.headers[key] = quote(val.encode('utf-8'), safe) setattr(self, '_headers_quoted', True) + self.headers['User-Agent'] = UserAgent + connection._auth_handler.add_auth(self, **kwargs) - self.headers['User-Agent'] = UserAgent # I'm not sure if this is still needed, now that add_auth is # setting the content-length for POST requests. if 'Content-Length' not in self.headers: @@ -485,13 +486,13 @@ class AWSAuthConnection(object): validate_certs) if self.https_validate_certificates and not HAVE_HTTPS_CONNECTION: raise BotoClientError( - "SSL server certificate validation is enabled in boto " - "configuration, but Python dependencies required to " - "support this feature are not available. Certificate " - "validation is only supported when running under Python " - "2.6 or later.") + "SSL server certificate validation is enabled in boto " + "configuration, but Python dependencies required to " + "support this feature are not available. Certificate " + "validation is only supported when running under Python " + "2.6 or later.") certs_file = config.get_value( - 'Boto', 'ca_certificates_file', DEFAULT_CA_CERTS_FILE) + 'Boto', 'ca_certificates_file', DEFAULT_CA_CERTS_FILE) if certs_file == 'system': certs_file = None self.ca_certificates_file = certs_file @@ -508,7 +509,7 @@ class AWSAuthConnection(object): self.http_unretryable_exceptions = [] if HAVE_HTTPS_CONNECTION: self.http_unretryable_exceptions.append( - https_connection.InvalidCertificateException) + https_connection.InvalidCertificateException) # define values in socket exceptions we don't want to catch self.socket_exception_values = (errno.EINTR,) @@ -565,7 +566,7 @@ class AWSAuthConnection(object): self._connection = (self.host, self.port, self.is_secure) self._last_rs = None self._auth_handler = auth.get_auth_handler( - host, config, self.provider, self._required_auth_capability()) + host, config, self.provider, self._required_auth_capability()) if getattr(self, 'AuthServiceName', None) is not None: self.auth_service_name = self.AuthServiceName self.request_hook = None @@ -667,9 +668,9 @@ class AWSAuthConnection(object): self.proxy_pass = proxy_pass if 'http_proxy' in os.environ and not self.proxy: pattern = re.compile( - '(?:http://)?' \ - '(?:(?P<user>[\w\-\.]+):(?P<pass>.*)@)?' \ - '(?P<host>[\w\-\.]+)' \ + '(?:http://)?' + '(?:(?P<user>[\w\-\.]+):(?P<pass>.*)@)?' + '(?P<host>[\w\-\.]+)' '(?::(?P<port>\d+))?' ) match = pattern.match(os.environ['http_proxy']) @@ -689,8 +690,8 @@ class AWSAuthConnection(object): self.proxy_pass = config.get_value('Boto', 'proxy_pass', None) if not self.proxy_port and self.proxy: - print("http_proxy environment variable does not specify " \ - "a port, using default") + print("http_proxy environment variable does not specify " + "a port, using default") self.proxy_port = self.port self.no_proxy = os.environ.get('no_proxy', '') or os.environ.get('NO_PROXY', '') @@ -740,30 +741,30 @@ class AWSAuthConnection(object): if is_secure: boto.log.debug( - 'establishing HTTPS connection: host=%s, kwargs=%s', - host, http_connection_kwargs) + 'establishing HTTPS connection: host=%s, kwargs=%s', + host, http_connection_kwargs) if self.use_proxy and not self.skip_proxy(host): connection = self.proxy_ssl(host, is_secure and 443 or 80) elif self.https_connection_factory: connection = self.https_connection_factory(host) elif self.https_validate_certificates and HAVE_HTTPS_CONNECTION: connection = https_connection.CertValidatingHTTPSConnection( - host, ca_certs=self.ca_certificates_file, - **http_connection_kwargs) + host, ca_certs=self.ca_certificates_file, + **http_connection_kwargs) else: - connection = http_client.HTTPSConnection(host, - **http_connection_kwargs) + connection = http_client.HTTPSConnection( + host, **http_connection_kwargs) else: boto.log.debug('establishing HTTP connection: kwargs=%s' % - http_connection_kwargs) + http_connection_kwargs) if self.https_connection_factory: # even though the factory says https, this is too handy # to not be able to allow overriding for http also. - connection = self.https_connection_factory(host, - **http_connection_kwargs) + connection = self.https_connection_factory( + host, **http_connection_kwargs) else: - connection = http_client.HTTPConnection(host, - **http_connection_kwargs) + connection = http_client.HTTPConnection( + host, **http_connection_kwargs) if self.debug > 1: connection.set_debuglevel(self.debug) # self.connection must be maintained for backwards-compatibility @@ -822,7 +823,7 @@ class AWSAuthConnection(object): if self.https_validate_certificates and HAVE_HTTPS_CONNECTION: msg = "wrapping ssl socket for proxied connection; " if self.ca_certificates_file: - msg += "CA certificate file=%s" %self.ca_certificates_file + msg += "CA certificate file=%s" % self.ca_certificates_file else: msg += "using system provided SSL certs" boto.log.debug(msg) @@ -836,7 +837,7 @@ class AWSAuthConnection(object): hostname = self.host.split(':', 0)[0] if not https_connection.ValidateCertificateHostname(cert, hostname): raise https_connection.InvalidCertificateException( - hostname, cert, 'hostname mismatch') + hostname, cert, 'hostname mismatch') else: # Fallback for old Python without ssl.wrap_socket if hasattr(http_client, 'ssl'): @@ -1008,8 +1009,8 @@ class AWSAuthConnection(object): 'encountered unretryable %s exception, re-raising' % e.__class__.__name__) raise - boto.log.debug('encountered %s exception, reconnecting' % \ - e.__class__.__name__) + boto.log.debug('encountered %s exception, reconnecting' % + e.__class__.__name__) connection = self.new_http_connection(request.host, request.port, self.is_secure) time.sleep(next_sleep) @@ -1041,8 +1042,7 @@ class AWSAuthConnection(object): headers = {} else: headers = headers.copy() - if (self.host_header and - not boto.utils.find_matching_headers('host', headers)): + if self.host_header and not boto.utils.find_matching_headers('host', headers): headers['host'] = self.host_header host = host or self.host if self.use_proxy: @@ -1085,14 +1085,15 @@ class AWSQueryConnection(AWSAuthConnection): proxy_user=None, proxy_pass=None, host=None, debug=0, https_connection_factory=None, path='/', security_token=None, validate_certs=True, profile_name=None): - super(AWSQueryConnection, self).__init__(host, aws_access_key_id, - aws_secret_access_key, - is_secure, port, proxy, - proxy_port, proxy_user, proxy_pass, - debug, https_connection_factory, path, - security_token=security_token, - validate_certs=validate_certs, - profile_name=profile_name) + super(AWSQueryConnection, self).__init__( + host, aws_access_key_id, + aws_secret_access_key, + is_secure, port, proxy, + proxy_port, proxy_user, proxy_pass, + debug, https_connection_factory, path, + security_token=security_token, + validate_certs=validate_certs, + profile_name=profile_name) def _required_auth_capability(self): return [] diff --git a/boto/contrib/__init__.py b/boto/contrib/__init__.py index 303dbb66..a7e571e2 100644 --- a/boto/contrib/__init__.py +++ b/boto/contrib/__init__.py @@ -14,9 +14,8 @@ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL- # ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT -# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS # IN THE SOFTWARE. # - diff --git a/boto/contrib/ymlmessage.py b/boto/contrib/ymlmessage.py index 6f3dd20c..ae6aea48 100644 --- a/boto/contrib/ymlmessage.py +++ b/boto/contrib/ymlmessage.py @@ -28,6 +28,7 @@ This module requires the yaml module. from boto.sqs.message import Message import yaml + class YAMLMessage(Message): """ The YAMLMessage class provides a YAML compatible message. Encoding and diff --git a/boto/ec2/autoscale/__init__.py b/boto/ec2/autoscale/__init__.py index 96c42848..5a58748d 100644 --- a/boto/ec2/autoscale/__init__.py +++ b/boto/ec2/autoscale/__init__.py @@ -222,7 +222,10 @@ class AutoScaleConnection(AWSQueryConnection): if launch_config.key_name: params['KeyName'] = launch_config.key_name if launch_config.user_data: - params['UserData'] = base64.b64encode(launch_config.user_data).decode('utf-8') + user_data = launch_config.user_data + if isinstance(user_data, six.text_type): + user_data = user_data.encode('utf-8') + params['UserData'] = base64.b64encode(user_data).decode('utf-8') if launch_config.kernel_id: params['KernelId'] = launch_config.kernel_id if launch_config.ramdisk_id: diff --git a/boto/ec2/autoscale/launchconfig.py b/boto/ec2/autoscale/launchconfig.py index 6d897fd2..889cee21 100644 --- a/boto/ec2/autoscale/launchconfig.py +++ b/boto/ec2/autoscale/launchconfig.py @@ -128,8 +128,8 @@ class LaunchConfiguration(object): :type instance_type: str :param instance_type: The instance type - :type kern_id: str - :param kern_id: Kernel id for instance + :type kernel_id: str + :param kernel_id: Kernel id for instance :type ramdisk_id: str :param ramdisk_id: RAM disk id for instance diff --git a/boto/ec2/elb/__init__.py b/boto/ec2/elb/__init__.py index 5ae272f7..6123909c 100644 --- a/boto/ec2/elb/__init__.py +++ b/boto/ec2/elb/__init__.py @@ -400,7 +400,7 @@ class ELBConnection(AWSQueryConnection): :param attribute: The attribute you wish to change. * crossZoneLoadBalancing - Boolean (true) - * connectionSettings - :py:class:`ConnectionSettingAttribute` instance + * connectingSettings - :py:class:`ConnectionSettingAttribute` instance * accessLog - :py:class:`AccessLogAttribute` instance * connectionDraining - :py:class:`ConnectionDrainingAttribute` instance @@ -437,7 +437,7 @@ class ELBConnection(AWSQueryConnection): value.enabled and 'true' or 'false' params['LoadBalancerAttributes.ConnectionDraining.Timeout'] = \ value.timeout - elif attribute.lower == 'connectingsettings': + elif attribute.lower() == 'connectingsettings': params['LoadBalancerAttributes.ConnectionSettings.IdleTimeout'] = \ value.idle_timeout else: @@ -472,7 +472,7 @@ class ELBConnection(AWSQueryConnection): * accessLog - :py:class:`AccessLogAttribute` instance * crossZoneLoadBalancing - Boolean - * connectionSettings - :py:class:`ConnectionSettingAttribute` instance + * connectingSettings - :py:class:`ConnectionSettingAttribute` instance * connectionDraining - :py:class:`ConnectionDrainingAttribute` instance diff --git a/boto/endpoints.json b/boto/endpoints.json index 5c95b5ca..46276438 100644 --- a/boto/endpoints.json +++ b/boto/endpoints.json @@ -35,6 +35,9 @@ }, "cloudsearch": { "ap-southeast-1": "cloudsearch.ap-southeast-1.amazonaws.com", + "ap-southeast-2": "cloudsearch.ap-southeast-2.amazonaws.com", + "ap-northeast-1": "cloudsearch.ap-northeast-1.amazonaws.com", + "sa-east-1": "cloudsearch.sa-east-1.amazonaws.com", "eu-west-1": "cloudsearch.eu-west-1.amazonaws.com", "us-east-1": "cloudsearch.us-east-1.amazonaws.com", "us-west-1": "cloudsearch.us-west-1.amazonaws.com", @@ -62,6 +65,12 @@ "us-west-1": "monitoring.us-west-1.amazonaws.com", "us-west-2": "monitoring.us-west-2.amazonaws.com" }, + "cognito-identity": { + "us-east-1": "cognito-identity.us-east-1.amazonaws.com" + }, + "cognito-sync": { + "us-east-1": "cognito-sync.us-east-1.amazonaws.com" + }, "datapipeline": { "us-east-1": "datapipeline.us-east-1.amazonaws.com", "us-west-2": "datapipeline.us-west-2.amazonaws.com", diff --git a/boto/exception.py b/boto/exception.py index 9baa0999..36c226fa 100644 --- a/boto/exception.py +++ b/boto/exception.py @@ -30,9 +30,10 @@ import xml.sax import boto from boto import handler -from boto.compat import json, six, StandardError +from boto.compat import json, StandardError from boto.resultset import ResultSet + class BotoClientError(StandardError): """ General Boto Client error (error accessing AWS) @@ -112,7 +113,7 @@ class BotoServerError(StandardError): try: h = handler.XmlHandlerWrapper(self, self) h.parseString(self.body) - except (TypeError, xml.sax.SAXParseException) as pe: + except (TypeError, xml.sax.SAXParseException): # What if it's JSON? Let's try that. try: parsed = json.loads(self.body) @@ -209,6 +210,7 @@ class StorageCreateError(BotoServerError): else: return super(StorageCreateError, self).endElement(name, value, connection) + class S3CreateError(StorageCreateError): """ Error creating a bucket or key on S3. @@ -294,15 +296,15 @@ class StorageResponseError(BotoServerError): super(StorageResponseError, self).__init__(status, reason, body) def startElement(self, name, attrs, connection): - return super(StorageResponseError, self).startElement(name, attrs, - connection) + return super(StorageResponseError, self).startElement( + name, attrs, connection) def endElement(self, name, value, connection): if name == 'Resource': self.resource = value else: - return super(StorageResponseError, self).endElement(name, value, - connection) + return super(StorageResponseError, self).endElement( + name, value, connection) def _cleanupParsedProperties(self): super(StorageResponseError, self)._cleanupParsedProperties() @@ -332,8 +334,8 @@ class EC2ResponseError(BotoServerError): self.errors = None self._errorResultSet = [] super(EC2ResponseError, self).__init__(status, reason, body) - self.errors = [ (e.error_code, e.error_message) \ - for e in self._errorResultSet ] + self.errors = [ + (e.error_code, e.error_message) for e in self._errorResultSet] if len(self.errors): self.error_code, self.error_message = self.errors[0] @@ -348,7 +350,7 @@ class EC2ResponseError(BotoServerError): if name == 'RequestID': self.request_id = value else: - return None # don't call subclass here + return None # don't call subclass here def _cleanupParsedProperties(self): super(EC2ResponseError, self)._cleanupParsedProperties() @@ -420,30 +422,35 @@ class SDBResponseError(BotoServerError): """ pass + class AWSConnectionError(BotoClientError): """ General error connecting to Amazon Web Services. """ pass + class StorageDataError(BotoClientError): """ Error receiving data from a storage service. """ pass + class S3DataError(StorageDataError): """ Error receiving data from S3. """ pass + class GSDataError(StorageDataError): """ Error receiving data from GS. """ pass + class InvalidUriError(Exception): """Exception raised when URI is invalid.""" @@ -451,6 +458,7 @@ class InvalidUriError(Exception): super(InvalidUriError, self).__init__(message) self.message = message + class InvalidAclError(Exception): """Exception raised when ACL XML is invalid.""" @@ -458,6 +466,7 @@ class InvalidAclError(Exception): super(InvalidAclError, self).__init__(message) self.message = message + class InvalidCorsError(Exception): """Exception raised when CORS XML is invalid.""" @@ -465,10 +474,12 @@ class InvalidCorsError(Exception): super(InvalidCorsError, self).__init__(message) self.message = message + class NoAuthHandlerFound(Exception): """Is raised when no auth handlers were found ready to authenticate.""" pass + class InvalidLifecycleConfigError(Exception): """Exception raised when GCS lifecycle configuration XML is invalid.""" @@ -476,6 +487,7 @@ class InvalidLifecycleConfigError(Exception): super(InvalidLifecycleConfigError, self).__init__(message) self.message = message + # Enum class for resumable upload failure disposition. class ResumableTransferDisposition(object): # START_OVER means an attempt to resume an existing transfer failed, @@ -500,6 +512,7 @@ class ResumableTransferDisposition(object): # upload ID. ABORT = 'ABORT' + class ResumableUploadException(Exception): """ Exception raised for various resumable upload problems. @@ -516,6 +529,7 @@ class ResumableUploadException(Exception): return 'ResumableUploadException("%s", %s)' % ( self.message, self.disposition) + class ResumableDownloadException(Exception): """ Exception raised for various resumable download problems. @@ -532,6 +546,7 @@ class ResumableDownloadException(Exception): return 'ResumableDownloadException("%s", %s)' % ( self.message, self.disposition) + class TooManyRecordsException(Exception): """ Exception raised when a search of Route53 records returns more diff --git a/boto/handler.py b/boto/handler.py index b079ada6..3b5f0732 100644 --- a/boto/handler.py +++ b/boto/handler.py @@ -14,7 +14,7 @@ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL- # ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT -# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS # IN THE SOFTWARE. @@ -23,6 +23,7 @@ import xml.sax from boto.compat import StringIO + class XmlHandler(xml.sax.ContentHandler): def __init__(self, root_node, connection): diff --git a/boto/https_connection.py b/boto/https_connection.py index 9222fbde..ddc31a15 100644 --- a/boto/https_connection.py +++ b/boto/https_connection.py @@ -27,111 +27,112 @@ import boto from boto.compat import six, http_client -class InvalidCertificateException(http_client.HTTPException): - """Raised when a certificate is provided with an invalid hostname.""" - def __init__(self, host, cert, reason): - """Constructor. +class InvalidCertificateException(http_client.HTTPException): + """Raised when a certificate is provided with an invalid hostname.""" - Args: - host: The hostname the connection was made to. - cert: The SSL certificate (as a dictionary) the host returned. - """ - http_client.HTTPException.__init__(self) - self.host = host - self.cert = cert - self.reason = reason + def __init__(self, host, cert, reason): + """Constructor. - def __str__(self): - return ('Host %s returned an invalid certificate (%s): %s' % - (self.host, self.reason, self.cert)) + Args: + host: The hostname the connection was made to. + cert: The SSL certificate (as a dictionary) the host returned. + """ + http_client.HTTPException.__init__(self) + self.host = host + self.cert = cert + self.reason = reason -def GetValidHostsForCert(cert): - """Returns a list of valid host globs for an SSL certificate. - - Args: - cert: A dictionary representing an SSL certificate. - Returns: - list: A list of valid host globs. - """ - if 'subjectAltName' in cert: - return [x[1] for x in cert['subjectAltName'] if x[0].lower() == 'dns'] - else: - return [x[0][1] for x in cert['subject'] - if x[0][0].lower() == 'commonname'] + def __str__(self): + return ('Host %s returned an invalid certificate (%s): %s' % + (self.host, self.reason, self.cert)) -def ValidateCertificateHostname(cert, hostname): - """Validates that a given hostname is valid for an SSL certificate. - - Args: - cert: A dictionary representing an SSL certificate. - hostname: The hostname to test. - Returns: - bool: Whether or not the hostname is valid for this certificate. - """ - hosts = GetValidHostsForCert(cert) - boto.log.debug( - "validating server certificate: hostname=%s, certificate hosts=%s", - hostname, hosts) - for host in hosts: - host_re = host.replace('.', '\.').replace('*', '[^.]*') - if re.search('^%s$' % (host_re,), hostname, re.I): - return True - return False +def GetValidHostsForCert(cert): + """Returns a list of valid host globs for an SSL certificate. -class CertValidatingHTTPSConnection(http_client.HTTPConnection): - """An HTTPConnection that connects over SSL and validates certificates.""" + Args: + cert: A dictionary representing an SSL certificate. + Returns: + list: A list of valid host globs. + """ + if 'subjectAltName' in cert: + return [x[1] for x in cert['subjectAltName'] if x[0].lower() == 'dns'] + else: + return [x[0][1] for x in cert['subject'] + if x[0][0].lower() == 'commonname'] - default_port = http_client.HTTPS_PORT - def __init__(self, host, port=default_port, key_file=None, cert_file=None, - ca_certs=None, strict=None, **kwargs): - """Constructor. +def ValidateCertificateHostname(cert, hostname): + """Validates that a given hostname is valid for an SSL certificate. Args: - host: The hostname. Can be in 'host:port' form. - port: The port. Defaults to 443. - key_file: A file containing the client's private key - cert_file: A file containing the client's certificates - ca_certs: A file contianing a set of concatenated certificate authority - certs for validating the server against. - strict: When true, causes BadStatusLine to be raised if the status line - can't be parsed as a valid HTTP/1.0 or 1.1 status line. + cert: A dictionary representing an SSL certificate. + hostname: The hostname to test. + Returns: + bool: Whether or not the hostname is valid for this certificate. """ - if six.PY2: - # Python 3.2 and newer have deprecated and removed the strict - # parameter. Since the params are supported as keyword arguments - # we conditionally add it here. - kwargs['strict'] = strict - - http_client.HTTPConnection.__init__(self, host=host, port=port, **kwargs) - self.key_file = key_file - self.cert_file = cert_file - self.ca_certs = ca_certs - - def connect(self): - "Connect to a host on a given (SSL) port." - if hasattr(self, "timeout"): - sock = socket.create_connection((self.host, self.port), self.timeout) - else: - sock = socket.create_connection((self.host, self.port)) - msg = "wrapping ssl socket; " - if self.ca_certs: - msg += "CA certificate file=%s" %self.ca_certs - else: - msg += "using system provided SSL certs" - boto.log.debug(msg) - self.sock = ssl.wrap_socket(sock, keyfile=self.key_file, - certfile=self.cert_file, - cert_reqs=ssl.CERT_REQUIRED, - ca_certs=self.ca_certs) - cert = self.sock.getpeercert() - hostname = self.host.split(':', 0)[0] - if not ValidateCertificateHostname(cert, hostname): - raise InvalidCertificateException(hostname, - cert, - 'remote hostname "%s" does not match '\ - 'certificate' % hostname) + hosts = GetValidHostsForCert(cert) + boto.log.debug( + "validating server certificate: hostname=%s, certificate hosts=%s", + hostname, hosts) + for host in hosts: + host_re = host.replace('.', '\.').replace('*', '[^.]*') + if re.search('^%s$' % (host_re,), hostname, re.I): + return True + return False +class CertValidatingHTTPSConnection(http_client.HTTPConnection): + """An HTTPConnection that connects over SSL and validates certificates.""" + + default_port = http_client.HTTPS_PORT + + def __init__(self, host, port=default_port, key_file=None, cert_file=None, + ca_certs=None, strict=None, **kwargs): + """Constructor. + + Args: + host: The hostname. Can be in 'host:port' form. + port: The port. Defaults to 443. + key_file: A file containing the client's private key + cert_file: A file containing the client's certificates + ca_certs: A file contianing a set of concatenated certificate authority + certs for validating the server against. + strict: When true, causes BadStatusLine to be raised if the status line + can't be parsed as a valid HTTP/1.0 or 1.1 status line. + """ + if six.PY2: + # Python 3.2 and newer have deprecated and removed the strict + # parameter. Since the params are supported as keyword arguments + # we conditionally add it here. + kwargs['strict'] = strict + + http_client.HTTPConnection.__init__(self, host=host, port=port, **kwargs) + self.key_file = key_file + self.cert_file = cert_file + self.ca_certs = ca_certs + + def connect(self): + "Connect to a host on a given (SSL) port." + if hasattr(self, "timeout"): + sock = socket.create_connection((self.host, self.port), self.timeout) + else: + sock = socket.create_connection((self.host, self.port)) + msg = "wrapping ssl socket; " + if self.ca_certs: + msg += "CA certificate file=%s" % self.ca_certs + else: + msg += "using system provided SSL certs" + boto.log.debug(msg) + self.sock = ssl.wrap_socket(sock, keyfile=self.key_file, + certfile=self.cert_file, + cert_reqs=ssl.CERT_REQUIRED, + ca_certs=self.ca_certs) + cert = self.sock.getpeercert() + hostname = self.host.split(':', 0)[0] + if not ValidateCertificateHostname(cert, hostname): + raise InvalidCertificateException(hostname, + cert, + 'remote hostname "%s" does not match ' + 'certificate' % hostname) diff --git a/boto/jsonresponse.py b/boto/jsonresponse.py index ac3f1b4a..f872b429 100644 --- a/boto/jsonresponse.py +++ b/boto/jsonresponse.py @@ -23,6 +23,7 @@ import xml.sax from boto import utils + class XmlHandler(xml.sax.ContentHandler): def __init__(self, root_node, connection): @@ -52,7 +53,8 @@ class XmlHandler(xml.sax.ContentHandler): if not isinstance(s, bytes): s = s.encode('utf-8') xml.sax.parseString(s, self) - + + class Element(dict): def __init__(self, connection=None, element_name=None, @@ -116,6 +118,7 @@ class Element(dict): elif isinstance(self.parent, ListElement): self.parent.append(value) + class ListElement(list): def __init__(self, connection=None, element_name=None, diff --git a/boto/plugin.py b/boto/plugin.py index f8b592cc..2c2931c9 100644 --- a/boto/plugin.py +++ b/boto/plugin.py @@ -14,7 +14,7 @@ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL- # ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT -# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS # IN THE SOFTWARE. @@ -26,19 +26,20 @@ Implements plugin related api. To define a new plugin just subclass Plugin, like this. class AuthPlugin(Plugin): - pass + pass Then start creating subclasses of your new plugin. class MyFancyAuth(AuthPlugin): - capability = ['sign', 'vmac'] + capability = ['sign', 'vmac'] The actual interface is duck typed. - """ import glob -import imp, os.path +import imp +import os.path + class Plugin(object): """Base class for all plugins.""" @@ -50,10 +51,11 @@ class Plugin(object): """Returns true if the requested capability is supported by this plugin """ for c in requested_capability: - if not c in cls.capability: + if c not in cls.capability: return False return True + def get_plugin(cls, requested_capability=None): if not requested_capability: requested_capability = [] @@ -63,18 +65,20 @@ def get_plugin(cls, requested_capability=None): result.append(handler) return result + def _import_module(filename): (path, name) = os.path.split(filename) (name, ext) = os.path.splitext(name) (file, filename, data) = imp.find_module(name, [path]) try: - return imp.load_module(name, file, filename, data) + return imp.load_module(name, file, filename, data) finally: - if file: - file.close() + if file: + file.close() + +_plugin_loaded = False -_plugin_loaded = False def load_plugins(config): global _plugin_loaded @@ -87,4 +91,3 @@ def load_plugins(config): directory = config.get('Plugin', 'plugin_directory') for file in glob.glob(os.path.join(directory, '*.py')): _import_module(file) - diff --git a/boto/provider.py b/boto/provider.py index 8e04bff3..0da2f78a 100644 --- a/boto/provider.py +++ b/boto/provider.py @@ -69,7 +69,8 @@ STORAGE_PERMISSIONS_ERROR = 'StoragePermissionsError' STORAGE_RESPONSE_ERROR = 'StorageResponseError' -class ProfileNotFoundError(ValueError): pass +class ProfileNotFoundError(ValueError): + pass class Provider(object): @@ -252,7 +253,7 @@ class Provider(object): # datetime docs. seconds_left = ( (delta.microseconds + (delta.seconds + delta.days * 24 * 3600) - * 10**6) / 10**6) + * 10 ** 6) / 10 ** 6) if seconds_left < (5 * 60): boto.log.debug("Credentials need to be refreshed.") return True @@ -444,6 +445,7 @@ class Provider(object): def supports_chunked_transfer(self): return self.ChunkedTransferSupport[self.name] + # Static utility method for getting default Provider. def get_default(): return Provider('aws') diff --git a/boto/regioninfo.py b/boto/regioninfo.py index 5862f16d..6aeda122 100644 --- a/boto/regioninfo.py +++ b/boto/regioninfo.py @@ -124,7 +124,7 @@ def get_regions(service_name, region_cls=None, connection_cls=None): """ endpoints = load_regions() - if not service_name in endpoints: + if service_name not in endpoints: raise BotoClientError( "Service '%s' not found in endpoints." % service_name ) diff --git a/boto/requestlog.py b/boto/requestlog.py index 5f1c2551..d8009fe7 100644 --- a/boto/requestlog.py +++ b/boto/requestlog.py @@ -1,9 +1,11 @@ - +import sys from datetime import datetime from threading import Thread import Queue from boto.utils import RequestHook +from boto.compat import long_type + class RequestLogger(RequestHook): """ @@ -14,18 +16,16 @@ class RequestLogger(RequestHook): self.request_log_file = open(filename, 'w') self.request_log_queue = Queue.Queue(100) Thread(target=self._request_log_worker).start() - def handle_request_data(self, request, response, error=False): len = 0 if error else response.getheader('Content-Length') now = datetime.now() time = now.strftime('%Y-%m-%d %H:%M:%S') td = (now - request.start_time) - duration = (td.microseconds + long(td.seconds + td.days*24*3600) * 1e6) / 1e6 - + duration = (td.microseconds + long_type(td.seconds + td.days * 24 * 3600) * 1e6) / 1e6 + # write output including timestamp, status code, response time, response size, request action self.request_log_queue.put("'%s', '%s', '%s', '%s', '%s'\n" % (time, response.status, duration, len, request.params['Action'])) - def _request_log_worker(self): while True: @@ -35,5 +35,5 @@ class RequestLogger(RequestHook): self.request_log_file.flush() self.request_log_queue.task_done() except: - import traceback; traceback.print_exc(file=sys.stdout) - + import traceback + traceback.print_exc(file=sys.stdout) diff --git a/boto/resultset.py b/boto/resultset.py index 83052582..189a47a3 100644 --- a/boto/resultset.py +++ b/boto/resultset.py @@ -21,14 +21,15 @@ from boto.s3.user import User + class ResultSet(list): """ The ResultSet is used to pass results back from the Amazon services to the client. It is light wrapper around Python's :py:class:`list` class, - with some additional methods for parsing XML results from AWS. - Because I don't really want any dependencies on external libraries, - I'm using the standard SAX parser that comes with Python. The good news is - that it's quite fast and efficient but it makes some things rather + with some additional methods for parsing XML results from AWS. + Because I don't really want any dependencies on external libraries, + I'm using the standard SAX parser that comes with Python. The good news is + that it's quite fast and efficient but it makes some things rather difficult. You can pass in, as the marker_elem parameter, a list of tuples. @@ -54,7 +55,7 @@ class ResultSet(list): self.next_key_marker = None self.next_upload_id_marker = None self.next_version_id_marker = None - self.next_generation_marker= None + self.next_generation_marker = None self.version_id_marker = None self.is_truncated = False self.next_token = None @@ -132,6 +133,7 @@ class ResultSet(list): else: setattr(self, name, value) + class BooleanResult(object): def __init__(self, marker_elem=None): diff --git a/boto/route53/connection.py b/boto/route53/connection.py index 9f17781a..c13ab2e0 100644 --- a/boto/route53/connection.py +++ b/boto/route53/connection.py @@ -521,12 +521,18 @@ class Route53Connection(AWSAuthConnection): if response.status == 400: code = response.getheader('Code') - if code and 'PriorRequestNotComplete' in code: + if code: # This is a case where we need to ignore a 400 error, as # Route53 returns this. See # http://docs.aws.amazon.com/Route53/latest/DeveloperGuide/DNSLimitations.html + if 'PriorRequestNotComplete' in code: + error = 'PriorRequestNotComplete' + elif 'Throttling' in code: + error = 'Throttling' + else: + return status msg = "%s, retry attempt %s" % ( - 'PriorRequestNotComplete', + error, i ) next_sleep = min(random.random() * (2 ** i), diff --git a/boto/route53/zone.py b/boto/route53/zone.py index 167a0891..b21c8de4 100644 --- a/boto/route53/zone.py +++ b/boto/route53/zone.py @@ -233,7 +233,14 @@ class Zone(object): # name/type for get_all_rrsets sets the starting record; they # are not a filter - results = [r for r in returned if r.name == name and r.type == type] + results = [] + for r in returned: + if r.name == name and r.type == type: + results.append(r) + # Is at the end of the list of matched records. No need to continue + # since the records are sorted by name and type. + else: + break weight = None region = None diff --git a/boto/storage_uri.py b/boto/storage_uri.py index 40fee473..34b7b060 100755 --- a/boto/storage_uri.py +++ b/boto/storage_uri.py @@ -87,8 +87,8 @@ class StorageUri(object): for arg in args: if args[arg]: sys.stderr.write( - 'Warning: %s ignores argument: %s=%s\n' % - (function_name, arg, str(args[arg]))) + 'Warning: %s ignores argument: %s=%s\n' % + (function_name, arg, str(args[arg]))) def connect(self, access_key_id=None, secret_access_key=None, **kwargs): """ @@ -103,7 +103,7 @@ class StorageUri(object): connection_args = dict(self.connection_args or ()) if (hasattr(self, 'suppress_consec_slashes') and - 'suppress_consec_slashes' not in connection_args): + 'suppress_consec_slashes' not in connection_args): connection_args['suppress_consec_slashes'] = ( self.suppress_consec_slashes) connection_args.update(kwargs) @@ -167,7 +167,7 @@ class StorageUri(object): if all_versions: return (v for v in bucket.list_versions( prefix=prefix, delimiter=delimiter, headers=headers) - if not isinstance(v, DeleteMarker)) + if not isinstance(v, DeleteMarker)) else: return bucket.list(prefix=prefix, delimiter=delimiter, headers=headers) @@ -247,7 +247,7 @@ class BucketStorageUri(StorageUri): """ delim = '/' - capabilities = set([]) # A set of additional capabilities. + capabilities = set([]) # A set of additional capabilities. def __init__(self, scheme, bucket_name=None, object_name=None, debug=0, connection_args=None, suppress_consec_slashes=True, @@ -299,37 +299,37 @@ class BucketStorageUri(StorageUri): self._build_uri_strings() def _build_uri_strings(self): - if self.bucket_name and self.object_name: - self.versionless_uri = '%s://%s/%s' % (self.scheme, self.bucket_name, - self.object_name) - if self.generation: - self.version_specific_uri = '%s#%s' % (self.versionless_uri, - self.generation) - elif self.version_id: - self.version_specific_uri = '%s#%s' % ( - self.versionless_uri, self.version_id) - if self.is_version_specific: - self.uri = self.version_specific_uri - else: - self.uri = self.versionless_uri - elif self.bucket_name: - self.uri = ('%s://%s/' % (self.scheme, self.bucket_name)) - else: - self.uri = ('%s://' % self.scheme) + if self.bucket_name and self.object_name: + self.versionless_uri = '%s://%s/%s' % (self.scheme, self.bucket_name, + self.object_name) + if self.generation: + self.version_specific_uri = '%s#%s' % (self.versionless_uri, + self.generation) + elif self.version_id: + self.version_specific_uri = '%s#%s' % ( + self.versionless_uri, self.version_id) + if self.is_version_specific: + self.uri = self.version_specific_uri + else: + self.uri = self.versionless_uri + elif self.bucket_name: + self.uri = ('%s://%s/' % (self.scheme, self.bucket_name)) + else: + self.uri = ('%s://' % self.scheme) def _update_from_key(self, key): - self._update_from_values( - getattr(key, 'version_id', None), - getattr(key, 'generation', None), - getattr(key, 'is_latest', None), - getattr(key, 'md5', None)) + self._update_from_values( + getattr(key, 'version_id', None), + getattr(key, 'generation', None), + getattr(key, 'is_latest', None), + getattr(key, 'md5', None)) def _update_from_values(self, version_id, generation, is_latest, md5): - self.version_id = version_id - self.generation = generation - self.is_latest = is_latest - self._build_uri_strings() - self.md5 = md5 + self.version_id = version_id + self.generation = generation + self.is_latest = is_latest + self._build_uri_strings() + self.md5 = md5 def get_key(self, validate=False, headers=None, version_id=None): self._check_object_uri('get_key') @@ -388,14 +388,14 @@ class BucketStorageUri(StorageUri): is_latest = key.is_latest return BucketStorageUri( - key.provider.get_provider_name(), - bucket_name=key.bucket.name, - object_name=key.name, - debug=self.debug, - suppress_consec_slashes=self.suppress_consec_slashes, - version_id=version_id, - generation=generation, - is_latest=is_latest) + key.provider.get_provider_name(), + bucket_name=key.bucket.name, + object_name=key.name, + debug=self.debug, + suppress_consec_slashes=self.suppress_consec_slashes, + version_id=version_id, + generation=generation, + is_latest=is_latest) def get_acl(self, validate=False, headers=None, version_id=None): """returns a bucket's acl""" @@ -464,8 +464,8 @@ class BucketStorageUri(StorageUri): 'URIs.' % self.scheme) if self.object_name: if recursive: - raise ValueError('add_group_email_grant() on key-ful URI cannot ' - 'specify recursive=True') + raise ValueError('add_group_email_grant() on key-ful URI cannot ' + 'specify recursive=True') key = self.get_key(validate, headers) self.check_response(key, 'key', self.uri) key.add_group_email_grant(permission, email_address, headers) @@ -556,10 +556,10 @@ class BucketStorageUri(StorageUri): # Pass storage_class param only if this is a GCS bucket. (In S3 the # storage class is specified on the key object.) if self.scheme == 'gs': - return conn.create_bucket(self.bucket_name, headers, location, policy, - storage_class) + return conn.create_bucket(self.bucket_name, headers, location, policy, + storage_class) else: - return conn.create_bucket(self.bucket_name, headers, location, policy) + return conn.create_bucket(self.bucket_name, headers, location, policy) def delete_bucket(self, headers=None): self._check_bucket_uri('delete_bucket') @@ -583,27 +583,27 @@ class BucketStorageUri(StorageUri): key_name = key_name or self.object_name or '' bucket = self.get_bucket(validate, headers) if self.generation: - bucket.set_acl( - acl_or_str, key_name, headers, generation=self.generation, - if_generation=if_generation, if_metageneration=if_metageneration) + bucket.set_acl( + acl_or_str, key_name, headers, generation=self.generation, + if_generation=if_generation, if_metageneration=if_metageneration) else: - version_id = version_id or self.version_id - bucket.set_acl(acl_or_str, key_name, headers, version_id) + version_id = version_id or self.version_id + bucket.set_acl(acl_or_str, key_name, headers, version_id) def set_xml_acl(self, xmlstring, key_name='', validate=False, headers=None, - version_id=None, if_generation=None, if_metageneration=None): + version_id=None, if_generation=None, if_metageneration=None): """Sets or updates a bucket's ACL with an XML string.""" self._check_bucket_uri('set_xml_acl') key_name = key_name or self.object_name or '' bucket = self.get_bucket(validate, headers) if self.generation: - bucket.set_xml_acl( - xmlstring, key_name, headers, generation=self.generation, - if_generation=if_generation, if_metageneration=if_metageneration) + bucket.set_xml_acl( + xmlstring, key_name, headers, generation=self.generation, + if_generation=if_generation, if_metageneration=if_metageneration) else: - version_id = version_id or self.version_id - bucket.set_xml_acl(xmlstring, key_name, headers, - version_id=version_id) + version_id = version_id or self.version_id + bucket.set_xml_acl(xmlstring, key_name, headers, + version_id=version_id) def set_def_xml_acl(self, xmlstring, validate=False, headers=None): """Sets or updates a bucket's default object ACL with an XML string.""" @@ -699,14 +699,16 @@ class BucketStorageUri(StorageUri): self._check_object_uri('copy_key') dst_bucket = self.get_bucket(validate=False, headers=headers) if src_generation: - return dst_bucket.copy_key(new_key_name=self.object_name, + return dst_bucket.copy_key( + new_key_name=self.object_name, src_bucket_name=src_bucket_name, src_key_name=src_key_name, metadata=metadata, storage_class=storage_class, preserve_acl=preserve_acl, encrypt_key=encrypt_key, headers=headers, query_args=query_args, src_generation=src_generation) else: - return dst_bucket.copy_key(new_key_name=self.object_name, + return dst_bucket.copy_key( + new_key_name=self.object_name, src_bucket_name=src_bucket_name, src_key_name=src_key_name, metadata=metadata, src_version_id=src_version_id, storage_class=storage_class, preserve_acl=preserve_acl, @@ -766,7 +768,7 @@ class BucketStorageUri(StorageUri): component_keys.append(suri.new_key()) component_keys[-1].generation = suri.generation self.generation = self.new_key().compose( - component_keys, content_type=content_type, headers=headers) + component_keys, content_type=content_type, headers=headers) self._build_uri_strings() return self @@ -786,12 +788,13 @@ class BucketStorageUri(StorageUri): bucket.configure_lifecycle(lifecycle_config, headers) def exists(self, headers=None): - """Returns True if the object exists or False if it doesn't""" - if not self.object_name: - raise InvalidUriError('exists on object-less URI (%s)' % self.uri) - bucket = self.get_bucket() - key = bucket.get_key(self.object_name, headers=headers) - return bool(key) + """Returns True if the object exists or False if it doesn't""" + if not self.object_name: + raise InvalidUriError('exists on object-less URI (%s)' % self.uri) + bucket = self.get_bucket() + key = bucket.get_key(self.object_name, headers=headers) + return bool(key) + class FileStorageUri(StorageUri): """ @@ -881,8 +884,8 @@ class FileStorageUri(StorageUri): self.get_key().close() def exists(self, _headers_not_used=None): - """Returns True if the file exists or False if it doesn't""" - # The _headers_not_used parameter is ignored. It is only there to ensure - # that this method's signature is identical to the exists method on the - # BucketStorageUri class. - return os.path.exists(self.object_name) + """Returns True if the file exists or False if it doesn't""" + # The _headers_not_used parameter is ignored. It is only there to ensure + # that this method's signature is identical to the exists method on the + # BucketStorageUri class. + return os.path.exists(self.object_name) diff --git a/boto/sts/connection.py b/boto/sts/connection.py index 59526222..e02f0f1e 100644 --- a/boto/sts/connection.py +++ b/boto/sts/connection.py @@ -92,7 +92,7 @@ class STSConnection(AWSQueryConnection): if self.anon: return ['pure-query'] else: - return ['sign-v2'] + return ['hmac-v4'] def _check_token_cache(self, token_key, duration=None, window_seconds=60): token = _session_token_cache.get(token_key, None) diff --git a/boto/utils.py b/boto/utils.py index dca332fe..0e7e3a79 100644 --- a/boto/utils.py +++ b/boto/utils.py @@ -392,7 +392,7 @@ def get_instance_metadata(version='latest', url='http://169.254.169.254', try: metadata_url = _build_instance_metadata_url(url, version, data) return _get_instance_metadata(metadata_url, num_retries=num_retries, timeout=timeout) - except urllib.error.URLError as e: + except urllib.error.URLError: return None @@ -414,7 +414,7 @@ def get_instance_identity(version='latest', url='http://169.254.169.254', if field: iid[field] = val return iid - except urllib.error.URLError as e: + except urllib.error.URLError: return None @@ -436,6 +436,7 @@ ISO8601_MS = '%Y-%m-%dT%H:%M:%S.%fZ' RFC1123 = '%a, %d %b %Y %H:%M:%S %Z' LOCALE_LOCK = threading.Lock() + @contextmanager def setlocale(name): """ @@ -449,6 +450,7 @@ def setlocale(name): finally: locale.setlocale(locale.LC_ALL, saved) + def get_ts(ts=None): if not ts: ts = time.gmtime() @@ -1038,6 +1040,7 @@ def merge_headers_by_name(name, headers): return ','.join(str(headers[h]) for h in matching_headers if headers[h] is not None) + class RequestHook(object): """ This can be extended and supplied to the connection object diff --git a/boto/vpc/routetable.py b/boto/vpc/routetable.py index 7f83717e..21060ee9 100644 --- a/boto/vpc/routetable.py +++ b/boto/vpc/routetable.py @@ -67,6 +67,8 @@ class Route(object): self.destination_cidr_block = None self.gateway_id = None self.instance_id = None + self.interface_id = None + self.vpc_peering_connection_id = None self.state = None def __repr__(self): @@ -82,6 +84,10 @@ class Route(object): self.gateway_id = value elif name == 'instanceId': self.instance_id = value + elif name == 'networkInterfaceId': + self.interface_id = value + elif name == 'vpcPeeringConnectionId': + self.vpc_peering_connection_id = value elif name == 'state': self.state = value |