@@ -37,15 +37,19 @@ def initialize(rules)
37
37
# "monitoring" for cloudwatch.
38
38
# @param [String] sts_regional_endpoints [STS only] Whether to use
39
39
# `legacy` (global endpoint for legacy regions) or `regional` mode for
40
- # using regional endpoint for supported regions except 'aws-global'
40
+ # using regional endpoint for supported regions except 'aws-global'
41
+ # @param [Hash] variants Endpoint variants such as 'fips' or 'dualstack'
42
+ # @option variants [Boolean] :dualstack When true, resolve a dualstack
43
+ # endpoint.
44
+ # @option variants [Boolean] :fips When true, resolve a FIPS endpoint.
41
45
# @api private Use the static class methods instead.
42
- def resolve ( region , service , sts_regional_endpoints )
43
- 'https://' + endpoint_for ( region , service , sts_regional_endpoints )
46
+ def resolve ( region , service , sts_regional_endpoints , variants )
47
+ 'https://' + endpoint_for ( region , service , build_is_global_fn ( sts_regional_endpoints ) , variants )
44
48
end
45
49
46
50
# @api private Use the static class methods instead.
47
51
def signing_region ( region , service , sts_regional_endpoints )
48
- credential_scope ( region , service , sts_regional_endpoints )
52
+ credential_scope ( region , service , build_is_global_fn ( sts_regional_endpoints ) )
49
53
. fetch ( 'region' , region )
50
54
end
51
55
@@ -54,47 +58,87 @@ def signing_service(region, service)
54
58
# don't default to the service name
55
59
# signers should prefer the api metadata's signingName
56
60
# if no service is set in the credentialScope
57
- credential_scope ( region , service , 'regional' )
61
+ credential_scope ( region , service , build_is_global_fn ( 'regional' ) )
58
62
. fetch ( 'service' , nil )
59
63
end
60
64
65
+ # @param [String] region The region used to fetch the partition.
66
+ # @param [String] service Used only if dualstack is true. Used to find a
67
+ # DNS suffix for a specific service.
68
+ # @param [Hash] variants Endpoint variants such as 'fips' or 'dualstack'
69
+ # @option variants [Boolean] :dualstack When true, resolve a dualstack
70
+ # endpoint.
71
+ # @option variants [Boolean] :fips When true, resolve a FIPS endpoint.
61
72
# @api private Use the static class methods instead.
62
- def credential_scope ( region , service , sts_regional_endpoints )
63
- partition = get_partition ( region )
64
- service_cfg = partition . fetch ( 'services' , { } )
65
- . fetch ( service , { } )
66
- endpoints = service_cfg . fetch ( 'endpoints' , { } )
73
+ def dns_suffix_for ( region , service , variants )
74
+ if configured_variants? ( variants )
75
+ resolve_variant ( region , service , variants ) [ 'dnsSuffix' ]
76
+ else
77
+ get_partition ( region ) [ 'dnsSuffix' ]
78
+ end
79
+ end
80
+
81
+ private
67
82
68
- # Check for sts legacy behavior
69
- sts_legacy = service == 'sts' &&
70
- sts_regional_endpoints == 'legacy' &&
71
- STS_LEGACY_REGIONS . include? ( region )
83
+ def configured_variants? ( variants )
84
+ variants . values . any?
85
+ end
72
86
73
- is_global = !endpoints . key? ( region ) &&
74
- service_cfg [ 'isRegionalized' ] == false
87
+ def fetch_variant ( cfg , tags )
88
+ variants = cfg . fetch ( 'variants' , [ ] )
89
+ variants . find { |v | tags == Set . new ( v [ 'tags' ] ) } || { }
90
+ end
75
91
76
- # Check for global endpoint.
77
- if sts_legacy || is_global
92
+ def resolve_variant ( region , service , config_variants )
93
+ tags = Set . new ( config_variants . select { |_k , v | v == true } . map { |k , _v | k . to_s } )
94
+ is_global_fn = build_is_global_fn # ignore legacy STS config for variants
95
+
96
+ partition_cfg = get_partition ( region )
97
+ service_cfg = partition_cfg . fetch ( 'services' , { } )
98
+ . fetch ( service , { } )
99
+
100
+ endpoints_cfg = service_cfg . fetch ( 'endpoints' , { } )
101
+
102
+ if is_global_fn . call ( service , region , endpoints_cfg , service_cfg )
78
103
region = service_cfg . fetch ( 'partitionEndpoint' , region )
79
104
end
80
105
81
- default_credential_scope = service_cfg
82
- . fetch ( 'defaults' , { } )
83
- . fetch ( 'credentialScope' , { } )
106
+ region_cfg = endpoints_cfg . fetch ( region , { } )
107
+ warn_deprecation ( service , region ) if region_cfg [ 'deprecated' ]
84
108
85
- endpoints
86
- . fetch ( region , { } )
87
- . fetch ( 'credentialScope' , default_credential_scope )
109
+ partition_defaults = fetch_variant ( partition_cfg . fetch ( 'defaults' , { } ) , tags )
110
+ service_defaults = fetch_variant ( service_cfg . fetch ( 'defaults' , { } ) , tags )
111
+ endpoint_cfg = fetch_variant ( region_cfg , tags )
112
+
113
+ # merge upwards, preferring values from endpoint > service > partition
114
+ partition_defaults . merge ( service_defaults . merge ( endpoint_cfg ) )
88
115
end
89
116
90
- # @api private Use the static class methods instead.
91
- def dns_suffix_for ( region )
92
- get_partition ( region ) [ 'dnsSuffix' ]
117
+ def validate_variant! ( config_variants , resolved_variant )
118
+ unless resolved_variant [ 'hostname' ] && resolved_variant [ 'dnsSuffix' ]
119
+ enabled_variants = config_variants . select { |_k , v | v } . map { |k , _v | k . to_s } . join ( ', ' )
120
+ raise ArgumentError ,
121
+ "#{ enabled_variants } not supported for this region and partition."
122
+ end
93
123
end
94
124
95
- private
125
+ def endpoint_for ( region , service , is_global_fn , variants )
126
+ if configured_variants? ( variants )
127
+ endpoint_with_variants_for ( region , service , variants )
128
+ else
129
+ endpoint_no_variants_for ( region , service , is_global_fn )
130
+ end
131
+ end
132
+
133
+ def endpoint_with_variants_for ( region , service , variants )
134
+ variant = resolve_variant ( region , service , variants )
135
+ validate_variant! ( variants , variant )
136
+ variant [ 'hostname' ] . sub ( '{region}' , region )
137
+ . sub ( '{service}' , service )
138
+ . sub ( '{dnsSuffix}' , variant [ 'dnsSuffix' ] )
139
+ end
96
140
97
- def endpoint_for ( region , service , sts_regional_endpoints )
141
+ def endpoint_no_variants_for ( region , service , is_global_fn )
98
142
partition = get_partition ( region )
99
143
service_cfg = partition . fetch ( 'services' , { } ) . fetch ( service , { } )
100
144
@@ -105,30 +149,66 @@ def endpoint_for(region, service, sts_regional_endpoints)
105
149
106
150
endpoints = service_cfg . fetch ( 'endpoints' , { } )
107
151
108
- # Check for sts legacy behavior
109
- sts_legacy = service == 'sts' &&
110
- sts_regional_endpoints == 'legacy' &&
111
- STS_LEGACY_REGIONS . include? ( region )
112
-
113
- is_global = !endpoints . key? ( region ) &&
114
- service_cfg [ 'isRegionalized' ] == false
115
-
116
152
# Check for global endpoint.
117
- if sts_legacy || is_global
153
+ if is_global_fn . call ( service , region , endpoints , service_cfg )
118
154
region = service_cfg . fetch ( 'partitionEndpoint' , region )
119
155
end
120
156
121
157
# Check for service/region level endpoint.
122
- endpoint = endpoints
158
+ region_cfg = endpoints
123
159
. fetch ( region , { } )
160
+ endpoint = region_cfg
124
161
. fetch ( 'hostname' , default_endpoint )
125
162
163
+ warn_deprecation ( service , region ) if region_cfg [ 'deprecated' ]
164
+
126
165
# Replace placeholders from the endpoints
127
166
endpoint . sub ( '{region}' , region )
128
167
. sub ( '{service}' , service )
129
168
. sub ( '{dnsSuffix}' , partition [ 'dnsSuffix' ] )
130
169
end
131
170
171
+ def warn_deprecation ( service , region )
172
+ warn ( "The endpoint for service: #{ service } , region: #{ region } " \
173
+ ' is deprecated.' )
174
+ end
175
+
176
+ # returns a callable that takes a region
177
+ # and returns true if the service is global
178
+ def build_is_global_fn ( sts_regional_endpoints = 'regional' )
179
+ lambda do |service , region , endpoints , service_cfg |
180
+ # Check for sts legacy behavior
181
+ sts_legacy = service == 'sts' &&
182
+ sts_regional_endpoints == 'legacy' &&
183
+ STS_LEGACY_REGIONS . include? ( region )
184
+
185
+ is_global = !endpoints . key? ( region ) &&
186
+ service_cfg [ 'isRegionalized' ] == false
187
+
188
+ sts_legacy || is_global
189
+ end
190
+ end
191
+
192
+ def credential_scope ( region , service , is_global_fn )
193
+ partition = get_partition ( region )
194
+ service_cfg = partition . fetch ( 'services' , { } )
195
+ . fetch ( service , { } )
196
+ endpoints = service_cfg . fetch ( 'endpoints' , { } )
197
+
198
+ # Check for global endpoint.
199
+ if is_global_fn . call ( service , region , endpoints , service_cfg )
200
+ region = service_cfg . fetch ( 'partitionEndpoint' , region )
201
+ end
202
+
203
+ default_credential_scope = service_cfg
204
+ . fetch ( 'defaults' , { } )
205
+ . fetch ( 'credentialScope' , { } )
206
+
207
+ endpoints
208
+ . fetch ( region , { } )
209
+ . fetch ( 'credentialScope' , default_credential_scope )
210
+ end
211
+
132
212
def get_partition ( region_or_partition )
133
213
partition_containing_region ( region_or_partition ) ||
134
214
partition_matching_region ( region_or_partition ) ||
@@ -144,7 +224,7 @@ def partition_containing_region(region)
144
224
145
225
def partition_matching_region ( region )
146
226
@rules [ 'partitions' ] . find do |p |
147
- region . match ( p [ 'regionRegex' ] ) ||
227
+ p [ 'regionRegex' ] && region . match ( p [ 'regionRegex' ] ) ||
148
228
p [ 'services' ] . values . find do |svc |
149
229
svc [ 'endpoints' ] . key? ( region ) if svc . key? ( 'endpoints' )
150
230
end
@@ -161,8 +241,8 @@ def default_partition
161
241
end
162
242
163
243
class << self
164
- def resolve ( region , service , sts_regional_endpoints = 'regional' )
165
- default_provider . resolve ( region , service , sts_regional_endpoints )
244
+ def resolve ( region , service , sts_endpoint = 'regional' , variants = { } )
245
+ default_provider . resolve ( region , service , sts_endpoint , variants )
166
246
end
167
247
168
248
def signing_region ( region , service , sts_regional_endpoints = 'regional' )
@@ -173,8 +253,8 @@ def signing_service(region, service)
173
253
default_provider . signing_service ( region , service )
174
254
end
175
255
176
- def dns_suffix_for ( region )
177
- default_provider . dns_suffix_for ( region )
256
+ def dns_suffix_for ( region , service = nil , variants = { } )
257
+ default_provider . dns_suffix_for ( region , service , variants )
178
258
end
179
259
180
260
private
0 commit comments