client.go raw
1 package client
2
3 import (
4 "context"
5 "fmt"
6 "net/http"
7 "time"
8
9 "github.com/aws/aws-sdk-go-v2/aws"
10 "github.com/aws/aws-sdk-go-v2/aws/middleware"
11 "github.com/aws/aws-sdk-go-v2/aws/retry"
12 awshttp "github.com/aws/aws-sdk-go-v2/aws/transport/http"
13 "github.com/aws/smithy-go"
14 smithymiddleware "github.com/aws/smithy-go/middleware"
15 smithyhttp "github.com/aws/smithy-go/transport/http"
16 )
17
18 // ServiceID is the client identifer
19 const ServiceID = "endpoint-credentials"
20
21 // HTTPClient is a client for sending HTTP requests
22 type HTTPClient interface {
23 Do(*http.Request) (*http.Response, error)
24 }
25
26 // Options is the endpoint client configurable options
27 type Options struct {
28 // The endpoint to retrieve credentials from
29 Endpoint string
30
31 // The HTTP client to invoke API calls with. Defaults to client's default HTTP
32 // implementation if nil.
33 HTTPClient HTTPClient
34
35 // Retryer guides how HTTP requests should be retried in case of recoverable
36 // failures. When nil the API client will use a default retryer.
37 Retryer aws.Retryer
38
39 // Set of options to modify how the credentials operation is invoked.
40 APIOptions []func(*smithymiddleware.Stack) error
41 }
42
43 // Copy creates a copy of the API options.
44 func (o Options) Copy() Options {
45 to := o
46 to.APIOptions = make([]func(*smithymiddleware.Stack) error, len(o.APIOptions))
47 copy(to.APIOptions, o.APIOptions)
48 return to
49 }
50
51 // Client is an client for retrieving AWS credentials from an endpoint
52 type Client struct {
53 options Options
54 }
55
56 // New constructs a new Client from the given options
57 func New(options Options, optFns ...func(*Options)) *Client {
58 options = options.Copy()
59
60 if options.HTTPClient == nil {
61 options.HTTPClient = awshttp.NewBuildableClient()
62 }
63
64 if options.Retryer == nil {
65 // Amazon-owned implementations of this endpoint are known to sometimes
66 // return plaintext responses (i.e. no Code) like normal, add a few
67 // additional status codes
68 options.Retryer = retry.NewStandard(func(o *retry.StandardOptions) {
69 o.Retryables = append(o.Retryables, retry.RetryableHTTPStatusCode{
70 Codes: map[int]struct{}{
71 http.StatusTooManyRequests: {},
72 },
73 })
74 })
75 }
76
77 for _, fn := range optFns {
78 fn(&options)
79 }
80
81 client := &Client{
82 options: options,
83 }
84
85 return client
86 }
87
88 // GetCredentialsInput is the input to send with the endpoint service to receive credentials.
89 type GetCredentialsInput struct {
90 AuthorizationToken string
91 }
92
93 // GetCredentials retrieves credentials from credential endpoint
94 func (c *Client) GetCredentials(ctx context.Context, params *GetCredentialsInput, optFns ...func(*Options)) (*GetCredentialsOutput, error) {
95 stack := smithymiddleware.NewStack("GetCredentials", smithyhttp.NewStackRequest)
96 options := c.options.Copy()
97 for _, fn := range optFns {
98 fn(&options)
99 }
100
101 stack.Serialize.Add(&serializeOpGetCredential{}, smithymiddleware.After)
102 stack.Build.Add(&buildEndpoint{Endpoint: options.Endpoint}, smithymiddleware.After)
103 stack.Deserialize.Add(&deserializeOpGetCredential{}, smithymiddleware.After)
104 addProtocolFinalizerMiddlewares(stack, options, "GetCredentials")
105 retry.AddRetryMiddlewares(stack, retry.AddRetryMiddlewaresOptions{Retryer: options.Retryer})
106 middleware.AddSDKAgentKey(middleware.FeatureMetadata, ServiceID)
107 smithyhttp.AddErrorCloseResponseBodyMiddleware(stack)
108 smithyhttp.AddCloseResponseBodyMiddleware(stack)
109
110 for _, fn := range options.APIOptions {
111 if err := fn(stack); err != nil {
112 return nil, err
113 }
114 }
115
116 handler := smithymiddleware.DecorateHandler(smithyhttp.NewClientHandler(options.HTTPClient), stack)
117 result, _, err := handler.Handle(ctx, params)
118 if err != nil {
119 return nil, err
120 }
121
122 return result.(*GetCredentialsOutput), err
123 }
124
125 // GetCredentialsOutput is the response from the credential endpoint
126 type GetCredentialsOutput struct {
127 Expiration *time.Time
128 AccessKeyID string
129 SecretAccessKey string
130 Token string
131 AccountID string
132 }
133
134 // EndpointError is an error returned from the endpoint service
135 type EndpointError struct {
136 Code string `json:"code"`
137 Message string `json:"message"`
138 Fault smithy.ErrorFault `json:"-"`
139 statusCode int `json:"-"`
140 }
141
142 // Error is the error mesage string
143 func (e *EndpointError) Error() string {
144 return fmt.Sprintf("%s: %s", e.Code, e.Message)
145 }
146
147 // ErrorCode is the error code returned by the endpoint
148 func (e *EndpointError) ErrorCode() string {
149 return e.Code
150 }
151
152 // ErrorMessage is the error message returned by the endpoint
153 func (e *EndpointError) ErrorMessage() string {
154 return e.Message
155 }
156
157 // ErrorFault indicates error fault classification
158 func (e *EndpointError) ErrorFault() smithy.ErrorFault {
159 return e.Fault
160 }
161
162 // HTTPStatusCode implements retry.HTTPStatusCode.
163 func (e *EndpointError) HTTPStatusCode() int {
164 return e.statusCode
165 }
166