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