You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

239 lines
9.5 KiB

4 years ago
  1. # Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License"). You
  4. # may not use this file except in compliance with the License. A copy of
  5. # the License is located at
  6. #
  7. # http://aws.amazon.com/apache2.0/
  8. #
  9. # or in the "license" file accompanying this file. This file is
  10. # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
  11. # ANY KIND, either express or implied. See the License for the specific
  12. # language governing permissions and limitations under the License.
  13. import boto.vendored.regions.regions as _regions
  14. class _CompatEndpointResolver(_regions.EndpointResolver):
  15. """Endpoint resolver which handles boto2 compatibility concerns.
  16. This is NOT intended for external use whatsoever.
  17. """
  18. _DEFAULT_SERVICE_RENAMES = {
  19. # The botocore resolver is based on endpoint prefix.
  20. # These don't always sync up to the name that boto2 uses.
  21. # A mapping can be provided that handles the mapping between
  22. # "service names" and endpoint prefixes.
  23. 'awslambda': 'lambda',
  24. 'cloudwatch': 'monitoring',
  25. 'ses': 'email',
  26. 'ec2containerservice': 'ecs',
  27. 'configservice': 'config',
  28. }
  29. def __init__(self, endpoint_data, service_rename_map=None):
  30. """
  31. :type endpoint_data: dict
  32. :param endpoint_data: Regions and endpoints data in the same format
  33. as is used by botocore / boto3.
  34. :type service_rename_map: dict
  35. :param service_rename_map: A mapping of boto2 service name to
  36. endpoint prefix.
  37. """
  38. super(_CompatEndpointResolver, self).__init__(endpoint_data)
  39. if service_rename_map is None:
  40. service_rename_map = self._DEFAULT_SERVICE_RENAMES
  41. # Mapping of boto2 service name to endpoint prefix
  42. self._endpoint_prefix_map = service_rename_map
  43. # Mapping of endpoint prefix to boto2 service name
  44. self._service_name_map = dict(
  45. (v, k) for k, v in service_rename_map.items())
  46. def get_available_endpoints(self, service_name, partition_name='aws',
  47. allow_non_regional=False):
  48. endpoint_prefix = self._endpoint_prefix(service_name)
  49. return super(_CompatEndpointResolver, self).get_available_endpoints(
  50. endpoint_prefix, partition_name, allow_non_regional)
  51. def get_all_available_regions(self, service_name):
  52. """Retrieve every region across partitions for a service."""
  53. regions = set()
  54. endpoint_prefix = self._endpoint_prefix(service_name)
  55. # Get every region for every partition in the new endpoint format
  56. for partition_name in self.get_available_partitions():
  57. if self._is_global_service(service_name, partition_name):
  58. # Global services are available in every region in the
  59. # partition in which they are considered global.
  60. partition = self._get_partition_data(partition_name)
  61. regions.update(partition['regions'].keys())
  62. continue
  63. else:
  64. regions.update(
  65. self.get_available_endpoints(
  66. endpoint_prefix, partition_name)
  67. )
  68. return list(regions)
  69. def construct_endpoint(self, service_name, region_name=None):
  70. endpoint_prefix = self._endpoint_prefix(service_name)
  71. return super(_CompatEndpointResolver, self).construct_endpoint(
  72. endpoint_prefix, region_name)
  73. def get_available_services(self):
  74. """Get a list of all the available services in the endpoints file(s)"""
  75. services = set()
  76. for partition in self._endpoint_data['partitions']:
  77. services.update(partition['services'].keys())
  78. return [self._service_name(s) for s in services]
  79. def _is_global_service(self, service_name, partition_name='aws'):
  80. """Determines whether a service uses a global endpoint.
  81. In theory a service can be 'global' in one partition but regional in
  82. another. In practice, each service is all global or all regional.
  83. """
  84. endpoint_prefix = self._endpoint_prefix(service_name)
  85. partition = self._get_partition_data(partition_name)
  86. service = partition['services'].get(endpoint_prefix, {})
  87. return 'partitionEndpoint' in service
  88. def _get_partition_data(self, partition_name):
  89. """Get partition information for a particular partition.
  90. This should NOT be used to get service endpoint data because it only
  91. loads from the new endpoint format. It should only be used for
  92. partition metadata and partition specific service metadata.
  93. :type partition_name: str
  94. :param partition_name: The name of the partition to search for.
  95. :returns: Partition info from the new endpoints format.
  96. :rtype: dict or None
  97. """
  98. for partition in self._endpoint_data['partitions']:
  99. if partition['partition'] == partition_name:
  100. return partition
  101. raise ValueError(
  102. "Could not find partition data for: %s" % partition_name)
  103. def _endpoint_prefix(self, service_name):
  104. """Given a boto2 service name, get the endpoint prefix."""
  105. return self._endpoint_prefix_map.get(service_name, service_name)
  106. def _service_name(self, endpoint_prefix):
  107. """Given an endpoint prefix, get the boto2 service name."""
  108. return self._service_name_map.get(endpoint_prefix, endpoint_prefix)
  109. class BotoEndpointResolver(object):
  110. """Resolves endpoint hostnames for AWS services.
  111. This is NOT intended for external use.
  112. """
  113. def __init__(self, endpoint_data, service_rename_map=None):
  114. """
  115. :type endpoint_data: dict
  116. :param endpoint_data: Regions and endpoints data in the same format
  117. as is used by botocore / boto3.
  118. :type service_rename_map: dict
  119. :param service_rename_map: A mapping of boto2 service name to
  120. endpoint prefix.
  121. """
  122. self._resolver = _CompatEndpointResolver(
  123. endpoint_data, service_rename_map)
  124. def resolve_hostname(self, service_name, region_name):
  125. """Resolve the hostname for a service in a particular region.
  126. :type service_name: str
  127. :param service_name: The service to look up.
  128. :type region_name: str
  129. :param region_name: The region to find the endpoint for.
  130. :return: The hostname for the given service in the given region.
  131. """
  132. endpoint = self._resolver.construct_endpoint(service_name, region_name)
  133. if endpoint is None:
  134. return None
  135. return endpoint.get('sslCommonName', endpoint['hostname'])
  136. def get_all_available_regions(self, service_name):
  137. """Get all the regions a service is available in.
  138. :type service_name: str
  139. :param service_name: The service to look up.
  140. :rtype: list of str
  141. :return: A list of all the regions the given service is available in.
  142. """
  143. return self._resolver.get_all_available_regions(service_name)
  144. def get_available_services(self):
  145. """Get all the services supported by the endpoint data.
  146. :rtype: list of str
  147. :return: A list of all the services explicitly contained within the
  148. endpoint data provided during instantiation.
  149. """
  150. return self._resolver.get_available_services()
  151. class StaticEndpointBuilder(object):
  152. """Builds a static mapping of endpoints in the legacy format."""
  153. def __init__(self, resolver):
  154. """
  155. :type resolver: BotoEndpointResolver
  156. :param resolver: An endpoint resolver.
  157. """
  158. self._resolver = resolver
  159. def build_static_endpoints(self, service_names=None):
  160. """Build a set of static endpoints in the legacy boto2 format.
  161. :param service_names: The names of the services to build. They must
  162. use the names that boto2 uses, not boto3, e.g "ec2containerservice"
  163. and not "ecs". If no service names are provided, all available
  164. services will be built.
  165. :return: A dict consisting of::
  166. {"service": {"region": "full.host.name"}}
  167. """
  168. if service_names is None:
  169. service_names = self._resolver.get_available_services()
  170. static_endpoints = {}
  171. for name in service_names:
  172. endpoints_for_service = self._build_endpoints_for_service(name)
  173. if endpoints_for_service:
  174. # It's possible that when we try to build endpoints for
  175. # services we get an empty hash. In that case we don't
  176. # bother adding it to the final list of static endpoints.
  177. static_endpoints[name] = endpoints_for_service
  178. self._handle_special_cases(static_endpoints)
  179. return static_endpoints
  180. def _build_endpoints_for_service(self, service_name):
  181. # Given a service name, 'ec2', build a dict of
  182. # 'region' -> 'hostname'
  183. endpoints = {}
  184. regions = self._resolver.get_all_available_regions(service_name)
  185. for region_name in regions:
  186. endpoints[region_name] = self._resolver.resolve_hostname(
  187. service_name, region_name)
  188. return endpoints
  189. def _handle_special_cases(self, static_endpoints):
  190. # cloudsearchdomain endpoints use the exact same set of endpoints as
  191. # cloudsearch.
  192. if 'cloudsearch' in static_endpoints:
  193. cloudsearch_endpoints = static_endpoints['cloudsearch']
  194. static_endpoints['cloudsearchdomain'] = cloudsearch_endpoints