s3 cache client-side support

Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
This commit is contained in:
CrazyMax
2022-08-30 17:00:10 +02:00
parent 6804bcbf12
commit 57d22a7bd1
305 changed files with 45257 additions and 6 deletions

View File

@ -0,0 +1,148 @@
package client
import (
"context"
"fmt"
"net/http"
"time"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/aws/middleware"
"github.com/aws/aws-sdk-go-v2/aws/retry"
awshttp "github.com/aws/aws-sdk-go-v2/aws/transport/http"
"github.com/aws/smithy-go"
smithymiddleware "github.com/aws/smithy-go/middleware"
smithyhttp "github.com/aws/smithy-go/transport/http"
)
// ServiceID is the client identifer
const ServiceID = "endpoint-credentials"
// HTTPClient is a client for sending HTTP requests
type HTTPClient interface {
Do(*http.Request) (*http.Response, error)
}
// Options is the endpoint client configurable options
type Options struct {
// The endpoint to retrieve credentials from
Endpoint string
// The HTTP client to invoke API calls with. Defaults to client's default HTTP
// implementation if nil.
HTTPClient HTTPClient
// Retryer guides how HTTP requests should be retried in case of recoverable
// failures. When nil the API client will use a default retryer.
Retryer aws.Retryer
// Set of options to modify how the credentials operation is invoked.
APIOptions []func(*smithymiddleware.Stack) error
}
// Copy creates a copy of the API options.
func (o Options) Copy() Options {
to := o
to.APIOptions = make([]func(*smithymiddleware.Stack) error, len(o.APIOptions))
copy(to.APIOptions, o.APIOptions)
return to
}
// Client is an client for retrieving AWS credentials from an endpoint
type Client struct {
options Options
}
// New constructs a new Client from the given options
func New(options Options, optFns ...func(*Options)) *Client {
options = options.Copy()
if options.HTTPClient == nil {
options.HTTPClient = awshttp.NewBuildableClient()
}
if options.Retryer == nil {
options.Retryer = retry.NewStandard()
}
for _, fn := range optFns {
fn(&options)
}
client := &Client{
options: options,
}
return client
}
// GetCredentialsInput is the input to send with the endpoint service to receive credentials.
type GetCredentialsInput struct {
AuthorizationToken string
}
// GetCredentials retrieves credentials from credential endpoint
func (c *Client) GetCredentials(ctx context.Context, params *GetCredentialsInput, optFns ...func(*Options)) (*GetCredentialsOutput, error) {
stack := smithymiddleware.NewStack("GetCredentials", smithyhttp.NewStackRequest)
options := c.options.Copy()
for _, fn := range optFns {
fn(&options)
}
stack.Serialize.Add(&serializeOpGetCredential{}, smithymiddleware.After)
stack.Build.Add(&buildEndpoint{Endpoint: options.Endpoint}, smithymiddleware.After)
stack.Deserialize.Add(&deserializeOpGetCredential{}, smithymiddleware.After)
retry.AddRetryMiddlewares(stack, retry.AddRetryMiddlewaresOptions{Retryer: options.Retryer})
middleware.AddSDKAgentKey(middleware.FeatureMetadata, ServiceID)
smithyhttp.AddErrorCloseResponseBodyMiddleware(stack)
smithyhttp.AddCloseResponseBodyMiddleware(stack)
for _, fn := range options.APIOptions {
if err := fn(stack); err != nil {
return nil, err
}
}
handler := smithymiddleware.DecorateHandler(smithyhttp.NewClientHandler(options.HTTPClient), stack)
result, _, err := handler.Handle(ctx, params)
if err != nil {
return nil, err
}
return result.(*GetCredentialsOutput), err
}
// GetCredentialsOutput is the response from the credential endpoint
type GetCredentialsOutput struct {
Expiration *time.Time
AccessKeyID string
SecretAccessKey string
Token string
}
// EndpointError is an error returned from the endpoint service
type EndpointError struct {
Code string `json:"code"`
Message string `json:"message"`
Fault smithy.ErrorFault `json:"-"`
}
// Error is the error mesage string
func (e *EndpointError) Error() string {
return fmt.Sprintf("%s: %s", e.Code, e.Message)
}
// ErrorCode is the error code returned by the endpoint
func (e *EndpointError) ErrorCode() string {
return e.Code
}
// ErrorMessage is the error message returned by the endpoint
func (e *EndpointError) ErrorMessage() string {
return e.Message
}
// ErrorFault indicates error fault classification
func (e *EndpointError) ErrorFault() smithy.ErrorFault {
return e.Fault
}

