1 package endpoints
2
3 import (
4 "fmt"
5 "github.com/aws/smithy-go/logging"
6 "regexp"
7 "strings"
8
9 "github.com/aws/aws-sdk-go-v2/aws"
10 )
11
12
13 type DefaultKey struct {
14 Variant EndpointVariant
15 ServiceVariant ServiceVariant
16 }
17
18
19 type EndpointKey struct {
20 Region string
21 Variant EndpointVariant
22 ServiceVariant ServiceVariant
23 }
24
25
26 type EndpointVariant uint64
27
28 const (
29
30 FIPSVariant EndpointVariant = 1 << (64 - 1 - iota)
31
32
33 DualStackVariant
34 )
35
36
37 type ServiceVariant uint64
38
39 const (
40 defaultProtocol = "https"
41 defaultSigner = "v4"
42 )
43
44 var (
45 protocolPriority = []string{"https", "http"}
46 signerPriority = []string{"v4", "s3v4"}
47 )
48
49
50 type Options struct {
51
52 Logger logging.Logger
53
54
55 LogDeprecated bool
56
57
58
59 ResolvedRegion string
60
61
62 DisableHTTPS bool
63
64
65
66 UseDualStackEndpoint aws.DualStackEndpointState
67
68
69
70 UseFIPSEndpoint aws.FIPSEndpointState
71
72
73 ServiceVariant ServiceVariant
74 }
75
76
77 func (o Options) GetEndpointVariant() (v EndpointVariant) {
78 if o.UseDualStackEndpoint == aws.DualStackEndpointStateEnabled {
79 v |= DualStackVariant
80 }
81 if o.UseFIPSEndpoint == aws.FIPSEndpointStateEnabled {
82 v |= FIPSVariant
83 }
84 return v
85 }
86
87
88 type Partitions []Partition
89
90
91 func (ps Partitions) ResolveEndpoint(region string, opts Options) (aws.Endpoint, error) {
92 if len(ps) == 0 {
93 return aws.Endpoint{}, fmt.Errorf("no partitions found")
94 }
95
96 if opts.Logger == nil {
97 opts.Logger = logging.Nop{}
98 }
99
100 if len(opts.ResolvedRegion) > 0 {
101 region = opts.ResolvedRegion
102 }
103
104 for i := 0; i < len(ps); i++ {
105 if !ps[i].canResolveEndpoint(region, opts) {
106 continue
107 }
108
109 return ps[i].ResolveEndpoint(region, opts)
110 }
111
112
113 return ps[0].ResolveEndpoint(region, opts)
114 }
115
116
117 type Partition struct {
118 ID string
119 RegionRegex *regexp.Regexp
120 PartitionEndpoint string
121 IsRegionalized bool
122 Defaults map[DefaultKey]Endpoint
123 Endpoints Endpoints
124 }
125
126 func (p Partition) canResolveEndpoint(region string, opts Options) bool {
127 _, ok := p.Endpoints[EndpointKey{
128 Region: region,
129 Variant: opts.GetEndpointVariant(),
130 }]
131 return ok || p.RegionRegex.MatchString(region)
132 }
133
134
135 func (p Partition) ResolveEndpoint(region string, options Options) (resolved aws.Endpoint, err error) {
136 if len(region) == 0 && len(p.PartitionEndpoint) != 0 {
137 region = p.PartitionEndpoint
138 }
139
140 endpoints := p.Endpoints
141
142 variant := options.GetEndpointVariant()
143 serviceVariant := options.ServiceVariant
144
145 defaults := p.Defaults[DefaultKey{
146 Variant: variant,
147 ServiceVariant: serviceVariant,
148 }]
149
150 return p.endpointForRegion(region, variant, serviceVariant, endpoints).resolve(p.ID, region, defaults, options)
151 }
152
153 func (p Partition) endpointForRegion(region string, variant EndpointVariant, serviceVariant ServiceVariant, endpoints Endpoints) Endpoint {
154 key := EndpointKey{
155 Region: region,
156 Variant: variant,
157 }
158
159 if e, ok := endpoints[key]; ok {
160 return e
161 }
162
163 if !p.IsRegionalized {
164 return endpoints[EndpointKey{
165 Region: p.PartitionEndpoint,
166 Variant: variant,
167 ServiceVariant: serviceVariant,
168 }]
169 }
170
171
172
173 return Endpoint{}
174 }
175
176
177 type Endpoints map[EndpointKey]Endpoint
178
179
180 type CredentialScope struct {
181 Region string
182 Service string
183 }
184
185
186 type Endpoint struct {
187
188 Unresolveable aws.Ternary
189
190 Hostname string
191 Protocols []string
192
193 CredentialScope CredentialScope
194
195 SignatureVersions []string
196
197
198 Deprecated aws.Ternary
199 }
200
201
202 func (e Endpoint) IsZero() bool {
203 switch {
204 case e.Unresolveable != aws.UnknownTernary:
205 return false
206 case len(e.Hostname) != 0:
207 return false
208 case len(e.Protocols) != 0:
209 return false
210 case e.CredentialScope != (CredentialScope{}):
211 return false
212 case len(e.SignatureVersions) != 0:
213 return false
214 }
215 return true
216 }
217
218 func (e Endpoint) resolve(partition, region string, def Endpoint, options Options) (aws.Endpoint, error) {
219 var merged Endpoint
220 merged.mergeIn(def)
221 merged.mergeIn(e)
222 e = merged
223
224 if e.IsZero() {
225 return aws.Endpoint{}, fmt.Errorf("unable to resolve endpoint for region: %v", region)
226 }
227
228 var u string
229 if e.Unresolveable != aws.TrueTernary {
230
231 hostname := strings.Replace(e.Hostname, "{region}", region, 1)
232
233 scheme := getEndpointScheme(e.Protocols, options.DisableHTTPS)
234 u = scheme + "://" + hostname
235 }
236
237 signingRegion := e.CredentialScope.Region
238 if len(signingRegion) == 0 {
239 signingRegion = region
240 }
241 signingName := e.CredentialScope.Service
242
243 if e.Deprecated == aws.TrueTernary && options.LogDeprecated {
244 options.Logger.Logf(logging.Warn, "endpoint identifier %q, url %q marked as deprecated", region, u)
245 }
246
247 return aws.Endpoint{
248 URL: u,
249 PartitionID: partition,
250 SigningRegion: signingRegion,
251 SigningName: signingName,
252 SigningMethod: getByPriority(e.SignatureVersions, signerPriority, defaultSigner),
253 }, nil
254 }
255
256 func (e *Endpoint) mergeIn(other Endpoint) {
257 if other.Unresolveable != aws.UnknownTernary {
258 e.Unresolveable = other.Unresolveable
259 }
260 if len(other.Hostname) > 0 {
261 e.Hostname = other.Hostname
262 }
263 if len(other.Protocols) > 0 {
264 e.Protocols = other.Protocols
265 }
266 if len(other.CredentialScope.Region) > 0 {
267 e.CredentialScope.Region = other.CredentialScope.Region
268 }
269 if len(other.CredentialScope.Service) > 0 {
270 e.CredentialScope.Service = other.CredentialScope.Service
271 }
272 if len(other.SignatureVersions) > 0 {
273 e.SignatureVersions = other.SignatureVersions
274 }
275 if other.Deprecated != aws.UnknownTernary {
276 e.Deprecated = other.Deprecated
277 }
278 }
279
280 func getEndpointScheme(protocols []string, disableHTTPS bool) string {
281 if disableHTTPS {
282 return "http"
283 }
284
285 return getByPriority(protocols, protocolPriority, defaultProtocol)
286 }
287
288 func getByPriority(s []string, p []string, def string) string {
289 if len(s) == 0 {
290 return def
291 }
292
293 for i := 0; i < len(p); i++ {
294 for j := 0; j < len(s); j++ {
295 if s[j] == p[i] {
296 return s[j]
297 }
298 }
299 }
300
301 return s[0]
302 }
303
View as plain text