src

Go monorepo.
git clone git://code.dwrz.net/src
Log | Files | Refs

endpoints.go (5041B)


      1 package endpoints
      2 
      3 import (
      4 	"fmt"
      5 	"regexp"
      6 	"strings"
      7 
      8 	"github.com/aws/aws-sdk-go-v2/aws"
      9 )
     10 
     11 const (
     12 	defaultProtocol = "https"
     13 	defaultSigner   = "v4"
     14 )
     15 
     16 var (
     17 	protocolPriority = []string{"https", "http"}
     18 	signerPriority   = []string{"v4"}
     19 )
     20 
     21 // Options provide configuration needed to direct how endpoints are resolved.
     22 type Options struct {
     23 	// Disable usage of HTTPS (TLS / SSL)
     24 	DisableHTTPS bool
     25 }
     26 
     27 // Partitions is a slice of partition
     28 type Partitions []Partition
     29 
     30 // ResolveEndpoint resolves a service endpoint for the given region and options.
     31 func (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.
     49 type Partition struct {
     50 	ID                string
     51 	RegionRegex       *regexp.Regexp
     52 	PartitionEndpoint string
     53 	IsRegionalized    bool
     54 	Defaults          Endpoint
     55 	Endpoints         Endpoints
     56 }
     57 
     58 func (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.
     64 func (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 
     74 func (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
     89 type Endpoints map[string]Endpoint
     90 
     91 // CredentialScope is the credential scope of a region and service
     92 type CredentialScope struct {
     93 	Region  string
     94 	Service string
     95 }
     96 
     97 // Endpoint is a service endpoint description
     98 type 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 
    110 func (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 
    140 func (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 
    161 func getEndpointScheme(protocols []string, disableHTTPS bool) string {
    162 	if disableHTTPS {
    163 		return "http"
    164 	}
    165 
    166 	return getByPriority(protocols, protocolPriority, defaultProtocol)
    167 }
    168 
    169 func 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 
    185 // MapFIPSRegion extracts the intrinsic AWS region from one that may have an
    186 // embedded FIPS microformat.
    187 func MapFIPSRegion(region string) string {
    188 	const fipsInfix = "-fips-"
    189 	const fipsPrefix = "fips-"
    190 	const fipsSuffix = "-fips"
    191 
    192 	if strings.Contains(region, fipsInfix) ||
    193 		strings.Contains(region, fipsPrefix) ||
    194 		strings.Contains(region, fipsSuffix) {
    195 		region = strings.ReplaceAll(region, fipsInfix, "-")
    196 		region = strings.ReplaceAll(region, fipsPrefix, "")
    197 		region = strings.ReplaceAll(region, fipsSuffix, "")
    198 	}
    199 
    200 	return region
    201 }