View File

@ -0,0 +1,120 @@
package client
import (
"context"
"encoding/json"
"fmt"
"net/url"
"github.com/aws/smithy-go"
smithymiddleware "github.com/aws/smithy-go/middleware"
smithyhttp "github.com/aws/smithy-go/transport/http"
)
type buildEndpoint struct {
Endpoint string
}
func (b *buildEndpoint) ID() string {
return "BuildEndpoint"
}
func (b *buildEndpoint) HandleBuild(ctx context.Context, in smithymiddleware.BuildInput, next smithymiddleware.BuildHandler) (
out smithymiddleware.BuildOutput, metadata smithymiddleware.Metadata, err error,
) {
request, ok := in.Request.(*smithyhttp.Request)
if !ok {
return out, metadata, fmt.Errorf("unknown transport, %T", in.Request)
}
if len(b.Endpoint) == 0 {
return out, metadata, fmt.Errorf("endpoint not provided")
}
parsed, err := url.Parse(b.Endpoint)
if err != nil {
return out, metadata, fmt.Errorf("failed to parse endpoint, %w", err)
}
request.URL = parsed
return next.HandleBuild(ctx, in)
}
type serializeOpGetCredential struct{}
func (s *serializeOpGetCredential) ID() string {
return "OperationSerializer"
}
func (s *serializeOpGetCredential) HandleSerialize(ctx context.Context, in smithymiddleware.SerializeInput, next smithymiddleware.SerializeHandler) (
out smithymiddleware.SerializeOutput, metadata smithymiddleware.Metadata, err error,
) {
request, ok := in.Request.(*smithyhttp.Request)
if !ok {
return out, metadata, fmt.Errorf("unknown transport type, %T", in.Request)
}
params, ok := in.Parameters.(*GetCredentialsInput)
if !ok {
return out, metadata, fmt.Errorf("unknown input parameters, %T", in.Parameters)
}
const acceptHeader = "Accept"
request.Header[acceptHeader] = append(request.Header[acceptHeader][:0], "application/json")
if len(params.AuthorizationToken) > 0 {
const authHeader = "Authorization"
request.Header[authHeader] = append(request.Header[authHeader][:0], params.AuthorizationToken)
}
return next.HandleSerialize(ctx, in)
}
type deserializeOpGetCredential struct{}
func (d *deserializeOpGetCredential) ID() string {
return "OperationDeserializer"
}
func (d *deserializeOpGetCredential) HandleDeserialize(ctx context.Context, in smithymiddleware.DeserializeInput, next smithymiddleware.DeserializeHandler) (
out smithymiddleware.DeserializeOutput, metadata smithymiddleware.Metadata, err error,
) {
out, metadata, err = next.HandleDeserialize(ctx, in)
if err != nil {
return out, metadata, err
}
response, ok := out.RawResponse.(*smithyhttp.Response)
if !ok {
return out, metadata, &smithy.DeserializationError{Err: fmt.Errorf("unknown transport type %T", out.RawResponse)}
}
if response.StatusCode < 200 || response.StatusCode >= 300 {
return out, metadata, deserializeError(response)
}
var shape *GetCredentialsOutput
if err = json.NewDecoder(response.Body).Decode(&shape); err != nil {
return out, metadata, &smithy.DeserializationError{Err: fmt.Errorf("failed to deserialize json response, %w", err)}
}
out.Result = shape
return out, metadata, err
}
func deserializeError(response *smithyhttp.Response) error {
var errShape *EndpointError
err := json.NewDecoder(response.Body).Decode(&errShape)
if err != nil {
return &smithy.DeserializationError{Err: fmt.Errorf("failed to decode error message, %w", err)}
}
if response.StatusCode >= 500 {
errShape.Fault = smithy.FaultServer
} else {
errShape.Fault = smithy.FaultClient
}
return errShape
}

