lib/aws-partitions/endpoint_provider.rb in aws-partitions-1.524.0 vs lib/aws-partitions/endpoint_provider.rb in aws-partitions-1.525.0
- old
+ new
@@ -35,102 +35,182 @@
# @param [String] region The region for the client.
# @param [String] service The endpoint prefix for the service, e.g.
# "monitoring" for cloudwatch.
# @param [String] sts_regional_endpoints [STS only] Whether to use
# `legacy` (global endpoint for legacy regions) or `regional` mode for
- # using regional endpoint for supported regions except 'aws-global'
+ # using regional endpoint for supported regions except 'aws-global'
+ # @param [Hash] variants Endpoint variants such as 'fips' or 'dualstack'
+ # @option variants [Boolean] :dualstack When true, resolve a dualstack
+ # endpoint.
+ # @option variants [Boolean] :fips When true, resolve a FIPS endpoint.
# @api private Use the static class methods instead.
- def resolve(region, service, sts_regional_endpoints)
- 'https://' + endpoint_for(region, service, sts_regional_endpoints)
+ def resolve(region, service, sts_regional_endpoints, variants)
+ 'https://' + endpoint_for(region, service, build_is_global_fn(sts_regional_endpoints), variants)
end
# @api private Use the static class methods instead.
def signing_region(region, service, sts_regional_endpoints)
- credential_scope(region, service, sts_regional_endpoints)
+ credential_scope(region, service, build_is_global_fn(sts_regional_endpoints))
.fetch('region', region)
end
# @api private Use the static class methods instead.
def signing_service(region, service)
# don't default to the service name
# signers should prefer the api metadata's signingName
# if no service is set in the credentialScope
- credential_scope(region, service, 'regional')
+ credential_scope(region, service, build_is_global_fn('regional'))
.fetch('service', nil)
end
+ # @param [String] region The region used to fetch the partition.
+ # @param [String] service Used only if dualstack is true. Used to find a
+ # DNS suffix for a specific service.
+ # @param [Hash] variants Endpoint variants such as 'fips' or 'dualstack'
+ # @option variants [Boolean] :dualstack When true, resolve a dualstack
+ # endpoint.
+ # @option variants [Boolean] :fips When true, resolve a FIPS endpoint.
# @api private Use the static class methods instead.
- def credential_scope(region, service, sts_regional_endpoints)
- partition = get_partition(region)
- service_cfg = partition.fetch('services', {})
- .fetch(service, {})
- endpoints = service_cfg.fetch('endpoints', {})
+ def dns_suffix_for(region, service, variants)
+ if configured_variants?(variants)
+ resolve_variant(region, service, variants)['dnsSuffix']
+ else
+ get_partition(region)['dnsSuffix']
+ end
+ end
- # Check for sts legacy behavior
- sts_legacy = service == 'sts' &&
- sts_regional_endpoints == 'legacy' &&
- STS_LEGACY_REGIONS.include?(region)
+ private
- is_global = !endpoints.key?(region) &&
- service_cfg['isRegionalized'] == false
+ def configured_variants?(variants)
+ variants.values.any?
+ end
- # Check for global endpoint.
- if sts_legacy || is_global
+ def fetch_variant(cfg, tags)
+ variants = cfg.fetch('variants', [])
+ variants.find { |v| tags == Set.new(v['tags']) } || {}
+ end
+
+ def resolve_variant(region, service, config_variants)
+ tags = Set.new(config_variants.select { |_k,v| v == true }.map { |k,_v| k.to_s })
+ is_global_fn = build_is_global_fn # ignore legacy STS config for variants
+
+ partition_cfg = get_partition(region)
+ service_cfg = partition_cfg.fetch('services', {})
+ .fetch(service, {})
+
+ endpoints_cfg = service_cfg.fetch('endpoints', {})
+
+ if is_global_fn.call(service, region, endpoints_cfg, service_cfg)
region = service_cfg.fetch('partitionEndpoint', region)
end
- default_credential_scope = service_cfg
- .fetch('defaults', {})
- .fetch('credentialScope', {})
+ region_cfg = endpoints_cfg.fetch(region, {})
+ warn_deprecation(service, region) if region_cfg['deprecated']
- endpoints
- .fetch(region, {})
- .fetch('credentialScope', default_credential_scope)
+ partition_defaults = fetch_variant(partition_cfg.fetch('defaults', {}), tags)
+ service_defaults = fetch_variant(service_cfg.fetch('defaults', {}), tags)
+ endpoint_cfg = fetch_variant(region_cfg, tags)
+
+ # merge upwards, preferring values from endpoint > service > partition
+ partition_defaults.merge(service_defaults.merge(endpoint_cfg))
end
- # @api private Use the static class methods instead.
- def dns_suffix_for(region)
- get_partition(region)['dnsSuffix']
+ def validate_variant!(config_variants, resolved_variant)
+ unless resolved_variant['hostname'] && resolved_variant['dnsSuffix']
+ enabled_variants = config_variants.select { |_k, v| v}.map { |k, _v| k.to_s }.join(', ')
+ raise ArgumentError,
+ "#{enabled_variants} not supported for this region and partition."
+ end
end
- private
+ def endpoint_for(region, service, is_global_fn, variants)
+ if configured_variants?(variants)
+ endpoint_with_variants_for(region, service, variants)
+ else
+ endpoint_no_variants_for(region, service, is_global_fn)
+ end
+ end
- def endpoint_for(region, service, sts_regional_endpoints)
+ def endpoint_with_variants_for(region, service, variants)
+ variant = resolve_variant(region, service, variants)
+ validate_variant!(variants, variant)
+ variant['hostname'].sub('{region}', region)
+ .sub('{service}', service)
+ .sub('{dnsSuffix}', variant['dnsSuffix'])
+ end
+
+ def endpoint_no_variants_for(region, service, is_global_fn)
partition = get_partition(region)
service_cfg = partition.fetch('services', {}).fetch(service, {})
# Find the default endpoint
default_endpoint = service_cfg
.fetch('defaults', {})
.fetch('hostname', partition['defaults']['hostname'])
endpoints = service_cfg.fetch('endpoints', {})
- # Check for sts legacy behavior
- sts_legacy = service == 'sts' &&
- sts_regional_endpoints == 'legacy' &&
- STS_LEGACY_REGIONS.include?(region)
-
- is_global = !endpoints.key?(region) &&
- service_cfg['isRegionalized'] == false
-
# Check for global endpoint.
- if sts_legacy || is_global
+ if is_global_fn.call(service, region, endpoints, service_cfg)
region = service_cfg.fetch('partitionEndpoint', region)
end
# Check for service/region level endpoint.
- endpoint = endpoints
+ region_cfg = endpoints
.fetch(region, {})
+ endpoint = region_cfg
.fetch('hostname', default_endpoint)
+ warn_deprecation(service, region) if region_cfg['deprecated']
+
# Replace placeholders from the endpoints
endpoint.sub('{region}', region)
.sub('{service}', service)
.sub('{dnsSuffix}', partition['dnsSuffix'])
end
+ def warn_deprecation(service, region)
+ warn("The endpoint for service: #{service}, region: #{region}"\
+ ' is deprecated.')
+ end
+
+ # returns a callable that takes a region
+ # and returns true if the service is global
+ def build_is_global_fn(sts_regional_endpoints='regional')
+ lambda do |service, region, endpoints, service_cfg|
+ # Check for sts legacy behavior
+ sts_legacy = service == 'sts' &&
+ sts_regional_endpoints == 'legacy' &&
+ STS_LEGACY_REGIONS.include?(region)
+
+ is_global = !endpoints.key?(region) &&
+ service_cfg['isRegionalized'] == false
+
+ sts_legacy || is_global
+ end
+ end
+
+ def credential_scope(region, service, is_global_fn)
+ partition = get_partition(region)
+ service_cfg = partition.fetch('services', {})
+ .fetch(service, {})
+ endpoints = service_cfg.fetch('endpoints', {})
+
+ # Check for global endpoint.
+ if is_global_fn.call(service, region, endpoints, service_cfg)
+ region = service_cfg.fetch('partitionEndpoint', region)
+ end
+
+ default_credential_scope = service_cfg
+ .fetch('defaults', {})
+ .fetch('credentialScope', {})
+
+ endpoints
+ .fetch(region, {})
+ .fetch('credentialScope', default_credential_scope)
+ end
+
def get_partition(region_or_partition)
partition_containing_region(region_or_partition) ||
partition_matching_region(region_or_partition) ||
partition_matching_name(region_or_partition) ||
default_partition
@@ -142,11 +222,11 @@
end
end
def partition_matching_region(region)
@rules['partitions'].find do |p|
- region.match(p['regionRegex']) ||
+ p['regionRegex'] && region.match(p['regionRegex']) ||
p['services'].values.find do |svc|
svc['endpoints'].key?(region) if svc.key?('endpoints')
end
end
end
@@ -159,23 +239,23 @@
@rules['partitions'].find { |p| p['partition'] == 'aws' } ||
@rules['partitions'].first
end
class << self
- def resolve(region, service, sts_regional_endpoints = 'regional')
- default_provider.resolve(region, service, sts_regional_endpoints)
+ def resolve(region, service, sts_endpoint = 'regional', variants = {})
+ default_provider.resolve(region, service, sts_endpoint, variants)
end
def signing_region(region, service, sts_regional_endpoints = 'regional')
default_provider.signing_region(region, service, sts_regional_endpoints)
end
def signing_service(region, service)
default_provider.signing_service(region, service)
end
- def dns_suffix_for(region)
- default_provider.dns_suffix_for(region)
+ def dns_suffix_for(region, service = nil, variants = {})
+ default_provider.dns_suffix_for(region, service, variants)
end
private
def default_provider