]>
Commit | Line | Data |
---|---|---|
bae9f6d2 JC |
1 | package endpoints |
2 | ||
3 | import ( | |
4 | "fmt" | |
5 | "regexp" | |
6 | ||
7 | "github.com/aws/aws-sdk-go/aws/awserr" | |
8 | ) | |
9 | ||
10 | // Options provide the configuration needed to direct how the | |
11 | // endpoints will be resolved. | |
12 | type Options struct { | |
13 | // DisableSSL forces the endpoint to be resolved as HTTP. | |
14 | // instead of HTTPS if the service supports it. | |
15 | DisableSSL bool | |
16 | ||
17 | // Sets the resolver to resolve the endpoint as a dualstack endpoint | |
18 | // for the service. If dualstack support for a service is not known and | |
19 | // StrictMatching is not enabled a dualstack endpoint for the service will | |
20 | // be returned. This endpoint may not be valid. If StrictMatching is | |
21 | // enabled only services that are known to support dualstack will return | |
22 | // dualstack endpoints. | |
23 | UseDualStack bool | |
24 | ||
25 | // Enables strict matching of services and regions resolved endpoints. | |
26 | // If the partition doesn't enumerate the exact service and region an | |
27 | // error will be returned. This option will prevent returning endpoints | |
28 | // that look valid, but may not resolve to any real endpoint. | |
29 | StrictMatching bool | |
30 | ||
31 | // Enables resolving a service endpoint based on the region provided if the | |
32 | // service does not exist. The service endpoint ID will be used as the service | |
33 | // domain name prefix. By default the endpoint resolver requires the service | |
34 | // to be known when resolving endpoints. | |
35 | // | |
36 | // If resolving an endpoint on the partition list the provided region will | |
37 | // be used to determine which partition's domain name pattern to the service | |
107c1cdb | 38 | // endpoint ID with. If both the service and region are unknown and resolving |
bae9f6d2 JC |
39 | // the endpoint on partition list an UnknownEndpointError error will be returned. |
40 | // | |
41 | // If resolving and endpoint on a partition specific resolver that partition's | |
42 | // domain name pattern will be used with the service endpoint ID. If both | |
43 | // region and service do not exist when resolving an endpoint on a specific | |
44 | // partition the partition's domain pattern will be used to combine the | |
45 | // endpoint and region together. | |
46 | // | |
47 | // This option is ignored if StrictMatching is enabled. | |
48 | ResolveUnknownService bool | |
49 | } | |
50 | ||
51 | // Set combines all of the option functions together. | |
52 | func (o *Options) Set(optFns ...func(*Options)) { | |
53 | for _, fn := range optFns { | |
54 | fn(o) | |
55 | } | |
56 | } | |
57 | ||
58 | // DisableSSLOption sets the DisableSSL options. Can be used as a functional | |
59 | // option when resolving endpoints. | |
60 | func DisableSSLOption(o *Options) { | |
61 | o.DisableSSL = true | |
62 | } | |
63 | ||
64 | // UseDualStackOption sets the UseDualStack option. Can be used as a functional | |
65 | // option when resolving endpoints. | |
66 | func UseDualStackOption(o *Options) { | |
67 | o.UseDualStack = true | |
68 | } | |
69 | ||
70 | // StrictMatchingOption sets the StrictMatching option. Can be used as a functional | |
71 | // option when resolving endpoints. | |
72 | func StrictMatchingOption(o *Options) { | |
73 | o.StrictMatching = true | |
74 | } | |
75 | ||
76 | // ResolveUnknownServiceOption sets the ResolveUnknownService option. Can be used | |
77 | // as a functional option when resolving endpoints. | |
78 | func ResolveUnknownServiceOption(o *Options) { | |
79 | o.ResolveUnknownService = true | |
80 | } | |
81 | ||
82 | // A Resolver provides the interface for functionality to resolve endpoints. | |
83 | // The build in Partition and DefaultResolver return value satisfy this interface. | |
84 | type Resolver interface { | |
85 | EndpointFor(service, region string, opts ...func(*Options)) (ResolvedEndpoint, error) | |
86 | } | |
87 | ||
88 | // ResolverFunc is a helper utility that wraps a function so it satisfies the | |
89 | // Resolver interface. This is useful when you want to add additional endpoint | |
90 | // resolving logic, or stub out specific endpoints with custom values. | |
91 | type ResolverFunc func(service, region string, opts ...func(*Options)) (ResolvedEndpoint, error) | |
92 | ||
93 | // EndpointFor wraps the ResolverFunc function to satisfy the Resolver interface. | |
94 | func (fn ResolverFunc) EndpointFor(service, region string, opts ...func(*Options)) (ResolvedEndpoint, error) { | |
95 | return fn(service, region, opts...) | |
96 | } | |
97 | ||
98 | var schemeRE = regexp.MustCompile("^([^:]+)://") | |
99 | ||
100 | // AddScheme adds the HTTP or HTTPS schemes to a endpoint URL if there is no | |
101 | // scheme. If disableSSL is true HTTP will set HTTP instead of the default HTTPS. | |
102 | // | |
103 | // If disableSSL is set, it will only set the URL's scheme if the URL does not | |
104 | // contain a scheme. | |
105 | func AddScheme(endpoint string, disableSSL bool) string { | |
106 | if !schemeRE.MatchString(endpoint) { | |
107 | scheme := "https" | |
108 | if disableSSL { | |
109 | scheme = "http" | |
110 | } | |
111 | endpoint = fmt.Sprintf("%s://%s", scheme, endpoint) | |
112 | } | |
113 | ||
114 | return endpoint | |
115 | } | |
116 | ||
117 | // EnumPartitions a provides a way to retrieve the underlying partitions that | |
118 | // make up the SDK's default Resolver, or any resolver decoded from a model | |
119 | // file. | |
120 | // | |
121 | // Use this interface with DefaultResolver and DecodeModels to get the list of | |
122 | // Partitions. | |
123 | type EnumPartitions interface { | |
124 | Partitions() []Partition | |
125 | } | |
126 | ||
127 | // RegionsForService returns a map of regions for the partition and service. | |
128 | // If either the partition or service does not exist false will be returned | |
129 | // as the second parameter. | |
130 | // | |
131 | // This example shows how to get the regions for DynamoDB in the AWS partition. | |
132 | // rs, exists := endpoints.RegionsForService(endpoints.DefaultPartitions(), endpoints.AwsPartitionID, endpoints.DynamodbServiceID) | |
133 | // | |
134 | // This is equivalent to using the partition directly. | |
135 | // rs := endpoints.AwsPartition().Services()[endpoints.DynamodbServiceID].Regions() | |
136 | func RegionsForService(ps []Partition, partitionID, serviceID string) (map[string]Region, bool) { | |
137 | for _, p := range ps { | |
138 | if p.ID() != partitionID { | |
139 | continue | |
140 | } | |
141 | if _, ok := p.p.Services[serviceID]; !ok { | |
142 | break | |
143 | } | |
144 | ||
145 | s := Service{ | |
146 | id: serviceID, | |
147 | p: p.p, | |
148 | } | |
149 | return s.Regions(), true | |
150 | } | |
151 | ||
152 | return map[string]Region{}, false | |
153 | } | |
154 | ||
155 | // PartitionForRegion returns the first partition which includes the region | |
156 | // passed in. This includes both known regions and regions which match | |
157 | // a pattern supported by the partition which may include regions that are | |
158 | // not explicitly known by the partition. Use the Regions method of the | |
159 | // returned Partition if explicit support is needed. | |
160 | func PartitionForRegion(ps []Partition, regionID string) (Partition, bool) { | |
161 | for _, p := range ps { | |
162 | if _, ok := p.p.Regions[regionID]; ok || p.p.RegionRegex.MatchString(regionID) { | |
163 | return p, true | |
164 | } | |
165 | } | |
166 | ||
167 | return Partition{}, false | |
168 | } | |
169 | ||
170 | // A Partition provides the ability to enumerate the partition's regions | |
171 | // and services. | |
172 | type Partition struct { | |
173 | id string | |
174 | p *partition | |
175 | } | |
176 | ||
177 | // ID returns the identifier of the partition. | |
178 | func (p Partition) ID() string { return p.id } | |
179 | ||
180 | // EndpointFor attempts to resolve the endpoint based on service and region. | |
181 | // See Options for information on configuring how the endpoint is resolved. | |
182 | // | |
183 | // If the service cannot be found in the metadata the UnknownServiceError | |
184 | // error will be returned. This validation will occur regardless if | |
185 | // StrictMatching is enabled. To enable resolving unknown services set the | |
186 | // "ResolveUnknownService" option to true. When StrictMatching is disabled | |
187 | // this option allows the partition resolver to resolve a endpoint based on | |
188 | // the service endpoint ID provided. | |
189 | // | |
190 | // When resolving endpoints you can choose to enable StrictMatching. This will | |
191 | // require the provided service and region to be known by the partition. | |
192 | // If the endpoint cannot be strictly resolved an error will be returned. This | |
193 | // mode is useful to ensure the endpoint resolved is valid. Without | |
194 | // StrictMatching enabled the endpoint returned my look valid but may not work. | |
195 | // StrictMatching requires the SDK to be updated if you want to take advantage | |
196 | // of new regions and services expansions. | |
197 | // | |
198 | // Errors that can be returned. | |
199 | // * UnknownServiceError | |
200 | // * UnknownEndpointError | |
201 | func (p Partition) EndpointFor(service, region string, opts ...func(*Options)) (ResolvedEndpoint, error) { | |
202 | return p.p.EndpointFor(service, region, opts...) | |
203 | } | |
204 | ||
205 | // Regions returns a map of Regions indexed by their ID. This is useful for | |
206 | // enumerating over the regions in a partition. | |
207 | func (p Partition) Regions() map[string]Region { | |
208 | rs := map[string]Region{} | |
15c0b25d | 209 | for id, r := range p.p.Regions { |
bae9f6d2 | 210 | rs[id] = Region{ |
15c0b25d AP |
211 | id: id, |
212 | desc: r.Description, | |
213 | p: p.p, | |
bae9f6d2 JC |
214 | } |
215 | } | |
216 | ||
217 | return rs | |
218 | } | |
219 | ||
220 | // Services returns a map of Service indexed by their ID. This is useful for | |
221 | // enumerating over the services in a partition. | |
222 | func (p Partition) Services() map[string]Service { | |
223 | ss := map[string]Service{} | |
224 | for id := range p.p.Services { | |
225 | ss[id] = Service{ | |
226 | id: id, | |
227 | p: p.p, | |
228 | } | |
229 | } | |
230 | ||
231 | return ss | |
232 | } | |
233 | ||
234 | // A Region provides information about a region, and ability to resolve an | |
235 | // endpoint from the context of a region, given a service. | |
236 | type Region struct { | |
237 | id, desc string | |
238 | p *partition | |
239 | } | |
240 | ||
241 | // ID returns the region's identifier. | |
242 | func (r Region) ID() string { return r.id } | |
243 | ||
15c0b25d AP |
244 | // Description returns the region's description. The region description |
245 | // is free text, it can be empty, and it may change between SDK releases. | |
246 | func (r Region) Description() string { return r.desc } | |
247 | ||
bae9f6d2 JC |
248 | // ResolveEndpoint resolves an endpoint from the context of the region given |
249 | // a service. See Partition.EndpointFor for usage and errors that can be returned. | |
250 | func (r Region) ResolveEndpoint(service string, opts ...func(*Options)) (ResolvedEndpoint, error) { | |
251 | return r.p.EndpointFor(service, r.id, opts...) | |
252 | } | |
253 | ||
254 | // Services returns a list of all services that are known to be in this region. | |
255 | func (r Region) Services() map[string]Service { | |
256 | ss := map[string]Service{} | |
257 | for id, s := range r.p.Services { | |
258 | if _, ok := s.Endpoints[r.id]; ok { | |
259 | ss[id] = Service{ | |
260 | id: id, | |
261 | p: r.p, | |
262 | } | |
263 | } | |
264 | } | |
265 | ||
266 | return ss | |
267 | } | |
268 | ||
269 | // A Service provides information about a service, and ability to resolve an | |
270 | // endpoint from the context of a service, given a region. | |
271 | type Service struct { | |
272 | id string | |
273 | p *partition | |
274 | } | |
275 | ||
276 | // ID returns the identifier for the service. | |
277 | func (s Service) ID() string { return s.id } | |
278 | ||
279 | // ResolveEndpoint resolves an endpoint from the context of a service given | |
280 | // a region. See Partition.EndpointFor for usage and errors that can be returned. | |
281 | func (s Service) ResolveEndpoint(region string, opts ...func(*Options)) (ResolvedEndpoint, error) { | |
282 | return s.p.EndpointFor(s.id, region, opts...) | |
283 | } | |
284 | ||
285 | // Regions returns a map of Regions that the service is present in. | |
286 | // | |
287 | // A region is the AWS region the service exists in. Whereas a Endpoint is | |
288 | // an URL that can be resolved to a instance of a service. | |
289 | func (s Service) Regions() map[string]Region { | |
290 | rs := map[string]Region{} | |
291 | for id := range s.p.Services[s.id].Endpoints { | |
15c0b25d | 292 | if r, ok := s.p.Regions[id]; ok { |
bae9f6d2 | 293 | rs[id] = Region{ |
15c0b25d AP |
294 | id: id, |
295 | desc: r.Description, | |
296 | p: s.p, | |
bae9f6d2 JC |
297 | } |
298 | } | |
299 | } | |
300 | ||
301 | return rs | |
302 | } | |
303 | ||
304 | // Endpoints returns a map of Endpoints indexed by their ID for all known | |
305 | // endpoints for a service. | |
306 | // | |
307 | // A region is the AWS region the service exists in. Whereas a Endpoint is | |
308 | // an URL that can be resolved to a instance of a service. | |
309 | func (s Service) Endpoints() map[string]Endpoint { | |
310 | es := map[string]Endpoint{} | |
311 | for id := range s.p.Services[s.id].Endpoints { | |
312 | es[id] = Endpoint{ | |
313 | id: id, | |
314 | serviceID: s.id, | |
315 | p: s.p, | |
316 | } | |
317 | } | |
318 | ||
319 | return es | |
320 | } | |
321 | ||
322 | // A Endpoint provides information about endpoints, and provides the ability | |
323 | // to resolve that endpoint for the service, and the region the endpoint | |
324 | // represents. | |
325 | type Endpoint struct { | |
326 | id string | |
327 | serviceID string | |
328 | p *partition | |
329 | } | |
330 | ||
331 | // ID returns the identifier for an endpoint. | |
332 | func (e Endpoint) ID() string { return e.id } | |
333 | ||
334 | // ServiceID returns the identifier the endpoint belongs to. | |
335 | func (e Endpoint) ServiceID() string { return e.serviceID } | |
336 | ||
337 | // ResolveEndpoint resolves an endpoint from the context of a service and | |
338 | // region the endpoint represents. See Partition.EndpointFor for usage and | |
339 | // errors that can be returned. | |
340 | func (e Endpoint) ResolveEndpoint(opts ...func(*Options)) (ResolvedEndpoint, error) { | |
341 | return e.p.EndpointFor(e.serviceID, e.id, opts...) | |
342 | } | |
343 | ||
344 | // A ResolvedEndpoint is an endpoint that has been resolved based on a partition | |
345 | // service, and region. | |
346 | type ResolvedEndpoint struct { | |
347 | // The endpoint URL | |
348 | URL string | |
349 | ||
350 | // The region that should be used for signing requests. | |
351 | SigningRegion string | |
352 | ||
353 | // The service name that should be used for signing requests. | |
354 | SigningName string | |
355 | ||
15c0b25d AP |
356 | // States that the signing name for this endpoint was derived from metadata |
357 | // passed in, but was not explicitly modeled. | |
358 | SigningNameDerived bool | |
359 | ||
bae9f6d2 JC |
360 | // The signing method that should be used for signing requests. |
361 | SigningMethod string | |
362 | } | |
363 | ||
364 | // So that the Error interface type can be included as an anonymous field | |
365 | // in the requestError struct and not conflict with the error.Error() method. | |
366 | type awsError awserr.Error | |
367 | ||
368 | // A EndpointNotFoundError is returned when in StrictMatching mode, and the | |
369 | // endpoint for the service and region cannot be found in any of the partitions. | |
370 | type EndpointNotFoundError struct { | |
371 | awsError | |
372 | Partition string | |
373 | Service string | |
374 | Region string | |
375 | } | |
376 | ||
377 | // A UnknownServiceError is returned when the service does not resolve to an | |
378 | // endpoint. Includes a list of all known services for the partition. Returned | |
379 | // when a partition does not support the service. | |
380 | type UnknownServiceError struct { | |
381 | awsError | |
382 | Partition string | |
383 | Service string | |
384 | Known []string | |
385 | } | |
386 | ||
387 | // NewUnknownServiceError builds and returns UnknownServiceError. | |
388 | func NewUnknownServiceError(p, s string, known []string) UnknownServiceError { | |
389 | return UnknownServiceError{ | |
390 | awsError: awserr.New("UnknownServiceError", | |
391 | "could not resolve endpoint for unknown service", nil), | |
392 | Partition: p, | |
393 | Service: s, | |
394 | Known: known, | |
395 | } | |
396 | } | |
397 | ||
398 | // String returns the string representation of the error. | |
399 | func (e UnknownServiceError) Error() string { | |
400 | extra := fmt.Sprintf("partition: %q, service: %q", | |
401 | e.Partition, e.Service) | |
402 | if len(e.Known) > 0 { | |
403 | extra += fmt.Sprintf(", known: %v", e.Known) | |
404 | } | |
405 | return awserr.SprintError(e.Code(), e.Message(), extra, e.OrigErr()) | |
406 | } | |
407 | ||
408 | // String returns the string representation of the error. | |
409 | func (e UnknownServiceError) String() string { | |
410 | return e.Error() | |
411 | } | |
412 | ||
413 | // A UnknownEndpointError is returned when in StrictMatching mode and the | |
414 | // service is valid, but the region does not resolve to an endpoint. Includes | |
415 | // a list of all known endpoints for the service. | |
416 | type UnknownEndpointError struct { | |
417 | awsError | |
418 | Partition string | |
419 | Service string | |
420 | Region string | |
421 | Known []string | |
422 | } | |
423 | ||
424 | // NewUnknownEndpointError builds and returns UnknownEndpointError. | |
425 | func NewUnknownEndpointError(p, s, r string, known []string) UnknownEndpointError { | |
426 | return UnknownEndpointError{ | |
427 | awsError: awserr.New("UnknownEndpointError", | |
428 | "could not resolve endpoint", nil), | |
429 | Partition: p, | |
430 | Service: s, | |
431 | Region: r, | |
432 | Known: known, | |
433 | } | |
434 | } | |
435 | ||
436 | // String returns the string representation of the error. | |
437 | func (e UnknownEndpointError) Error() string { | |
438 | extra := fmt.Sprintf("partition: %q, service: %q, region: %q", | |
439 | e.Partition, e.Service, e.Region) | |
440 | if len(e.Known) > 0 { | |
441 | extra += fmt.Sprintf(", known: %v", e.Known) | |
442 | } | |
443 | return awserr.SprintError(e.Code(), e.Message(), extra, e.OrigErr()) | |
444 | } | |
445 | ||
446 | // String returns the string representation of the error. | |
447 | func (e UnknownEndpointError) String() string { | |
448 | return e.Error() | |
449 | } |