View File

@ -0,0 +1,133 @@
// Package endpointcreds provides support for retrieving credentials from an
// arbitrary HTTP endpoint.
//
// The credentials endpoint Provider can receive both static and refreshable
// credentials that will expire. Credentials are static when an "Expiration"
// value is not provided in the endpoint's response.
//
// Static credentials will never expire once they have been retrieved. The format
// of the static credentials response:
// {
// "AccessKeyId" : "MUA...",
// "SecretAccessKey" : "/7PC5om....",
// }
//
// Refreshable credentials will expire within the "ExpiryWindow" of the Expiration
// value in the response. The format of the refreshable credentials response:
// {
// "AccessKeyId" : "MUA...",
// "SecretAccessKey" : "/7PC5om....",
// "Token" : "AQoDY....=",
// "Expiration" : "2016-02-25T06:03:31Z"
// }
//
// Errors should be returned in the following format and only returned with 400
// or 500 HTTP status codes.
// {
// "code": "ErrorCode",
// "message": "Helpful error message."
// }
package endpointcreds
import (
"context"
"fmt"
"net/http"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/credentials/endpointcreds/internal/client"
"github.com/aws/smithy-go/middleware"
)
// ProviderName is the name of the credentials provider.
const ProviderName = `CredentialsEndpointProvider`
type getCredentialsAPIClient interface {
GetCredentials(context.Context, *client.GetCredentialsInput, ...func(*client.Options)) (*client.GetCredentialsOutput, error)
}
// Provider satisfies the aws.CredentialsProvider interface, and is a client to
// retrieve credentials from an arbitrary endpoint.
type Provider struct {
// The AWS Client to make HTTP requests to the endpoint with. The endpoint
// the request will be made to is provided by the aws.Config's
// EndpointResolver.
client getCredentialsAPIClient
options Options
}
// HTTPClient is a client for sending HTTP requests
type HTTPClient interface {
Do(*http.Request) (*http.Response, error)
}
// Options is structure of configurable options for Provider
type Options struct {
// Endpoint to retrieve credentials from. Required
Endpoint string
// HTTPClient to handle sending HTTP requests to the target endpoint.
HTTPClient HTTPClient
// Set of options to modify how the credentials operation is invoked.
APIOptions []func(*middleware.Stack) error
// The Retryer to be used for determining whether a failed requested should be retried
Retryer aws.Retryer
// Optional authorization token value if set will be used as the value of
// the Authorization header of the endpoint credential request.
AuthorizationToken string
}
// New returns a credentials Provider for retrieving AWS credentials
// from arbitrary endpoint.
func New(endpoint string, optFns ...func(*Options)) *Provider {
o := Options{
Endpoint: endpoint,
}
for _, fn := range optFns {
fn(&o)
}
p := &Provider{
client: client.New(client.Options{
HTTPClient: o.HTTPClient,
Endpoint: o.Endpoint,
APIOptions: o.APIOptions,
Retryer: o.Retryer,
}),
options: o,
}
return p
}
// Retrieve will attempt to request the credentials from the endpoint the Provider
// was configured for. And error will be returned if the retrieval fails.
func (p *Provider) Retrieve(ctx context.Context) (aws.Credentials, error) {
resp, err := p.getCredentials(ctx)
if err != nil {
return aws.Credentials{}, fmt.Errorf("failed to load credentials, %w", err)
}
creds := aws.Credentials{
AccessKeyID: resp.AccessKeyID,
SecretAccessKey: resp.SecretAccessKey,
SessionToken: resp.Token,
Source: ProviderName,
}
if resp.Expiration != nil {
creds.CanExpire = true
creds.Expires = *resp.Expiration
}
return creds, nil
}
func (p *Provider) getCredentials(ctx context.Context) (*client.GetCredentialsOutput, error) {
return p.client.GetCredentials(ctx, &client.GetCredentialsInput{AuthorizationToken: p.options.AuthorizationToken})
}