From d3683963cfbee0700fe55252adcfc8a4a6b3cbe8 Mon Sep 17 00:00:00 2001 From: skotambkar Date: Thu, 5 Nov 2020 09:18:38 -0800 Subject: [PATCH] handwritten prototype of presign url client --- service/internal/presigned-url/middleware.go | 21 ++-- service/rds/api_client.go | 54 ++++++++++ service/rds/api_op_CopyDBClusterSnapshot.go | 107 ++++++++----------- 3 files changed, 107 insertions(+), 75 deletions(-) diff --git a/service/internal/presigned-url/middleware.go b/service/internal/presigned-url/middleware.go index 3c685debd0a..8441bffa4e5 100644 --- a/service/internal/presigned-url/middleware.go +++ b/service/internal/presigned-url/middleware.go @@ -3,9 +3,10 @@ package presignedurl import ( "context" "fmt" - "net/http" awsmiddleware "github.com/aws/aws-sdk-go-v2/aws/middleware" + v4 "github.com/aws/aws-sdk-go-v2/aws/signer/v4" + "github.com/awslabs/smithy-go/middleware" ) @@ -18,20 +19,16 @@ type ParameterAccessor struct { CopyInput func(interface{}) (interface{}, error) SetDestinationRegion func(interface{}, string) error SetPresignedURL func(interface{}, string) error -} -// URLPresigner provides the interface to presign the input parameters in to a -// presigned URL. -type URLPresigner interface { - PresignURL(ctx context.Context, srcRegion string, params interface{}) ( - presignedURL string, signedHeader http.Header, err error, - ) + // GetPresigner fetches a request presigner + // The function should be of format: + // func $name (ctx context.Context, srcRegion string, params interface{}) (v4.HTTPRequest, error) + GetPresigner func(context.Context, string, interface{}) (v4.PresignedHTTPRequest, error) } // Options provides the set of options needed by the presigned URL middleware. type Options struct { - Accessor ParameterAccessor - Presigner URLPresigner + Accessor ParameterAccessor } // AddMiddleware adds the Presign URL middleware to the middleware stack. @@ -84,13 +81,13 @@ func (m *presignMiddleware) HandleInitialize( return out, metadata, fmt.Errorf("presign middleware failed, %w", err) } - presignedURL, _, err := m.options.Presigner.PresignURL(ctx, srcRegion, paramCpy) + presignedReq, err := m.options.Accessor.GetPresigner(ctx, srcRegion, paramCpy) if err != nil { return out, metadata, fmt.Errorf("unable to create presigned URL, %w", err) } // Update the original input with the presigned URL value. - if err = m.options.Accessor.SetPresignedURL(input.Parameters, presignedURL); err != nil { + if err = m.options.Accessor.SetPresignedURL(input.Parameters, presignedReq.URL); err != nil { return out, metadata, fmt.Errorf("presign middleware failed, %w", err) } diff --git a/service/rds/api_client.go b/service/rds/api_client.go index 0d205c14e55..7ed59329423 100644 --- a/service/rds/api_client.go +++ b/service/rds/api_client.go @@ -6,6 +6,7 @@ import ( "context" "github.com/aws/aws-sdk-go-v2/aws" awsmiddleware "github.com/aws/aws-sdk-go-v2/aws/middleware" + "github.com/aws/aws-sdk-go-v2/aws/protocol/query" "github.com/aws/aws-sdk-go-v2/aws/retry" "github.com/aws/aws-sdk-go-v2/aws/signer/v4" awshttp "github.com/aws/aws-sdk-go-v2/aws/transport/http" @@ -194,3 +195,56 @@ func addRequestIDRetrieverMiddleware(stack *middleware.Stack) { func addResponseErrorMiddleware(stack *middleware.Stack) { awshttp.AddResponseErrorMiddleware(stack) } + +/* + Handwritten code +*/ + +// PresignClient is a presigner client +type PresignClient struct { + // no need to export these members + // client used by presigner + client *Client + // signer used for presigning + presigner *v4.Signer +} + +// NewPresignClient returns a pre-signed client +func NewPresignClient(options Options, optFns ...func(*Options)) *PresignClient { + client := New(options, optFns...) + presigner := v4.NewSigner() + return &PresignClient{ + client: client, + presigner: presigner, + } +} + +// WithPresigner returns a pre-signed client that uses an existing client and signer options +// to configure the signer used for presigning +// TODO: this util can be added later if need be. This util exposes a way to configure with v4 signer options. +// TODO: should this method be on Client. +func WithPresigner(c *Client, optsFn ...func(signer *v4.Signer)) *PresignClient { + return &PresignClient{client: c, presigner: v4.NewSigner(optsFn...)} +} + +func (c *PresignClient) convertToPresignMiddleware(stack *middleware.Stack, options Options) (err error) { + // remove middlewares not needed by presigner step + stack.Finalize.Clear() + stack.Deserialize.Clear() + + // remove request invocation id middleware as it conflicts with the presigned url usage + stack.Build.Remove(awsmiddleware.RequestInvocationIDMiddleware{}.ID()) + + // add presign middleware in Finalize step, this if failed should be retried + err = stack.Finalize.Add(v4.NewPresignHTTPRequestMiddleware(options.Credentials, c.presigner), middleware.After) + if err != nil { + return err + } + + // we convert all request to a GET request, this is required for generating presigned url + err = query.AddAsGetRequestMiddleware(stack) + if err != nil { + return err + } + return nil +} diff --git a/service/rds/api_op_CopyDBClusterSnapshot.go b/service/rds/api_op_CopyDBClusterSnapshot.go index 301b2a559ea..c109f14fbbd 100644 --- a/service/rds/api_op_CopyDBClusterSnapshot.go +++ b/service/rds/api_op_CopyDBClusterSnapshot.go @@ -5,14 +5,14 @@ package rds import ( "context" "fmt" + + "github.com/awslabs/smithy-go/middleware" + smithyhttp "github.com/awslabs/smithy-go/transport/http" + awsmiddleware "github.com/aws/aws-sdk-go-v2/aws/middleware" - "github.com/aws/aws-sdk-go-v2/aws/protocol/query" "github.com/aws/aws-sdk-go-v2/aws/signer/v4" presignedurlcust "github.com/aws/aws-sdk-go-v2/service/internal/presigned-url" "github.com/aws/aws-sdk-go-v2/service/rds/types" - "github.com/awslabs/smithy-go/middleware" - smithyhttp "github.com/awslabs/smithy-go/transport/http" - "net/http" ) // Copies a snapshot of a DB cluster. To copy a DB cluster snapshot from a shared @@ -295,52 +295,24 @@ func setCopyDBClusterSnapshotdestinationRegion(params interface{}, value string) return nil } -type copyDBClusterSnapshotHTTPPresignURLClient struct { - client *Client - presigner *v4.Signer -} - -func newCopyDBClusterSnapshotHTTPPresignURLClient(options Options, optFns ...func(*Options)) *copyDBClusterSnapshotHTTPPresignURLClient { - return ©DBClusterSnapshotHTTPPresignURLClient{ - client: New(options, optFns...), - presigner: v4.NewSigner(), - } -} -func (c *copyDBClusterSnapshotHTTPPresignURLClient) PresignCopyDBClusterSnapshot(ctx context.Context, params *CopyDBClusterSnapshotInput, optFns ...func(*Options)) (string, http.Header, error) { - if params == nil { - params = &CopyDBClusterSnapshotInput{} +// accessor function required for Autofill customization +func (c *PresignClient) getPresignedURL(ctx context.Context, region string, params interface{}) (result v4.PresignedHTTPRequest, err error) { + // validate params to be operation specific + input, ok := params.(*CopyDBClusterSnapshotInput) + if !ok { + return result, fmt.Errorf("expect *CopyDBClusterSnapshotInput type, got %T", params) } - - optFns = append(optFns, func(o *Options) { - o.HTTPClient = &smithyhttp.NopClient{} - }) - - ctx = presignedurlcust.WithIsPresigning(ctx) - result, _, err := c.client.invokeOperation(ctx, "CopyDBClusterSnapshot", params, optFns, - addOperationCopyDBClusterSnapshotMiddlewares, - c.convertToPresignMiddleware, - ) - if err != nil { - return ``, nil, err + optFn := func(o *Options) { + o.Region = region + o.APIOptions = append(o.APIOptions, presignedurlcust.RemoveMiddleware) } - out := result.(*v4.PresignedHTTPRequest) - return out.URL, out.SignedHeader, nil -} -func (c *copyDBClusterSnapshotHTTPPresignURLClient) convertToPresignMiddleware(stack *middleware.Stack, options Options) (err error) { - stack.Finalize.Clear() - stack.Deserialize.Clear() - stack.Build.Remove(awsmiddleware.RequestInvocationIDMiddleware{}.ID()) - err = stack.Finalize.Add(v4.NewPresignHTTPRequestMiddleware(options.Credentials, c.presigner), middleware.After) - if err != nil { - return err - } - err = query.AddAsGetRequestMiddleware(stack) - if err != nil { - return err - } - return nil + // perform operation presigning + result, err = c.PresignCopyDBClusterSnapshot(ctx, input, optFn) + // return presigned http request + return result, err } + func addCopyDBClusterSnapshotPresignURLMiddleware(stack *middleware.Stack, options Options) error { return presignedurlcust.AddMiddleware(stack, presignedurlcust.Options{ Accessor: presignedurlcust.ParameterAccessor{ @@ -349,27 +321,11 @@ func addCopyDBClusterSnapshotPresignURLMiddleware(stack *middleware.Stack, optio CopyInput: copyCopyDBClusterSnapshotInputForPresign, SetDestinationRegion: setCopyDBClusterSnapshotdestinationRegion, SetPresignedURL: setCopyDBClusterSnapshotPreSignedUrl, + GetPresigner: NewPresignClient(options).getPresignedURL, }, - Presigner: &presignAutoFillCopyDBClusterSnapshotClient{client: newCopyDBClusterSnapshotHTTPPresignURLClient(options)}, }) } -type presignAutoFillCopyDBClusterSnapshotClient struct { - client *copyDBClusterSnapshotHTTPPresignURLClient -} - -func (c *presignAutoFillCopyDBClusterSnapshotClient) PresignURL(ctx context.Context, region string, params interface{}) (string, http.Header, error) { - input, ok := params.(*CopyDBClusterSnapshotInput) - if !ok { - return ``, nil, fmt.Errorf("expect *CopyDBClusterSnapshotInput type, got %T", params) - } - optFn := func(o *Options) { - o.Region = region - o.APIOptions = append(o.APIOptions, presignedurlcust.RemoveMiddleware) - } - return c.client.PresignCopyDBClusterSnapshot(ctx, input, optFn) -} - func newServiceMetadataMiddleware_opCopyDBClusterSnapshot(region string) awsmiddleware.RegisterServiceMetadata { return awsmiddleware.RegisterServiceMetadata{ Region: region, @@ -378,3 +334,28 @@ func newServiceMetadataMiddleware_opCopyDBClusterSnapshot(region string) awsmidd OperationName: "CopyDBClusterSnapshot", } } + +// PresignCopyDBClusterSnapshot returns a Presigned HTTP request structure containing Request URL, method and SignedHeaders along with an error +func (c *PresignClient) PresignCopyDBClusterSnapshot(ctx context.Context, input *CopyDBClusterSnapshotInput, optFns ...func(*Options)) (v4.PresignedHTTPRequest, error) { + if input == nil { + input = &CopyDBClusterSnapshotInput{} + } + + optFns = append(optFns, func(o *Options) { + o.HTTPClient = &smithyhttp.NopClient{} + }) + + ctx = presignedurlcust.WithIsPresigning(ctx) + // TODO: metadata is lost here. Is it required? how to propogate it upwards if needed. + // alternatively should the metadata be logged in case of error? + result, _, err := c.client.invokeOperation(ctx, "CopyDBClusterSnapshot", input, optFns, + addOperationCopyDBClusterSnapshotMiddlewares, + c.convertToPresignMiddleware, + ) + if err != nil { + return v4.PresignedHTTPRequest{}, err + } + + out := result.(*v4.PresignedHTTPRequest) + return *out, nil +}