| package endpoints |
| |
| import ( |
| "fmt" |
| "regexp" |
| "strconv" |
| "strings" |
| ) |
| |
| type partitions []partition |
| |
| func (ps partitions) EndpointFor(service, region string, opts ...func(*Options)) (ResolvedEndpoint, error) { |
| var opt Options |
| opt.Set(opts...) |
| |
| for i := 0; i < len(ps); i++ { |
| if !ps[i].canResolveEndpoint(service, region, opt.StrictMatching) { |
| continue |
| } |
| |
| return ps[i].EndpointFor(service, region, opts...) |
| } |
| |
| // If loose matching fallback to first partition format to use |
| // when resolving the endpoint. |
| if !opt.StrictMatching && len(ps) > 0 { |
| return ps[0].EndpointFor(service, region, opts...) |
| } |
| |
| return ResolvedEndpoint{}, NewUnknownEndpointError("all partitions", service, region, []string{}) |
| } |
| |
| // Partitions satisfies the EnumPartitions interface and returns a list |
| // of Partitions representing each partition represented in the SDK's |
| // endpoints model. |
| func (ps partitions) Partitions() []Partition { |
| parts := make([]Partition, 0, len(ps)) |
| for i := 0; i < len(ps); i++ { |
| parts = append(parts, ps[i].Partition()) |
| } |
| |
| return parts |
| } |
| |
| type partition struct { |
| ID string `json:"partition"` |
| Name string `json:"partitionName"` |
| DNSSuffix string `json:"dnsSuffix"` |
| RegionRegex regionRegex `json:"regionRegex"` |
| Defaults endpoint `json:"defaults"` |
| Regions regions `json:"regions"` |
| Services services `json:"services"` |
| } |
| |
| func (p partition) Partition() Partition { |
| return Partition{ |
| id: p.ID, |
| p: &p, |
| } |
| } |
| |
| func (p partition) canResolveEndpoint(service, region string, strictMatch bool) bool { |
| s, hasService := p.Services[service] |
| _, hasEndpoint := s.Endpoints[region] |
| |
| if hasEndpoint && hasService { |
| return true |
| } |
| |
| if strictMatch { |
| return false |
| } |
| |
| return p.RegionRegex.MatchString(region) |
| } |
| |
| func (p partition) EndpointFor(service, region string, opts ...func(*Options)) (resolved ResolvedEndpoint, err error) { |
| var opt Options |
| opt.Set(opts...) |
| |
| s, hasService := p.Services[service] |
| if !(hasService || opt.ResolveUnknownService) { |
| // Only return error if the resolver will not fallback to creating |
| // endpoint based on service endpoint ID passed in. |
| return resolved, NewUnknownServiceError(p.ID, service, serviceList(p.Services)) |
| } |
| |
| e, hasEndpoint := s.endpointForRegion(region) |
| if !hasEndpoint && opt.StrictMatching { |
| return resolved, NewUnknownEndpointError(p.ID, service, region, endpointList(s.Endpoints)) |
| } |
| |
| defs := []endpoint{p.Defaults, s.Defaults} |
| return e.resolve(service, region, p.DNSSuffix, defs, opt), nil |
| } |
| |
| func serviceList(ss services) []string { |
| list := make([]string, 0, len(ss)) |
| for k := range ss { |
| list = append(list, k) |
| } |
| return list |
| } |
| func endpointList(es endpoints) []string { |
| list := make([]string, 0, len(es)) |
| for k := range es { |
| list = append(list, k) |
| } |
| return list |
| } |
| |
| type regionRegex struct { |
| *regexp.Regexp |
| } |
| |
| func (rr *regionRegex) UnmarshalJSON(b []byte) (err error) { |
| // Strip leading and trailing quotes |
| regex, err := strconv.Unquote(string(b)) |
| if err != nil { |
| return fmt.Errorf("unable to strip quotes from regex, %v", err) |
| } |
| |
| rr.Regexp, err = regexp.Compile(regex) |
| if err != nil { |
| return fmt.Errorf("unable to unmarshal region regex, %v", err) |
| } |
| return nil |
| } |
| |
| type regions map[string]region |
| |
| type region struct { |
| Description string `json:"description"` |
| } |
| |
| type services map[string]service |
| |
| type service struct { |
| PartitionEndpoint string `json:"partitionEndpoint"` |
| IsRegionalized boxedBool `json:"isRegionalized,omitempty"` |
| Defaults endpoint `json:"defaults"` |
| Endpoints endpoints `json:"endpoints"` |
| } |
| |
| func (s *service) endpointForRegion(region string) (endpoint, bool) { |
| if s.IsRegionalized == boxedFalse { |
| return s.Endpoints[s.PartitionEndpoint], region == s.PartitionEndpoint |
| } |
| |
| if e, ok := s.Endpoints[region]; ok { |
| return e, true |
| } |
| |
| // Unable to find any matching endpoint, return |
| // blank that will be used for generic endpoint creation. |
| return endpoint{}, false |
| } |
| |
| type endpoints map[string]endpoint |
| |
| type endpoint struct { |
| Hostname string `json:"hostname"` |
| Protocols []string `json:"protocols"` |
| CredentialScope credentialScope `json:"credentialScope"` |
| |
| // Custom fields not modeled |
| HasDualStack boxedBool `json:"-"` |
| DualStackHostname string `json:"-"` |
| |
| // Signature Version not used |
| SignatureVersions []string `json:"signatureVersions"` |
| |
| // SSLCommonName not used. |
| SSLCommonName string `json:"sslCommonName"` |
| } |
| |
| const ( |
| defaultProtocol = "https" |
| defaultSigner = "v4" |
| ) |
| |
| var ( |
| protocolPriority = []string{"https", "http"} |
| signerPriority = []string{"v4", "v2"} |
| ) |
| |
| func getByPriority(s []string, p []string, def string) string { |
| if len(s) == 0 { |
| return def |
| } |
| |
| for i := 0; i < len(p); i++ { |
| for j := 0; j < len(s); j++ { |
| if s[j] == p[i] { |
| return s[j] |
| } |
| } |
| } |
| |
| return s[0] |
| } |
| |
| func (e endpoint) resolve(service, region, dnsSuffix string, defs []endpoint, opts Options) ResolvedEndpoint { |
| var merged endpoint |
| for _, def := range defs { |
| merged.mergeIn(def) |
| } |
| merged.mergeIn(e) |
| e = merged |
| |
| hostname := e.Hostname |
| |
| // Offset the hostname for dualstack if enabled |
| if opts.UseDualStack && e.HasDualStack == boxedTrue { |
| hostname = e.DualStackHostname |
| } |
| |
| u := strings.Replace(hostname, "{service}", service, 1) |
| u = strings.Replace(u, "{region}", region, 1) |
| u = strings.Replace(u, "{dnsSuffix}", dnsSuffix, 1) |
| |
| scheme := getEndpointScheme(e.Protocols, opts.DisableSSL) |
| u = fmt.Sprintf("%s://%s", scheme, u) |
| |
| signingRegion := e.CredentialScope.Region |
| if len(signingRegion) == 0 { |
| signingRegion = region |
| } |
| |
| signingName := e.CredentialScope.Service |
| var signingNameDerived bool |
| if len(signingName) == 0 { |
| signingName = service |
| signingNameDerived = true |
| } |
| |
| return ResolvedEndpoint{ |
| URL: u, |
| SigningRegion: signingRegion, |
| SigningName: signingName, |
| SigningNameDerived: signingNameDerived, |
| SigningMethod: getByPriority(e.SignatureVersions, signerPriority, defaultSigner), |
| } |
| } |
| |
| func getEndpointScheme(protocols []string, disableSSL bool) string { |
| if disableSSL { |
| return "http" |
| } |
| |
| return getByPriority(protocols, protocolPriority, defaultProtocol) |
| } |
| |
| func (e *endpoint) mergeIn(other endpoint) { |
| if len(other.Hostname) > 0 { |
| e.Hostname = other.Hostname |
| } |
| if len(other.Protocols) > 0 { |
| e.Protocols = other.Protocols |
| } |
| if len(other.SignatureVersions) > 0 { |
| e.SignatureVersions = other.SignatureVersions |
| } |
| if len(other.CredentialScope.Region) > 0 { |
| e.CredentialScope.Region = other.CredentialScope.Region |
| } |
| if len(other.CredentialScope.Service) > 0 { |
| e.CredentialScope.Service = other.CredentialScope.Service |
| } |
| if len(other.SSLCommonName) > 0 { |
| e.SSLCommonName = other.SSLCommonName |
| } |
| if other.HasDualStack != boxedBoolUnset { |
| e.HasDualStack = other.HasDualStack |
| } |
| if len(other.DualStackHostname) > 0 { |
| e.DualStackHostname = other.DualStackHostname |
| } |
| } |
| |
| type credentialScope struct { |
| Region string `json:"region"` |
| Service string `json:"service"` |
| } |
| |
| type boxedBool int |
| |
| func (b *boxedBool) UnmarshalJSON(buf []byte) error { |
| v, err := strconv.ParseBool(string(buf)) |
| if err != nil { |
| return err |
| } |
| |
| if v { |
| *b = boxedTrue |
| } else { |
| *b = boxedFalse |
| } |
| |
| return nil |
| } |
| |
| const ( |
| boxedBoolUnset boxedBool = iota |
| boxedFalse |
| boxedTrue |
| ) |