src

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

endpoints.go (8043B)


      1 package endpoints
      2 
      3 import (
      4 	"fmt"
      5 	"github.com/aws/smithy-go/logging"
      6 	"regexp"
      7 	"strings"
      8 
      9 	"github.com/aws/aws-sdk-go-v2/aws"
     10 )
     11 
     12 // DefaultKey is a compound map key of a variant and other values.
     13 type DefaultKey struct {
     14 	Variant        EndpointVariant
     15 	ServiceVariant ServiceVariant
     16 }
     17 
     18 // EndpointKey is a compound map key of a region and associated variant value.
     19 type EndpointKey struct {
     20 	Region         string
     21 	Variant        EndpointVariant
     22 	ServiceVariant ServiceVariant
     23 }
     24 
     25 // EndpointVariant is a bit field to describe the endpoints attributes.
     26 type EndpointVariant uint64
     27 
     28 const (
     29 	// FIPSVariant indicates that the endpoint is FIPS capable.
     30 	FIPSVariant EndpointVariant = 1 << (64 - 1 - iota)
     31 
     32 	// DualStackVariant indicates that the endpoint is DualStack capable.
     33 	DualStackVariant
     34 )
     35 
     36 // ServiceVariant is a bit field to describe the service endpoint attributes.
     37 type ServiceVariant uint64
     38 
     39 const (
     40 	defaultProtocol = "https"
     41 	defaultSigner   = "v4"
     42 )
     43 
     44 var (
     45 	protocolPriority = []string{"https", "http"}
     46 	signerPriority   = []string{"v4", "s3v4"}
     47 )
     48 
     49 // Options provide configuration needed to direct how endpoints are resolved.
     50 type Options struct {
     51 	// Logger is a logging implementation that log events should be sent to.
     52 	Logger logging.Logger
     53 
     54 	// LogDeprecated indicates that deprecated endpoints should be logged to the provided logger.
     55 	LogDeprecated bool
     56 
     57 	// ResolvedRegion is the resolved region string. If provided (non-zero length) it takes priority
     58 	// over the region name passed to the ResolveEndpoint call.
     59 	ResolvedRegion string
     60 
     61 	// Disable usage of HTTPS (TLS / SSL)
     62 	DisableHTTPS bool
     63 
     64 	// Instruct the resolver to use a service endpoint that supports dual-stack.
     65 	// If a service does not have a dual-stack endpoint an error will be returned by the resolver.
     66 	UseDualStackEndpoint aws.DualStackEndpointState
     67 
     68 	// Instruct the resolver to use a service endpoint that supports FIPS.
     69 	// If a service does not have a FIPS endpoint an error will be returned by the resolver.
     70 	UseFIPSEndpoint aws.FIPSEndpointState
     71 
     72 	// ServiceVariant is a bitfield of service specified endpoint variant data.
     73 	ServiceVariant ServiceVariant
     74 }
     75 
     76 // GetEndpointVariant returns the EndpointVariant for the variant associated options.
     77 func (o Options) GetEndpointVariant() (v EndpointVariant) {
     78 	if o.UseDualStackEndpoint == aws.DualStackEndpointStateEnabled {
     79 		v |= DualStackVariant
     80 	}
     81 	if o.UseFIPSEndpoint == aws.FIPSEndpointStateEnabled {
     82 		v |= FIPSVariant
     83 	}
     84 	return v
     85 }
     86 
     87 // Partitions is a slice of partition
     88 type Partitions []Partition
     89 
     90 // ResolveEndpoint resolves a service endpoint for the given region and options.
     91 func (ps Partitions) ResolveEndpoint(region string, opts Options) (aws.Endpoint, error) {
     92 	if len(ps) == 0 {
     93 		return aws.Endpoint{}, fmt.Errorf("no partitions found")
     94 	}
     95 
     96 	if opts.Logger == nil {
     97 		opts.Logger = logging.Nop{}
     98 	}
     99 
    100 	if len(opts.ResolvedRegion) > 0 {
    101 		region = opts.ResolvedRegion
    102 	}
    103 
    104 	for i := 0; i < len(ps); i++ {
    105 		if !ps[i].canResolveEndpoint(region, opts) {
    106 			continue
    107 		}
    108 
    109 		return ps[i].ResolveEndpoint(region, opts)
    110 	}
    111 
    112 	// fallback to first partition format to use when resolving the endpoint.
    113 	return ps[0].ResolveEndpoint(region, opts)
    114 }
    115 
    116 // Partition is an AWS partition description for a service and its' region endpoints.
    117 type Partition struct {
    118 	ID                string
    119 	RegionRegex       *regexp.Regexp
    120 	PartitionEndpoint string
    121 	IsRegionalized    bool
    122 	Defaults          map[DefaultKey]Endpoint
    123 	Endpoints         Endpoints
    124 }
    125 
    126 func (p Partition) canResolveEndpoint(region string, opts Options) bool {
    127 	_, ok := p.Endpoints[EndpointKey{
    128 		Region:  region,
    129 		Variant: opts.GetEndpointVariant(),
    130 	}]
    131 	return ok || p.RegionRegex.MatchString(region)
    132 }
    133 
    134 // ResolveEndpoint resolves and service endpoint for the given region and options.
    135 func (p Partition) ResolveEndpoint(region string, options Options) (resolved aws.Endpoint, err error) {
    136 	if len(region) == 0 && len(p.PartitionEndpoint) != 0 {
    137 		region = p.PartitionEndpoint
    138 	}
    139 
    140 	endpoints := p.Endpoints
    141 
    142 	variant := options.GetEndpointVariant()
    143 	serviceVariant := options.ServiceVariant
    144 
    145 	defaults := p.Defaults[DefaultKey{
    146 		Variant:        variant,
    147 		ServiceVariant: serviceVariant,
    148 	}]
    149 
    150 	return p.endpointForRegion(region, variant, serviceVariant, endpoints).resolve(p.ID, region, defaults, options)
    151 }
    152 
    153 func (p Partition) endpointForRegion(region string, variant EndpointVariant, serviceVariant ServiceVariant, endpoints Endpoints) Endpoint {
    154 	key := EndpointKey{
    155 		Region:  region,
    156 		Variant: variant,
    157 	}
    158 
    159 	if e, ok := endpoints[key]; ok {
    160 		return e
    161 	}
    162 
    163 	if !p.IsRegionalized {
    164 		return endpoints[EndpointKey{
    165 			Region:         p.PartitionEndpoint,
    166 			Variant:        variant,
    167 			ServiceVariant: serviceVariant,
    168 		}]
    169 	}
    170 
    171 	// Unable to find any matching endpoint, return
    172 	// blank that will be used for generic endpoint creation.
    173 	return Endpoint{}
    174 }
    175 
    176 // Endpoints is a map of service config regions to endpoints
    177 type Endpoints map[EndpointKey]Endpoint
    178 
    179 // CredentialScope is the credential scope of a region and service
    180 type CredentialScope struct {
    181 	Region  string
    182 	Service string
    183 }
    184 
    185 // Endpoint is a service endpoint description
    186 type Endpoint struct {
    187 	// True if the endpoint cannot be resolved for this partition/region/service
    188 	Unresolveable aws.Ternary
    189 
    190 	Hostname  string
    191 	Protocols []string
    192 
    193 	CredentialScope CredentialScope
    194 
    195 	SignatureVersions []string
    196 
    197 	// Indicates that this endpoint is deprecated.
    198 	Deprecated aws.Ternary
    199 }
    200 
    201 // IsZero returns whether the endpoint structure is an empty (zero) value.
    202 func (e Endpoint) IsZero() bool {
    203 	switch {
    204 	case e.Unresolveable != aws.UnknownTernary:
    205 		return false
    206 	case len(e.Hostname) != 0:
    207 		return false
    208 	case len(e.Protocols) != 0:
    209 		return false
    210 	case e.CredentialScope != (CredentialScope{}):
    211 		return false
    212 	case len(e.SignatureVersions) != 0:
    213 		return false
    214 	}
    215 	return true
    216 }
    217 
    218 func (e Endpoint) resolve(partition, region string, def Endpoint, options Options) (aws.Endpoint, error) {
    219 	var merged Endpoint
    220 	merged.mergeIn(def)
    221 	merged.mergeIn(e)
    222 	e = merged
    223 
    224 	if e.IsZero() {
    225 		return aws.Endpoint{}, fmt.Errorf("unable to resolve endpoint for region: %v", region)
    226 	}
    227 
    228 	var u string
    229 	if e.Unresolveable != aws.TrueTernary {
    230 		// Only attempt to resolve the endpoint if it can be resolved.
    231 		hostname := strings.Replace(e.Hostname, "{region}", region, 1)
    232 
    233 		scheme := getEndpointScheme(e.Protocols, options.DisableHTTPS)
    234 		u = scheme + "://" + hostname
    235 	}
    236 
    237 	signingRegion := e.CredentialScope.Region
    238 	if len(signingRegion) == 0 {
    239 		signingRegion = region
    240 	}
    241 	signingName := e.CredentialScope.Service
    242 
    243 	if e.Deprecated == aws.TrueTernary && options.LogDeprecated {
    244 		options.Logger.Logf(logging.Warn, "endpoint identifier %q, url %q marked as deprecated", region, u)
    245 	}
    246 
    247 	return aws.Endpoint{
    248 		URL:           u,
    249 		PartitionID:   partition,
    250 		SigningRegion: signingRegion,
    251 		SigningName:   signingName,
    252 		SigningMethod: getByPriority(e.SignatureVersions, signerPriority, defaultSigner),
    253 	}, nil
    254 }
    255 
    256 func (e *Endpoint) mergeIn(other Endpoint) {
    257 	if other.Unresolveable != aws.UnknownTernary {
    258 		e.Unresolveable = other.Unresolveable
    259 	}
    260 	if len(other.Hostname) > 0 {
    261 		e.Hostname = other.Hostname
    262 	}
    263 	if len(other.Protocols) > 0 {
    264 		e.Protocols = other.Protocols
    265 	}
    266 	if len(other.CredentialScope.Region) > 0 {
    267 		e.CredentialScope.Region = other.CredentialScope.Region
    268 	}
    269 	if len(other.CredentialScope.Service) > 0 {
    270 		e.CredentialScope.Service = other.CredentialScope.Service
    271 	}
    272 	if len(other.SignatureVersions) > 0 {
    273 		e.SignatureVersions = other.SignatureVersions
    274 	}
    275 	if other.Deprecated != aws.UnknownTernary {
    276 		e.Deprecated = other.Deprecated
    277 	}
    278 }
    279 
    280 func getEndpointScheme(protocols []string, disableHTTPS bool) string {
    281 	if disableHTTPS {
    282 		return "http"
    283 	}
    284 
    285 	return getByPriority(protocols, protocolPriority, defaultProtocol)
    286 }
    287 
    288 func getByPriority(s []string, p []string, def string) string {
    289 	if len(s) == 0 {
    290 		return def
    291 	}
    292 
    293 	for i := 0; i < len(p); i++ {
    294 		for j := 0; j < len(s); j++ {
    295 			if s[j] == p[i] {
    296 				return s[j]
    297 			}
    298 		}
    299 	}
    300 
    301 	return s[0]
    302 }