src

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

endpoints.go (8011B)


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