v3model.go 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  1. package endpoints
  2. import (
  3. "fmt"
  4. "regexp"
  5. "strconv"
  6. "strings"
  7. )
  8. type partitions []partition
  9. func (ps partitions) EndpointFor(service, region string, opts ...func(*Options)) (ResolvedEndpoint, error) {
  10. var opt Options
  11. opt.Set(opts...)
  12. for i := 0; i < len(ps); i++ {
  13. if !ps[i].canResolveEndpoint(service, region, opt.StrictMatching) {
  14. continue
  15. }
  16. return ps[i].EndpointFor(service, region, opts...)
  17. }
  18. // If loose matching fallback to first partition format to use
  19. // when resolving the endpoint.
  20. if !opt.StrictMatching && len(ps) > 0 {
  21. return ps[0].EndpointFor(service, region, opts...)
  22. }
  23. return ResolvedEndpoint{}, NewUnknownEndpointError("all partitions", service, region, []string{})
  24. }
  25. // Partitions satisfies the EnumPartitions interface and returns a list
  26. // of Partitions representing each partition represented in the SDK's
  27. // endpoints model.
  28. func (ps partitions) Partitions() []Partition {
  29. parts := make([]Partition, 0, len(ps))
  30. for i := 0; i < len(ps); i++ {
  31. parts = append(parts, ps[i].Partition())
  32. }
  33. return parts
  34. }
  35. type partition struct {
  36. ID string `json:"partition"`
  37. Name string `json:"partitionName"`
  38. DNSSuffix string `json:"dnsSuffix"`
  39. RegionRegex regionRegex `json:"regionRegex"`
  40. Defaults endpoint `json:"defaults"`
  41. Regions regions `json:"regions"`
  42. Services services `json:"services"`
  43. }
  44. func (p partition) Partition() Partition {
  45. return Partition{
  46. id: p.ID,
  47. p: &p,
  48. }
  49. }
  50. func (p partition) canResolveEndpoint(service, region string, strictMatch bool) bool {
  51. s, hasService := p.Services[service]
  52. _, hasEndpoint := s.Endpoints[region]
  53. if hasEndpoint && hasService {
  54. return true
  55. }
  56. if strictMatch {
  57. return false
  58. }
  59. return p.RegionRegex.MatchString(region)
  60. }
  61. func (p partition) EndpointFor(service, region string, opts ...func(*Options)) (resolved ResolvedEndpoint, err error) {
  62. var opt Options
  63. opt.Set(opts...)
  64. s, hasService := p.Services[service]
  65. if !(hasService || opt.ResolveUnknownService) {
  66. // Only return error if the resolver will not fallback to creating
  67. // endpoint based on service endpoint ID passed in.
  68. return resolved, NewUnknownServiceError(p.ID, service, serviceList(p.Services))
  69. }
  70. e, hasEndpoint := s.endpointForRegion(region)
  71. if !hasEndpoint && opt.StrictMatching {
  72. return resolved, NewUnknownEndpointError(p.ID, service, region, endpointList(s.Endpoints))
  73. }
  74. defs := []endpoint{p.Defaults, s.Defaults}
  75. return e.resolve(service, region, p.DNSSuffix, defs, opt), nil
  76. }
  77. func serviceList(ss services) []string {
  78. list := make([]string, 0, len(ss))
  79. for k := range ss {
  80. list = append(list, k)
  81. }
  82. return list
  83. }
  84. func endpointList(es endpoints) []string {
  85. list := make([]string, 0, len(es))
  86. for k := range es {
  87. list = append(list, k)
  88. }
  89. return list
  90. }
  91. type regionRegex struct {
  92. *regexp.Regexp
  93. }
  94. func (rr *regionRegex) UnmarshalJSON(b []byte) (err error) {
  95. // Strip leading and trailing quotes
  96. regex, err := strconv.Unquote(string(b))
  97. if err != nil {
  98. return fmt.Errorf("unable to strip quotes from regex, %v", err)
  99. }
  100. rr.Regexp, err = regexp.Compile(regex)
  101. if err != nil {
  102. return fmt.Errorf("unable to unmarshal region regex, %v", err)
  103. }
  104. return nil
  105. }
  106. type regions map[string]region
  107. type region struct {
  108. Description string `json:"description"`
  109. }
  110. type services map[string]service
  111. type service struct {
  112. PartitionEndpoint string `json:"partitionEndpoint"`
  113. IsRegionalized boxedBool `json:"isRegionalized,omitempty"`
  114. Defaults endpoint `json:"defaults"`
  115. Endpoints endpoints `json:"endpoints"`
  116. }
  117. func (s *service) endpointForRegion(region string) (endpoint, bool) {
  118. if s.IsRegionalized == boxedFalse {
  119. return s.Endpoints[s.PartitionEndpoint], region == s.PartitionEndpoint
  120. }
  121. if e, ok := s.Endpoints[region]; ok {
  122. return e, true
  123. }
  124. // Unable to find any matching endpoint, return
  125. // blank that will be used for generic endpoint creation.
  126. return endpoint{}, false
  127. }
  128. type endpoints map[string]endpoint
  129. type endpoint struct {
  130. Hostname string `json:"hostname"`
  131. Protocols []string `json:"protocols"`
  132. CredentialScope credentialScope `json:"credentialScope"`
  133. // Custom fields not modeled
  134. HasDualStack boxedBool `json:"-"`
  135. DualStackHostname string `json:"-"`
  136. // Signature Version not used
  137. SignatureVersions []string `json:"signatureVersions"`
  138. // SSLCommonName not used.
  139. SSLCommonName string `json:"sslCommonName"`
  140. }
  141. const (
  142. defaultProtocol = "https"
  143. defaultSigner = "v4"
  144. )
  145. var (
  146. protocolPriority = []string{"https", "http"}
  147. signerPriority = []string{"v4", "v2"}
  148. )
  149. func getByPriority(s []string, p []string, def string) string {
  150. if len(s) == 0 {
  151. return def
  152. }
  153. for i := 0; i < len(p); i++ {
  154. for j := 0; j < len(s); j++ {
  155. if s[j] == p[i] {
  156. return s[j]
  157. }
  158. }
  159. }
  160. return s[0]
  161. }
  162. func (e endpoint) resolve(service, region, dnsSuffix string, defs []endpoint, opts Options) ResolvedEndpoint {
  163. var merged endpoint
  164. for _, def := range defs {
  165. merged.mergeIn(def)
  166. }
  167. merged.mergeIn(e)
  168. e = merged
  169. hostname := e.Hostname
  170. // Offset the hostname for dualstack if enabled
  171. if opts.UseDualStack && e.HasDualStack == boxedTrue {
  172. hostname = e.DualStackHostname
  173. }
  174. u := strings.Replace(hostname, "{service}", service, 1)
  175. u = strings.Replace(u, "{region}", region, 1)
  176. u = strings.Replace(u, "{dnsSuffix}", dnsSuffix, 1)
  177. scheme := getEndpointScheme(e.Protocols, opts.DisableSSL)
  178. u = fmt.Sprintf("%s://%s", scheme, u)
  179. signingRegion := e.CredentialScope.Region
  180. if len(signingRegion) == 0 {
  181. signingRegion = region
  182. }
  183. signingName := e.CredentialScope.Service
  184. if len(signingName) == 0 {
  185. signingName = service
  186. }
  187. return ResolvedEndpoint{
  188. URL: u,
  189. SigningRegion: signingRegion,
  190. SigningName: signingName,
  191. SigningMethod: getByPriority(e.SignatureVersions, signerPriority, defaultSigner),
  192. }
  193. }
  194. func getEndpointScheme(protocols []string, disableSSL bool) string {
  195. if disableSSL {
  196. return "http"
  197. }
  198. return getByPriority(protocols, protocolPriority, defaultProtocol)
  199. }
  200. func (e *endpoint) mergeIn(other endpoint) {
  201. if len(other.Hostname) > 0 {
  202. e.Hostname = other.Hostname
  203. }
  204. if len(other.Protocols) > 0 {
  205. e.Protocols = other.Protocols
  206. }
  207. if len(other.SignatureVersions) > 0 {
  208. e.SignatureVersions = other.SignatureVersions
  209. }
  210. if len(other.CredentialScope.Region) > 0 {
  211. e.CredentialScope.Region = other.CredentialScope.Region
  212. }
  213. if len(other.CredentialScope.Service) > 0 {
  214. e.CredentialScope.Service = other.CredentialScope.Service
  215. }
  216. if len(other.SSLCommonName) > 0 {
  217. e.SSLCommonName = other.SSLCommonName
  218. }
  219. if other.HasDualStack != boxedBoolUnset {
  220. e.HasDualStack = other.HasDualStack
  221. }
  222. if len(other.DualStackHostname) > 0 {
  223. e.DualStackHostname = other.DualStackHostname
  224. }
  225. }
  226. type credentialScope struct {
  227. Region string `json:"region"`
  228. Service string `json:"service"`
  229. }
  230. type boxedBool int
  231. func (b *boxedBool) UnmarshalJSON(buf []byte) error {
  232. v, err := strconv.ParseBool(string(buf))
  233. if err != nil {
  234. return err
  235. }
  236. if v {
  237. *b = boxedTrue
  238. } else {
  239. *b = boxedFalse
  240. }
  241. return nil
  242. }
  243. const (
  244. boxedBoolUnset boxedBool = iota
  245. boxedFalse
  246. boxedTrue
  247. )