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