endpoints.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449
  1. package endpoints
  2. import (
  3. "fmt"
  4. "regexp"
  5. "github.com/aws/aws-sdk-go/aws/awserr"
  6. )
  7. // Options provide the configuration needed to direct how the
  8. // endpoints will be resolved.
  9. type Options struct {
  10. // DisableSSL forces the endpoint to be resolved as HTTP.
  11. // instead of HTTPS if the service supports it.
  12. DisableSSL bool
  13. // Sets the resolver to resolve the endpoint as a dualstack endpoint
  14. // for the service. If dualstack support for a service is not known and
  15. // StrictMatching is not enabled a dualstack endpoint for the service will
  16. // be returned. This endpoint may not be valid. If StrictMatching is
  17. // enabled only services that are known to support dualstack will return
  18. // dualstack endpoints.
  19. UseDualStack bool
  20. // Enables strict matching of services and regions resolved endpoints.
  21. // If the partition doesn't enumerate the exact service and region an
  22. // error will be returned. This option will prevent returning endpoints
  23. // that look valid, but may not resolve to any real endpoint.
  24. StrictMatching bool
  25. // Enables resolving a service endpoint based on the region provided if the
  26. // service does not exist. The service endpoint ID will be used as the service
  27. // domain name prefix. By default the endpoint resolver requires the service
  28. // to be known when resolving endpoints.
  29. //
  30. // If resolving an endpoint on the partition list the provided region will
  31. // be used to determine which partition's domain name pattern to the service
  32. // endpoint ID with. If both the service and region are unkonwn and resolving
  33. // the endpoint on partition list an UnknownEndpointError error will be returned.
  34. //
  35. // If resolving and endpoint on a partition specific resolver that partition's
  36. // domain name pattern will be used with the service endpoint ID. If both
  37. // region and service do not exist when resolving an endpoint on a specific
  38. // partition the partition's domain pattern will be used to combine the
  39. // endpoint and region together.
  40. //
  41. // This option is ignored if StrictMatching is enabled.
  42. ResolveUnknownService bool
  43. }
  44. // Set combines all of the option functions together.
  45. func (o *Options) Set(optFns ...func(*Options)) {
  46. for _, fn := range optFns {
  47. fn(o)
  48. }
  49. }
  50. // DisableSSLOption sets the DisableSSL options. Can be used as a functional
  51. // option when resolving endpoints.
  52. func DisableSSLOption(o *Options) {
  53. o.DisableSSL = true
  54. }
  55. // UseDualStackOption sets the UseDualStack option. Can be used as a functional
  56. // option when resolving endpoints.
  57. func UseDualStackOption(o *Options) {
  58. o.UseDualStack = true
  59. }
  60. // StrictMatchingOption sets the StrictMatching option. Can be used as a functional
  61. // option when resolving endpoints.
  62. func StrictMatchingOption(o *Options) {
  63. o.StrictMatching = true
  64. }
  65. // ResolveUnknownServiceOption sets the ResolveUnknownService option. Can be used
  66. // as a functional option when resolving endpoints.
  67. func ResolveUnknownServiceOption(o *Options) {
  68. o.ResolveUnknownService = true
  69. }
  70. // A Resolver provides the interface for functionality to resolve endpoints.
  71. // The build in Partition and DefaultResolver return value satisfy this interface.
  72. type Resolver interface {
  73. EndpointFor(service, region string, opts ...func(*Options)) (ResolvedEndpoint, error)
  74. }
  75. // ResolverFunc is a helper utility that wraps a function so it satisfies the
  76. // Resolver interface. This is useful when you want to add additional endpoint
  77. // resolving logic, or stub out specific endpoints with custom values.
  78. type ResolverFunc func(service, region string, opts ...func(*Options)) (ResolvedEndpoint, error)
  79. // EndpointFor wraps the ResolverFunc function to satisfy the Resolver interface.
  80. func (fn ResolverFunc) EndpointFor(service, region string, opts ...func(*Options)) (ResolvedEndpoint, error) {
  81. return fn(service, region, opts...)
  82. }
  83. var schemeRE = regexp.MustCompile("^([^:]+)://")
  84. // AddScheme adds the HTTP or HTTPS schemes to a endpoint URL if there is no
  85. // scheme. If disableSSL is true HTTP will set HTTP instead of the default HTTPS.
  86. //
  87. // If disableSSL is set, it will only set the URL's scheme if the URL does not
  88. // contain a scheme.
  89. func AddScheme(endpoint string, disableSSL bool) string {
  90. if !schemeRE.MatchString(endpoint) {
  91. scheme := "https"
  92. if disableSSL {
  93. scheme = "http"
  94. }
  95. endpoint = fmt.Sprintf("%s://%s", scheme, endpoint)
  96. }
  97. return endpoint
  98. }
  99. // EnumPartitions a provides a way to retrieve the underlying partitions that
  100. // make up the SDK's default Resolver, or any resolver decoded from a model
  101. // file.
  102. //
  103. // Use this interface with DefaultResolver and DecodeModels to get the list of
  104. // Partitions.
  105. type EnumPartitions interface {
  106. Partitions() []Partition
  107. }
  108. // RegionsForService returns a map of regions for the partition and service.
  109. // If either the partition or service does not exist false will be returned
  110. // as the second parameter.
  111. //
  112. // This example shows how to get the regions for DynamoDB in the AWS partition.
  113. // rs, exists := endpoints.RegionsForService(endpoints.DefaultPartitions(), endpoints.AwsPartitionID, endpoints.DynamodbServiceID)
  114. //
  115. // This is equivalent to using the partition directly.
  116. // rs := endpoints.AwsPartition().Services()[endpoints.DynamodbServiceID].Regions()
  117. func RegionsForService(ps []Partition, partitionID, serviceID string) (map[string]Region, bool) {
  118. for _, p := range ps {
  119. if p.ID() != partitionID {
  120. continue
  121. }
  122. if _, ok := p.p.Services[serviceID]; !ok {
  123. break
  124. }
  125. s := Service{
  126. id: serviceID,
  127. p: p.p,
  128. }
  129. return s.Regions(), true
  130. }
  131. return map[string]Region{}, false
  132. }
  133. // PartitionForRegion returns the first partition which includes the region
  134. // passed in. This includes both known regions and regions which match
  135. // a pattern supported by the partition which may include regions that are
  136. // not explicitly known by the partition. Use the Regions method of the
  137. // returned Partition if explicit support is needed.
  138. func PartitionForRegion(ps []Partition, regionID string) (Partition, bool) {
  139. for _, p := range ps {
  140. if _, ok := p.p.Regions[regionID]; ok || p.p.RegionRegex.MatchString(regionID) {
  141. return p, true
  142. }
  143. }
  144. return Partition{}, false
  145. }
  146. // A Partition provides the ability to enumerate the partition's regions
  147. // and services.
  148. type Partition struct {
  149. id string
  150. p *partition
  151. }
  152. // ID returns the identifier of the partition.
  153. func (p Partition) ID() string { return p.id }
  154. // EndpointFor attempts to resolve the endpoint based on service and region.
  155. // See Options for information on configuring how the endpoint is resolved.
  156. //
  157. // If the service cannot be found in the metadata the UnknownServiceError
  158. // error will be returned. This validation will occur regardless if
  159. // StrictMatching is enabled. To enable resolving unknown services set the
  160. // "ResolveUnknownService" option to true. When StrictMatching is disabled
  161. // this option allows the partition resolver to resolve a endpoint based on
  162. // the service endpoint ID provided.
  163. //
  164. // When resolving endpoints you can choose to enable StrictMatching. This will
  165. // require the provided service and region to be known by the partition.
  166. // If the endpoint cannot be strictly resolved an error will be returned. This
  167. // mode is useful to ensure the endpoint resolved is valid. Without
  168. // StrictMatching enabled the endpoint returned my look valid but may not work.
  169. // StrictMatching requires the SDK to be updated if you want to take advantage
  170. // of new regions and services expansions.
  171. //
  172. // Errors that can be returned.
  173. // * UnknownServiceError
  174. // * UnknownEndpointError
  175. func (p Partition) EndpointFor(service, region string, opts ...func(*Options)) (ResolvedEndpoint, error) {
  176. return p.p.EndpointFor(service, region, opts...)
  177. }
  178. // Regions returns a map of Regions indexed by their ID. This is useful for
  179. // enumerating over the regions in a partition.
  180. func (p Partition) Regions() map[string]Region {
  181. rs := map[string]Region{}
  182. for id, r := range p.p.Regions {
  183. rs[id] = Region{
  184. id: id,
  185. desc: r.Description,
  186. p: p.p,
  187. }
  188. }
  189. return rs
  190. }
  191. // Services returns a map of Service indexed by their ID. This is useful for
  192. // enumerating over the services in a partition.
  193. func (p Partition) Services() map[string]Service {
  194. ss := map[string]Service{}
  195. for id := range p.p.Services {
  196. ss[id] = Service{
  197. id: id,
  198. p: p.p,
  199. }
  200. }
  201. return ss
  202. }
  203. // A Region provides information about a region, and ability to resolve an
  204. // endpoint from the context of a region, given a service.
  205. type Region struct {
  206. id, desc string
  207. p *partition
  208. }
  209. // ID returns the region's identifier.
  210. func (r Region) ID() string { return r.id }
  211. // Description returns the region's description. The region description
  212. // is free text, it can be empty, and it may change between SDK releases.
  213. func (r Region) Description() string { return r.desc }
  214. // ResolveEndpoint resolves an endpoint from the context of the region given
  215. // a service. See Partition.EndpointFor for usage and errors that can be returned.
  216. func (r Region) ResolveEndpoint(service string, opts ...func(*Options)) (ResolvedEndpoint, error) {
  217. return r.p.EndpointFor(service, r.id, opts...)
  218. }
  219. // Services returns a list of all services that are known to be in this region.
  220. func (r Region) Services() map[string]Service {
  221. ss := map[string]Service{}
  222. for id, s := range r.p.Services {
  223. if _, ok := s.Endpoints[r.id]; ok {
  224. ss[id] = Service{
  225. id: id,
  226. p: r.p,
  227. }
  228. }
  229. }
  230. return ss
  231. }
  232. // A Service provides information about a service, and ability to resolve an
  233. // endpoint from the context of a service, given a region.
  234. type Service struct {
  235. id string
  236. p *partition
  237. }
  238. // ID returns the identifier for the service.
  239. func (s Service) ID() string { return s.id }
  240. // ResolveEndpoint resolves an endpoint from the context of a service given
  241. // a region. See Partition.EndpointFor for usage and errors that can be returned.
  242. func (s Service) ResolveEndpoint(region string, opts ...func(*Options)) (ResolvedEndpoint, error) {
  243. return s.p.EndpointFor(s.id, region, opts...)
  244. }
  245. // Regions returns a map of Regions that the service is present in.
  246. //
  247. // A region is the AWS region the service exists in. Whereas a Endpoint is
  248. // an URL that can be resolved to a instance of a service.
  249. func (s Service) Regions() map[string]Region {
  250. rs := map[string]Region{}
  251. for id := range s.p.Services[s.id].Endpoints {
  252. if r, ok := s.p.Regions[id]; ok {
  253. rs[id] = Region{
  254. id: id,
  255. desc: r.Description,
  256. p: s.p,
  257. }
  258. }
  259. }
  260. return rs
  261. }
  262. // Endpoints returns a map of Endpoints indexed by their ID for all known
  263. // endpoints for a service.
  264. //
  265. // A region is the AWS region the service exists in. Whereas a Endpoint is
  266. // an URL that can be resolved to a instance of a service.
  267. func (s Service) Endpoints() map[string]Endpoint {
  268. es := map[string]Endpoint{}
  269. for id := range s.p.Services[s.id].Endpoints {
  270. es[id] = Endpoint{
  271. id: id,
  272. serviceID: s.id,
  273. p: s.p,
  274. }
  275. }
  276. return es
  277. }
  278. // A Endpoint provides information about endpoints, and provides the ability
  279. // to resolve that endpoint for the service, and the region the endpoint
  280. // represents.
  281. type Endpoint struct {
  282. id string
  283. serviceID string
  284. p *partition
  285. }
  286. // ID returns the identifier for an endpoint.
  287. func (e Endpoint) ID() string { return e.id }
  288. // ServiceID returns the identifier the endpoint belongs to.
  289. func (e Endpoint) ServiceID() string { return e.serviceID }
  290. // ResolveEndpoint resolves an endpoint from the context of a service and
  291. // region the endpoint represents. See Partition.EndpointFor for usage and
  292. // errors that can be returned.
  293. func (e Endpoint) ResolveEndpoint(opts ...func(*Options)) (ResolvedEndpoint, error) {
  294. return e.p.EndpointFor(e.serviceID, e.id, opts...)
  295. }
  296. // A ResolvedEndpoint is an endpoint that has been resolved based on a partition
  297. // service, and region.
  298. type ResolvedEndpoint struct {
  299. // The endpoint URL
  300. URL string
  301. // The region that should be used for signing requests.
  302. SigningRegion string
  303. // The service name that should be used for signing requests.
  304. SigningName string
  305. // States that the signing name for this endpoint was derived from metadata
  306. // passed in, but was not explicitly modeled.
  307. SigningNameDerived bool
  308. // The signing method that should be used for signing requests.
  309. SigningMethod string
  310. }
  311. // So that the Error interface type can be included as an anonymous field
  312. // in the requestError struct and not conflict with the error.Error() method.
  313. type awsError awserr.Error
  314. // A EndpointNotFoundError is returned when in StrictMatching mode, and the
  315. // endpoint for the service and region cannot be found in any of the partitions.
  316. type EndpointNotFoundError struct {
  317. awsError
  318. Partition string
  319. Service string
  320. Region string
  321. }
  322. // A UnknownServiceError is returned when the service does not resolve to an
  323. // endpoint. Includes a list of all known services for the partition. Returned
  324. // when a partition does not support the service.
  325. type UnknownServiceError struct {
  326. awsError
  327. Partition string
  328. Service string
  329. Known []string
  330. }
  331. // NewUnknownServiceError builds and returns UnknownServiceError.
  332. func NewUnknownServiceError(p, s string, known []string) UnknownServiceError {
  333. return UnknownServiceError{
  334. awsError: awserr.New("UnknownServiceError",
  335. "could not resolve endpoint for unknown service", nil),
  336. Partition: p,
  337. Service: s,
  338. Known: known,
  339. }
  340. }
  341. // String returns the string representation of the error.
  342. func (e UnknownServiceError) Error() string {
  343. extra := fmt.Sprintf("partition: %q, service: %q",
  344. e.Partition, e.Service)
  345. if len(e.Known) > 0 {
  346. extra += fmt.Sprintf(", known: %v", e.Known)
  347. }
  348. return awserr.SprintError(e.Code(), e.Message(), extra, e.OrigErr())
  349. }
  350. // String returns the string representation of the error.
  351. func (e UnknownServiceError) String() string {
  352. return e.Error()
  353. }
  354. // A UnknownEndpointError is returned when in StrictMatching mode and the
  355. // service is valid, but the region does not resolve to an endpoint. Includes
  356. // a list of all known endpoints for the service.
  357. type UnknownEndpointError struct {
  358. awsError
  359. Partition string
  360. Service string
  361. Region string
  362. Known []string
  363. }
  364. // NewUnknownEndpointError builds and returns UnknownEndpointError.
  365. func NewUnknownEndpointError(p, s, r string, known []string) UnknownEndpointError {
  366. return UnknownEndpointError{
  367. awsError: awserr.New("UnknownEndpointError",
  368. "could not resolve endpoint", nil),
  369. Partition: p,
  370. Service: s,
  371. Region: r,
  372. Known: known,
  373. }
  374. }
  375. // String returns the string representation of the error.
  376. func (e UnknownEndpointError) Error() string {
  377. extra := fmt.Sprintf("partition: %q, service: %q, region: %q",
  378. e.Partition, e.Service, e.Region)
  379. if len(e.Known) > 0 {
  380. extra += fmt.Sprintf(", known: %v", e.Known)
  381. }
  382. return awserr.SprintError(e.Code(), e.Message(), extra, e.OrigErr())
  383. }
  384. // String returns the string representation of the error.
  385. func (e UnknownEndpointError) String() string {
  386. return e.Error()
  387. }