1package endpoints
2
3import (
4	"fmt"
5	"regexp"
6	"strings"
7
8	"github.com/aws/aws-sdk-go-v2/aws"
9)
10
11const (
12	defaultProtocol = "https"
13	defaultSigner   = "v4"
14)
15
16var (
17	protocolPriority = []string{"https", "http"}
18	signerPriority   = []string{"v4"}
19)
20
21// Options provide configuration needed to direct how endpoints are resolved.
22type Options struct {
23	// Disable usage of HTTPS (TLS / SSL)
24	DisableHTTPS bool
25}
26
27// Partitions is a slice of partition
28type Partitions []Partition
29
30// ResolveEndpoint resolves a service endpoint for the given region and options.
31func (ps Partitions) ResolveEndpoint(region string, opts Options) (aws.Endpoint, error) {
32	if len(ps) == 0 {
33		return aws.Endpoint{}, fmt.Errorf("no partitions found")
34	}
35
36	for i := 0; i < len(ps); i++ {
37		if !ps[i].canResolveEndpoint(region) {
38			continue
39		}
40
41		return ps[i].ResolveEndpoint(region, opts)
42	}
43
44	// fallback to first partition format to use when resolving the endpoint.
45	return ps[0].ResolveEndpoint(region, opts)
46}
47
48// Partition is an AWS partition description for a service and its' region endpoints.
49type Partition struct {
50	ID                string
51	RegionRegex       *regexp.Regexp
52	PartitionEndpoint string
53	IsRegionalized    bool
54	Defaults          Endpoint
55	Endpoints         Endpoints
56}
57
58func (p Partition) canResolveEndpoint(region string) bool {
59	_, ok := p.Endpoints[region]
60	return ok || p.RegionRegex.MatchString(region)
61}
62
63// ResolveEndpoint resolves and service endpoint for the given region and options.
64func (p Partition) ResolveEndpoint(region string, options Options) (resolved aws.Endpoint, err error) {
65	if len(region) == 0 && len(p.PartitionEndpoint) != 0 {
66		region = p.PartitionEndpoint
67	}
68
69	e, _ := p.endpointForRegion(region)
70
71	return e.resolve(p.ID, region, p.Defaults, options), nil
72}
73
74func (p Partition) endpointForRegion(region string) (Endpoint, bool) {
75	if e, ok := p.Endpoints[region]; ok {
76		return e, true
77	}
78
79	if !p.IsRegionalized {
80		return p.Endpoints[p.PartitionEndpoint], region == p.PartitionEndpoint
81	}
82
83	// Unable to find any matching endpoint, return
84	// blank that will be used for generic endpoint creation.
85	return Endpoint{}, false
86}
87
88// Endpoints is a map of service config regions to endpoints
89type Endpoints map[string]Endpoint
90
91// CredentialScope is the credential scope of a region and service
92type CredentialScope struct {
93	Region  string
94	Service string
95}
96
97// Endpoint is a service endpoint description
98type Endpoint struct {
99	// True if the endpoint cannot be resolved for this partition/region/service
100	Unresolveable aws.Ternary
101
102	Hostname  string
103	Protocols []string
104
105	CredentialScope CredentialScope
106
107	SignatureVersions []string `json:"signatureVersions"`
108}
109
110func (e Endpoint) resolve(partition, region string, def Endpoint, options Options) aws.Endpoint {
111	var merged Endpoint
112	merged.mergeIn(def)
113	merged.mergeIn(e)
114	e = merged
115
116	var u string
117	if e.Unresolveable != aws.TrueTernary {
118		// Only attempt to resolve the endpoint if it can be resolved.
119		hostname := strings.Replace(e.Hostname, "{region}", region, 1)
120
121		scheme := getEndpointScheme(e.Protocols, options.DisableHTTPS)
122		u = scheme + "://" + hostname
123	}
124
125	signingRegion := e.CredentialScope.Region
126	if len(signingRegion) == 0 {
127		signingRegion = region
128	}
129	signingName := e.CredentialScope.Service
130
131	return aws.Endpoint{
132		URL:           u,
133		PartitionID:   partition,
134		SigningRegion: signingRegion,
135		SigningName:   signingName,
136		SigningMethod: getByPriority(e.SignatureVersions, signerPriority, defaultSigner),
137	}
138}
139
140func (e *Endpoint) mergeIn(other Endpoint) {
141	if other.Unresolveable != aws.UnknownTernary {
142		e.Unresolveable = other.Unresolveable
143	}
144	if len(other.Hostname) > 0 {
145		e.Hostname = other.Hostname
146	}
147	if len(other.Protocols) > 0 {
148		e.Protocols = other.Protocols
149	}
150	if len(other.CredentialScope.Region) > 0 {
151		e.CredentialScope.Region = other.CredentialScope.Region
152	}
153	if len(other.CredentialScope.Service) > 0 {
154		e.CredentialScope.Service = other.CredentialScope.Service
155	}
156	if len(other.SignatureVersions) > 0 {
157		e.SignatureVersions = other.SignatureVersions
158	}
159}
160
161func getEndpointScheme(protocols []string, disableHTTPS bool) string {
162	if disableHTTPS {
163		return "http"
164	}
165
166	return getByPriority(protocols, protocolPriority, defaultProtocol)
167}
168
169func getByPriority(s []string, p []string, def string) string {
170	if len(s) == 0 {
171		return def
172	}
173
174	for i := 0; i < len(p); i++ {
175		for j := 0; j < len(s); j++ {
176			if s[j] == p[i] {
177				return s[j]
178			}
179		}
180	}
181
182	return s[0]
183}
184