client.go

  1package client
  2
  3import (
  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
 19const ServiceID = "endpoint-credentials"
 20
 21// HTTPClient is a client for sending HTTP requests
 22type HTTPClient interface {
 23	Do(*http.Request) (*http.Response, error)
 24}
 25
 26// Options is the endpoint client configurable options
 27type 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.
 44func (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
 52type Client struct {
 53	options Options
 54}
 55
 56// New constructs a new Client from the given options
 57func 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.
 89type GetCredentialsInput struct {
 90	AuthorizationToken string
 91}
 92
 93// GetCredentials retrieves credentials from credential endpoint
 94func (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
126type 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
135type 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
143func (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
148func (e *EndpointError) ErrorCode() string {
149	return e.Code
150}
151
152// ErrorMessage is the error message returned by the endpoint
153func (e *EndpointError) ErrorMessage() string {
154	return e.Message
155}
156
157// ErrorFault indicates error fault classification
158func (e *EndpointError) ErrorFault() smithy.ErrorFault {
159	return e.Fault
160}
161
162// HTTPStatusCode implements retry.HTTPStatusCode.
163func (e *EndpointError) HTTPStatusCode() int {
164	return e.statusCode
165}