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