|
|
- # Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
- #
- # Licensed under the Apache License, Version 2.0 (the "License"). You
- # may not use this file except in compliance with the License. A copy of
- # the License is located at
- #
- # http://aws.amazon.com/apache2.0/
- #
- # or in the "license" file accompanying this file. This file is
- # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
- # ANY KIND, either express or implied. See the License for the specific
- # language governing permissions and limitations under the License.
- import boto.vendored.regions.regions as _regions
-
-
- class _CompatEndpointResolver(_regions.EndpointResolver):
- """Endpoint resolver which handles boto2 compatibility concerns.
-
- This is NOT intended for external use whatsoever.
- """
-
- _DEFAULT_SERVICE_RENAMES = {
- # The botocore resolver is based on endpoint prefix.
- # These don't always sync up to the name that boto2 uses.
- # A mapping can be provided that handles the mapping between
- # "service names" and endpoint prefixes.
- 'awslambda': 'lambda',
- 'cloudwatch': 'monitoring',
- 'ses': 'email',
- 'ec2containerservice': 'ecs',
- 'configservice': 'config',
- }
-
- def __init__(self, endpoint_data, service_rename_map=None):
- """
- :type endpoint_data: dict
- :param endpoint_data: Regions and endpoints data in the same format
- as is used by botocore / boto3.
-
- :type service_rename_map: dict
- :param service_rename_map: A mapping of boto2 service name to
- endpoint prefix.
- """
- super(_CompatEndpointResolver, self).__init__(endpoint_data)
- if service_rename_map is None:
- service_rename_map = self._DEFAULT_SERVICE_RENAMES
- # Mapping of boto2 service name to endpoint prefix
- self._endpoint_prefix_map = service_rename_map
- # Mapping of endpoint prefix to boto2 service name
- self._service_name_map = dict(
- (v, k) for k, v in service_rename_map.items())
-
- def get_available_endpoints(self, service_name, partition_name='aws',
- allow_non_regional=False):
- endpoint_prefix = self._endpoint_prefix(service_name)
- return super(_CompatEndpointResolver, self).get_available_endpoints(
- endpoint_prefix, partition_name, allow_non_regional)
-
- def get_all_available_regions(self, service_name):
- """Retrieve every region across partitions for a service."""
- regions = set()
- endpoint_prefix = self._endpoint_prefix(service_name)
-
- # Get every region for every partition in the new endpoint format
- for partition_name in self.get_available_partitions():
- if self._is_global_service(service_name, partition_name):
- # Global services are available in every region in the
- # partition in which they are considered global.
- partition = self._get_partition_data(partition_name)
- regions.update(partition['regions'].keys())
- continue
- else:
- regions.update(
- self.get_available_endpoints(
- endpoint_prefix, partition_name)
- )
-
- return list(regions)
-
- def construct_endpoint(self, service_name, region_name=None):
- endpoint_prefix = self._endpoint_prefix(service_name)
- return super(_CompatEndpointResolver, self).construct_endpoint(
- endpoint_prefix, region_name)
-
- def get_available_services(self):
- """Get a list of all the available services in the endpoints file(s)"""
- services = set()
-
- for partition in self._endpoint_data['partitions']:
- services.update(partition['services'].keys())
-
- return [self._service_name(s) for s in services]
-
- def _is_global_service(self, service_name, partition_name='aws'):
- """Determines whether a service uses a global endpoint.
-
- In theory a service can be 'global' in one partition but regional in
- another. In practice, each service is all global or all regional.
- """
- endpoint_prefix = self._endpoint_prefix(service_name)
- partition = self._get_partition_data(partition_name)
- service = partition['services'].get(endpoint_prefix, {})
- return 'partitionEndpoint' in service
-
- def _get_partition_data(self, partition_name):
- """Get partition information for a particular partition.
-
- This should NOT be used to get service endpoint data because it only
- loads from the new endpoint format. It should only be used for
- partition metadata and partition specific service metadata.
-
- :type partition_name: str
- :param partition_name: The name of the partition to search for.
-
- :returns: Partition info from the new endpoints format.
- :rtype: dict or None
- """
- for partition in self._endpoint_data['partitions']:
- if partition['partition'] == partition_name:
- return partition
- raise ValueError(
- "Could not find partition data for: %s" % partition_name)
-
- def _endpoint_prefix(self, service_name):
- """Given a boto2 service name, get the endpoint prefix."""
- return self._endpoint_prefix_map.get(service_name, service_name)
-
- def _service_name(self, endpoint_prefix):
- """Given an endpoint prefix, get the boto2 service name."""
- return self._service_name_map.get(endpoint_prefix, endpoint_prefix)
-
-
- class BotoEndpointResolver(object):
- """Resolves endpoint hostnames for AWS services.
-
- This is NOT intended for external use.
- """
-
- def __init__(self, endpoint_data, service_rename_map=None):
- """
- :type endpoint_data: dict
- :param endpoint_data: Regions and endpoints data in the same format
- as is used by botocore / boto3.
-
- :type service_rename_map: dict
- :param service_rename_map: A mapping of boto2 service name to
- endpoint prefix.
- """
- self._resolver = _CompatEndpointResolver(
- endpoint_data, service_rename_map)
-
- def resolve_hostname(self, service_name, region_name):
- """Resolve the hostname for a service in a particular region.
-
- :type service_name: str
- :param service_name: The service to look up.
-
- :type region_name: str
- :param region_name: The region to find the endpoint for.
-
- :return: The hostname for the given service in the given region.
- """
- endpoint = self._resolver.construct_endpoint(service_name, region_name)
- if endpoint is None:
- return None
- return endpoint.get('sslCommonName', endpoint['hostname'])
-
- def get_all_available_regions(self, service_name):
- """Get all the regions a service is available in.
-
- :type service_name: str
- :param service_name: The service to look up.
-
- :rtype: list of str
- :return: A list of all the regions the given service is available in.
- """
- return self._resolver.get_all_available_regions(service_name)
-
- def get_available_services(self):
- """Get all the services supported by the endpoint data.
-
- :rtype: list of str
- :return: A list of all the services explicitly contained within the
- endpoint data provided during instantiation.
- """
- return self._resolver.get_available_services()
-
-
- class StaticEndpointBuilder(object):
- """Builds a static mapping of endpoints in the legacy format."""
-
- def __init__(self, resolver):
- """
- :type resolver: BotoEndpointResolver
- :param resolver: An endpoint resolver.
- """
- self._resolver = resolver
-
- def build_static_endpoints(self, service_names=None):
- """Build a set of static endpoints in the legacy boto2 format.
-
- :param service_names: The names of the services to build. They must
- use the names that boto2 uses, not boto3, e.g "ec2containerservice"
- and not "ecs". If no service names are provided, all available
- services will be built.
-
- :return: A dict consisting of::
- {"service": {"region": "full.host.name"}}
- """
- if service_names is None:
- service_names = self._resolver.get_available_services()
-
- static_endpoints = {}
- for name in service_names:
- endpoints_for_service = self._build_endpoints_for_service(name)
- if endpoints_for_service:
- # It's possible that when we try to build endpoints for
- # services we get an empty hash. In that case we don't
- # bother adding it to the final list of static endpoints.
- static_endpoints[name] = endpoints_for_service
- self._handle_special_cases(static_endpoints)
- return static_endpoints
-
- def _build_endpoints_for_service(self, service_name):
- # Given a service name, 'ec2', build a dict of
- # 'region' -> 'hostname'
- endpoints = {}
- regions = self._resolver.get_all_available_regions(service_name)
- for region_name in regions:
- endpoints[region_name] = self._resolver.resolve_hostname(
- service_name, region_name)
- return endpoints
-
- def _handle_special_cases(self, static_endpoints):
- # cloudsearchdomain endpoints use the exact same set of endpoints as
- # cloudsearch.
- if 'cloudsearch' in static_endpoints:
- cloudsearch_endpoints = static_endpoints['cloudsearch']
- static_endpoints['cloudsearchdomain'] = cloudsearch_endpoints
|