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
}