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