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