diff --git a/aws/signer/v4/middleware.go b/aws/signer/v4/middleware.go
index 626715709ff..272baf37e6d 100644
--- a/aws/signer/v4/middleware.go
+++ b/aws/signer/v4/middleware.go
@@ -102,6 +102,13 @@ func AddComputePayloadSHA256Middleware(stack *middleware.Stack) error {
return stack.Build.Add(&computePayloadSHA256{}, middleware.After)
}
+// RemoveComputePayloadSHA256Middleware removes computePayloadSHA256 from the
+// operation middleware stack
+func RemoveComputePayloadSHA256Middleware(stack *middleware.Stack) error {
+ _, err := stack.Build.Remove(computePayloadHashMiddlewareID)
+ return err
+}
+
// ID is the middleware name
func (m *computePayloadSHA256) ID() string {
return computePayloadHashMiddlewareID
@@ -159,6 +166,13 @@ func AddContentSHA256HeaderMiddleware(stack *middleware.Stack) error {
return stack.Build.Insert(&contentSHA256Header{}, computePayloadHashMiddlewareID, middleware.After)
}
+// RemoveContentSHA256HeaderMiddleware removes contentSHA256Header middleware
+// from the operation middleware stack
+func RemoveContentSHA256HeaderMiddleware(stack *middleware.Stack) error {
+ _, err := stack.Build.Remove((*contentSHA256Header)(nil).ID())
+ return err
+}
+
// ID returns the ContentSHA256HeaderMiddleware identifier
func (m *contentSHA256Header) ID() string {
return "SigV4ContentSHA256Header"
diff --git a/codegen/smithy-aws-go-codegen/src/main/java/software/amazon/smithy/aws/go/codegen/AwsHttpPresignURLClientGenerator.java b/codegen/smithy-aws-go-codegen/src/main/java/software/amazon/smithy/aws/go/codegen/AwsHttpPresignURLClientGenerator.java
index 5dc35c03bd5..eb0981038a7 100644
--- a/codegen/smithy-aws-go-codegen/src/main/java/software/amazon/smithy/aws/go/codegen/AwsHttpPresignURLClientGenerator.java
+++ b/codegen/smithy-aws-go-codegen/src/main/java/software/amazon/smithy/aws/go/codegen/AwsHttpPresignURLClientGenerator.java
@@ -17,102 +17,231 @@
package software.amazon.smithy.aws.go.codegen;
-import java.util.ArrayList;
-import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.TreeSet;
import software.amazon.smithy.aws.go.codegen.customization.AwsCustomGoDependency;
+import software.amazon.smithy.aws.go.codegen.customization.PresignURLAutoFill;
+import software.amazon.smithy.aws.traits.ServiceTrait;
+import software.amazon.smithy.aws.traits.protocols.AwsQueryTrait;
+import software.amazon.smithy.aws.traits.protocols.Ec2QueryTrait;
import software.amazon.smithy.codegen.core.Symbol;
import software.amazon.smithy.codegen.core.SymbolProvider;
+import software.amazon.smithy.go.codegen.GoDelegator;
+import software.amazon.smithy.go.codegen.GoSettings;
import software.amazon.smithy.go.codegen.GoWriter;
import software.amazon.smithy.go.codegen.OperationGenerator;
import software.amazon.smithy.go.codegen.SmithyGoDependency;
import software.amazon.smithy.go.codegen.SymbolUtils;
-import software.amazon.smithy.go.codegen.integration.ProtocolUtils;
+import software.amazon.smithy.go.codegen.integration.GoIntegration;
import software.amazon.smithy.model.Model;
+import software.amazon.smithy.model.shapes.MemberShape;
import software.amazon.smithy.model.shapes.OperationShape;
+import software.amazon.smithy.model.shapes.ServiceShape;
import software.amazon.smithy.model.shapes.Shape;
-import software.amazon.smithy.utils.SmithyBuilder;
-
-public class AwsHttpPresignURLClientGenerator {
+import software.amazon.smithy.model.shapes.ShapeId;
+import software.amazon.smithy.model.traits.StreamingTrait;
+import software.amazon.smithy.utils.MapUtils;
+import software.amazon.smithy.utils.SetUtils;
+
+/**
+ * AwsHttpPresignURLClientGenerator class is a runtime plugin integration class
+ * that generates code for presign URL clients and associated presign operations.
+ *
+ * This class pulls in a static list from PresignURLAutofill customization which
+ * rely on the generated presigned url client and operation. This is done to
+ * deduplicate the listing but make this class dependent on presence of PresignURLAutofill
+ * class as a composition.
+ */
+public class AwsHttpPresignURLClientGenerator implements GoIntegration {
+ // constants
private static final String CONVERT_TO_PRESIGN_MIDDLEWARE_NAME = "convertToPresignMiddleware";
- private final Model model;
- private final SymbolProvider symbolProvider;
+ private static final String PRESIGN_CLIENT = "PresignClient";
+ private static final Symbol presignClientSymbol = buildSymbol(PRESIGN_CLIENT, true);
+
+ private static final String NEW_CLIENT = "NewPresignClient";
+ private static final String PRESIGN_OPTIONS = "PresignOptions";
+ private static final Symbol presignOptionsSymbol = buildSymbol(PRESIGN_OPTIONS, true);
+
+ private static final String PRESIGN_OPTIONS_FROM_CLIENT_OPTIONS = "WithPresignClientFromClientOptions";
+ private static final String PRESIGN_OPTIONS_FROM_EXPIRES = "WithPresignExpires";
+
+ private static final Symbol presignerInterfaceSymbol = SymbolUtils.createPointableSymbolBuilder(
+ "HTTPPresignerV4"
+ ).build();
+ private static final Symbol v4NewPresignerSymbol = SymbolUtils.createPointableSymbolBuilder(
+ "NewSigner", AwsGoDependency.AWS_SIGNER_V4
+ ).build();
+ private static final Symbol v4PresignedHTTPRequestSymbol = SymbolUtils.createPointableSymbolBuilder(
+ "PresignedHTTPRequest", AwsGoDependency.AWS_SIGNER_V4
+ ).build();
+
+ // constant map with service to list of operation for which presignedURL client and operation must be generated.
+ private static final Map> presignedClientMap = MapUtils.of(
+ ShapeId.from("com.amazonaws.s3#AmazonS3"), SetUtils.of(
+ ShapeId.from("com.amazonaws.s3#GetObject"),
+ ShapeId.from("com.amazonaws.s3#PutObject")
+ )
+ );
+
+ private static final String addAsUnsignedPayloadName(String operationName) {
+ return String.format("add%sPayloadAsUnsigned", operationName);
+ }
- private final Symbol presignClientSymbol;
- private final Symbol newPresignClientSymbol;
+ // map of service to list of operations for which presignedURL client and operation should
+ // be generated.
+ public static Map> PRESIGNER_MAP = new TreeMap<>();
- private final OperationShape operation;
- private final Symbol operationSymbol;
- private final Shape operationInput;
- private final Symbol operationInputSymbol;
+ // build pointable symbols
+ private static Symbol buildSymbol(String name, boolean exported) {
+ if (!exported) {
+ name = Character.toLowerCase(name.charAt(0)) + name.substring(1);
+ }
+ return SymbolUtils.createPointableSymbolBuilder(name).build();
+ }
- private final boolean exported;
+ /**
+ * generates code to iterate thru func optionals and assign value into the dest variable
+ *
+ * @param writer GoWriter to write the code to
+ * @param src variable name that denotes functional options
+ * @param dest variable in which result of processed functional options are stored
+ * @param destType value type used by functional options
+ */
+ private static final void processFunctionalOptions(
+ GoWriter writer,
+ String src,
+ String dest,
+ Symbol destType
+ ) {
+ writer.write("var $L $T", dest, destType);
+ writer.openBlock("for _, fn := range $L {", "}", src, () -> {
+ writer.write("fn(&$L)", dest);
+ }).insertTrailingNewline();
+ }
- private final List convertToPresignMiddlewareHelpers;
+ @Override
+ public void processFinalizedModel(GoSettings settings, Model model) {
+ PRESIGNER_MAP.putAll(presignedClientMap);
+
+ // update map for presign client/operation generation to include
+ // service/operations that use PresignURLAutoFill customization class.
+ Map> autofillMap = PresignURLAutoFill.SERVICE_TO_OPERATION_MAP;
+ for (ShapeId service : autofillMap.keySet()) {
+ if (!PRESIGNER_MAP.containsKey(service)) {
+ PRESIGNER_MAP.put(service, autofillMap.get(service));
+ } else {
+ Set operations = new TreeSet<>();
+ for (ShapeId operation : PRESIGNER_MAP.get(service)) {
+ operations.add(operation);
+ }
+ for (ShapeId operation : autofillMap.get(service)) {
+ operations.add(operation);
+ }
+ PRESIGNER_MAP.put(service, operations);
+ }
+ }
+ }
- private AwsHttpPresignURLClientGenerator(Builder builder) {
- this.exported = builder.exported;
+ @Override
+ public byte getOrder() {
+ // The associated customization ordering is relative to operation deserializers
+ // and thus the integration should be added at the end.
+ return 127;
+ }
- this.model = SmithyBuilder.requiredState("model", builder.model);
- this.symbolProvider = SmithyBuilder.requiredState("symbolProvider", builder.symbolProvider);
- this.convertToPresignMiddlewareHelpers = builder.convertToPresignMiddlewareHelpers;
+ @Override
+ public void writeAdditionalFiles(
+ GoSettings settings,
+ Model model,
+ SymbolProvider symbolProvider,
+ GoDelegator goDelegator
+ ) {
+ ServiceShape serviceShape = settings.getService(model);
+ if (!PRESIGNER_MAP.containsKey(serviceShape.getId())) {
+ return;
+ }
- this.operation = SmithyBuilder.requiredState("operation", builder.operation);
- this.operationSymbol = symbolProvider.toSymbol(operation);
+ Set validOperations = PRESIGNER_MAP.get(serviceShape.getId());
+ if (validOperations.isEmpty()) {
+ return;
+ }
- this.operationInput = ProtocolUtils.expectInput(model, operation);
- this.operationInputSymbol = symbolProvider.toSymbol(operationInput);
+ // delegator for service shape
+ goDelegator.useShapeWriter(serviceShape, (writer) -> {
+ // generate presigner interface
+ writePresignInterface(writer, model, symbolProvider, serviceShape);
- this.presignClientSymbol = buildPresignClientSymbol(operationSymbol, exported);
- this.newPresignClientSymbol = buildNewPresignClientSymbol(operationSymbol, exported);
- }
+ // generate presign options and helpers per service
+ writePresignOptionType(writer, model, symbolProvider, serviceShape);
- /**
- * Writes the Presign client's type and methods.
- *
- * @param writer writer to write to
- */
- public void writePresignClientType(GoWriter writer) {
- writer.addUseImports(SmithyGoDependency.CONTEXT);
- writer.addUseImports(AwsGoDependency.AWS_SIGNER_V4);
+ // generate Presign client per service
+ writePresignClientType(writer, model, symbolProvider, serviceShape);
- writer.openBlock("type $T struct {", "}", presignClientSymbol, () -> {
- writer.write("client *Client");
- writer.write("presigner *v4.Signer");
+ // generate client helpers such as copyAPIClient, GetAPIClientOptions()
+ writePresignClientHelpers(writer, model, symbolProvider, serviceShape);
+
+ // generate convertToPresignMiddleware per service
+ writeConvertToPresignMiddleware(writer, model, symbolProvider, serviceShape);
});
- writer.openBlock("func $L(options Options, optFns ...func(*Options)) *$T {", "}",
- newPresignClientSymbol.getName(),
- presignClientSymbol,
- () -> {
- writer.openBlock("return &$T{", "}", presignClientSymbol, () -> {
- writer.write("client: New(options, optFns...),");
+ for (ShapeId operationId : serviceShape.getAllOperations()) {
+ OperationShape operationShape = model.expectShape(operationId, OperationShape.class);
+ if (!validOperations.contains(operationShape.getId())) {
+ continue;
+ }
- writer.addUseImports(AwsGoDependency.AWS_SIGNER_V4);
- writer.write("presigner: v4.NewSigner(),");
- });
- });
+ goDelegator.useShapeWriter(operationShape, (writer) -> {
+ // generate presign operation function for a client operation.
+ writePresignOperationFunction(writer, model, symbolProvider, serviceShape, operationShape);
- writer.addUseImports(SmithyGoDependency.NET_HTTP);
- writer.openBlock(
- // TODO presign with expire can be supported with a builder param that adds an additional expires param to presign signature.
+ // generate s3 unsigned payload middleware helper
+ writeS3AddAsUnsignedPayloadHelper(writer, model, symbolProvider, serviceShape, operationShape);
+ });
+ }
+ }
- // TODO Should this return a v4.PresignedHTTPRequest type instead of individual fields?
- "func (c *$T) Presign$T(ctx context.Context, params $P, optFns ...func(*Options)) "
- + "(string, http.Header, error) {",
- "}",
- presignClientSymbol, operationSymbol, operationInputSymbol,
+ private void writePresignOperationFunction(
+ GoWriter writer,
+ Model model,
+ SymbolProvider symbolProvider,
+ ServiceShape serviceShape,
+ OperationShape operationShape
+ ) {
+ Symbol operationSymbol = symbolProvider.toSymbol(operationShape);
+
+ Shape operationInputShape = model.expectShape(operationShape.getInput().get());
+ Symbol operationInputSymbol = symbolProvider.toSymbol(operationInputShape);
+
+ writer.writeDocs(
+ String.format(
+ "Presign%s is used to generate a presigned HTTP Request which contains presigned URL, signed headers "
+ + "and HTTP method used.", operationSymbol.getName())
+ );
+ writer.openBlock(
+ "func (c *$T) Presign$T(ctx context.Context, params $P, optFns ...func($P)) "
+ + "($P, error) {", "}", presignClientSymbol, operationSymbol,
+ operationInputSymbol, presignOptionsSymbol, v4PresignedHTTPRequestSymbol,
() -> {
Symbol nopClient = SymbolUtils.createPointableSymbolBuilder("NopClient",
SmithyGoDependency.SMITHY_HTTP_TRANSPORT)
.build();
- writer.write("if params == nil { params = &$T{} }", operationInputSymbol);
+ writer.write("if params == nil { params = &$T{} }", operationInputSymbol).insertTrailingNewline();
+
+ // process presignerOptions
+ processFunctionalOptions(writer, "optFns", "presignOptions", presignOptionsSymbol);
+
+ // check if presigner was set for presignerOptions
+ writer.openBlock("if len(optFns) != 0 {", "}", () -> {
+ writer.write("c = $L(c.client, optFns...)", NEW_CLIENT);
+ });
writer.write("");
- // TODO could be replaced with a `WithAPIOptions` client option helper.
- // TODO could be replaced with a `WithHTTPClient` client option helper.
- writer.openBlock("optFns = append(optFns, func(o *Options) {", "})", () -> {
+ writer.write("clientOptFns := make([]func(o *Options), 0)");
+ writer.openBlock("clientOptFns = append(clientOptFns, func(o *Options) {", "})", () -> {
writer.write("o.HTTPClient = &$T{}", nopClient);
});
writer.write("");
@@ -121,19 +250,80 @@ public void writePresignClientType(GoWriter writer) {
AwsCustomGoDependency.PRESIGNEDURL_CUSTOMIZATION).build();
writer.write("ctx = $T(ctx)", withIsPresigning);
- writer.openBlock("result, _, err := c.client.invokeOperation(ctx, $S, params, optFns,", ")",
+ writer.openBlock("result, _, err := c.client.invokeOperation(ctx, $S, params, clientOptFns,", ")",
operationSymbol.getName(), () -> {
writer.write("$L,", OperationGenerator
.getAddOperationMiddlewareFuncName(operationSymbol));
writer.write("c.$L,", CONVERT_TO_PRESIGN_MIDDLEWARE_NAME);
+
+ // s3 should add a middleware where it switches to using unisgned payload if
+ // input is a stream.
+ if (isS3ServiceShape(model, serviceShape)) {
+ if (operationInputShape.members().stream().anyMatch(memberShape -> {
+ return memberShape.getMemberTrait(model, StreamingTrait.class).isPresent();
+ })) {
+ writer.write("$L,", addAsUnsignedPayloadName(operationSymbol.getName()));
+ }
+ }
});
- writer.write("if err != nil { return ``, nil, err }");
+ writer.write("if err != nil { return nil, err }");
writer.write("");
- writer.write("out := result.(*v4.PresignedHTTPRequest)");
- writer.write("return out.URL, out.SignedHeader, nil");
+ writer.write("out := result.($P)", v4PresignedHTTPRequestSymbol);
+ writer.write("return out, nil");
});
+ writer.write("");
+ }
+
+ private void writeS3AddAsUnsignedPayloadHelper(
+ GoWriter writer,
+ Model model,
+ SymbolProvider symbolProvider,
+ ServiceShape serviceShape,
+ OperationShape operationShape
+ ) {
+ // if service is not s3, return
+ if (!isS3ServiceShape(model, serviceShape)) {
+ return;
+ }
+
+ Symbol operationSymbol = symbolProvider.toSymbol(operationShape);
+ Shape operationInputShape = model.expectShape(operationShape.getInput().get());
+
+ // return if not streaming
+ if (operationInputShape.members().stream().noneMatch(memberShape -> {
+ return memberShape.getMemberTrait(model, StreamingTrait.class).isPresent();
+ })) { return; }
+
+ writer.openBlock("func $L(stack $P, options Options) error {", "}",
+ addAsUnsignedPayloadName(operationSymbol.getName()),
+ SymbolUtils.createPointableSymbolBuilder("Stack", SmithyGoDependency.SMITHY_MIDDLEWARE).build(),
+ () -> {
+ writer.addUseImports(AwsGoDependency.AWS_SIGNER_V4);
+ writer.write("v4.RemoveContentSHA256HeaderMiddleware(stack)");
+ writer.write("v4.RemoveComputePayloadSHA256Middleware(stack)");
+
+ writer.write("return $T(stack)", SymbolUtils.createValueSymbolBuilder(
+ "AddUnsignedPayloadMiddleware", AwsGoDependency.AWS_SIGNER_V4).build());
+ });
+ writer.write("");
+ }
+
+ /**
+ * generates a helper to mutate request middleware stack in favor of generating a presign URL request
+ *
+ * @param writer the writer to write to
+ * @param model the service model
+ * @param symbolProvider the symbol provider
+ * @param serviceShape the service for which helper is generated
+ */
+ private void writeConvertToPresignMiddleware(
+ GoWriter writer,
+ Model model,
+ SymbolProvider symbolProvider,
+ ServiceShape serviceShape
+ ) {
Symbol smithyStack = SymbolUtils.createPointableSymbolBuilder("Stack", SmithyGoDependency.SMITHY_MIDDLEWARE)
.build();
@@ -166,138 +356,215 @@ public void writePresignClientType(GoWriter writer) {
presignMiddleware, smithyAfter);
writer.write("if err != nil { return err }");
- convertToPresignMiddlewareHelpers.forEach((symbol) -> {
- writer.write("err = $T(stack)", symbol);
+ // if protocol used is ec2query or query
+ if (serviceShape.hasTrait(AwsQueryTrait.ID) || serviceShape.hasTrait(Ec2QueryTrait.ID)) {
+ // presigned url should convert to Get request
+ Symbol queryAsGetMiddleware = SymbolUtils.createValueSymbolBuilder("AddAsGetRequestMiddleware",
+ AwsGoDependency.AWS_QUERY_PROTOCOL).build();
+
+ writer.writeDocs("convert request to a GET request");
+ writer.write("err = $T(stack)", queryAsGetMiddleware);
writer.write("if err != nil { return err }");
- });
+ }
+
+ // s3 service needs expires and sets unsignedPayload if input is stream
+ if (isS3ServiceShape(model, serviceShape)) {
+ Symbol expiresAsHeaderMiddleware = SymbolUtils.createValueSymbolBuilder(
+ "AddExpiresOnPresignedURL",
+ AwsCustomGoDependency.S3_CUSTOMIZATION).build();
+ writer.writeDocs("add middleware to set expiration for s3 presigned url, "
+ + " if expiration is set to 0, this middleware sets a default expiration of 900 seconds");
+ writer.write("err = stack.Build.Add(&$T{ Expires: c.expires, }, middleware.After)",
+ expiresAsHeaderMiddleware);
+ writer.write("if err != nil { return err }");
+ }
writer.write("return nil");
- });
+ }).insertTrailingNewline();
}
- public Symbol getPresignClientSymbol() {
- return presignClientSymbol;
- }
- public Symbol getNewPresignClientSymbol() {
- return newPresignClientSymbol;
- }
+ /**
+ * Writes the Presign client's type and methods.
+ *
+ * @param writer writer to write to
+ */
+ private void writePresignClientType(
+ GoWriter writer,
+ Model model,
+ SymbolProvider symbolProvider,
+ ServiceShape serviceShape
+ ) {
+ writer.addUseImports(SmithyGoDependency.CONTEXT);
+ writer.addUseImports(AwsGoDependency.AWS_SIGNER_V4);
- private static Symbol buildNewPresignClientSymbol(Symbol operation, boolean exported) {
- String name = String.format("New%sHTTPPresignURLClient", operation.getName());
- return buildSymbol(name, exported);
- }
+ writer.writeDocs(String.format("%s represents the presign url client", PRESIGN_CLIENT));
+ writer.openBlock("type $T struct {", "}", presignClientSymbol, () -> {
+ writer.write("client *Client");
+ writer.write("presigner $T", presignerInterfaceSymbol);
- private static Symbol buildPresignClientSymbol(Symbol operation, boolean exported) {
- String name = String.format("%sHTTPPresignURLClient", operation.getName());
- return buildSymbol(name, exported);
- }
+ if (isS3ServiceShape(model, serviceShape)) {
+ writer.addUseImports(SmithyGoDependency.TIME);
+ writer.write("expires time.Duration");
+ }
+ });
+ writer.write("");
+
+ // generate NewPresignClient
+ writer.writeDocs(
+ String.format("%s generates a presign client using provided API Client and presign options",
+ NEW_CLIENT)
+ );
+ writer.openBlock("func $L(c *Client, optFns ...func($P)) $P {", "}",
+ NEW_CLIENT, presignOptionsSymbol, presignClientSymbol, () -> {
+ processFunctionalOptions(writer, "optFns", "presignOptions", presignOptionsSymbol);
+ writer.write("client := copyAPIClient(c, presignOptions.ClientOptions...)");
+ writer.openBlock("if presignOptions.Presigner == nil {", "}", () -> {
+ writer.write("presignOptions.Presigner = $T()", v4NewPresignerSymbol);
+ });
- private static Symbol buildAPIClientSymbol(Symbol operation, boolean exported) {
- String name = String.format("%sAPIClient", operation.getName());
- return buildSymbol(name, exported);
+ writer.write("");
+ writer.openBlock("return &$L{", "}", presignClientSymbol, () -> {
+ writer.write("client: client,");
+ writer.write("presigner: presignOptions.Presigner,");
+ // if s3 assign expires value on client
+ if (isS3ServiceShape(model, serviceShape)) {
+ writer.write("expires: presignOptions.Expires,");
+ }
+ });
+ });
+ writer.write("");
}
- private static Symbol buildSymbol(String name, boolean exported) {
- if (!exported) {
- name = Character.toLowerCase(name.charAt(0)) + name.substring(1);
- }
- return SymbolUtils.createValueSymbolBuilder(name).
- build();
+ /**
+ * Writes the Presign client's helper methods.
+ *
+ * @param writer writer to write to
+ */
+ private void writePresignClientHelpers(
+ GoWriter writer,
+ Model model,
+ SymbolProvider symbolProvider,
+ ServiceShape serviceShape
+ ) {
+ // generate copy API client
+ final String COPY_API_CLIENT = "copyAPIClient";
+ writer.openBlock("func $L(c *Client, optFns ...func(*Options)) *Client {", "}",
+ COPY_API_CLIENT, () -> {
+ writer.write("return New(c.options, optFns...)");
+ writer.insertTrailingNewline();
+ });
+ writer.write("");
}
/**
- * Builder for the HTTP Presign URL client client generator.
+ * Writes the presigner interface used by the presign url client
*/
- public static class Builder implements SmithyBuilder {
- private Model model;
- private SymbolProvider symbolProvider;
- private OperationShape operation;
- private boolean exported;
- private List convertToPresignMiddlewareHelpers = new ArrayList<>();
-
- /**
- * Sets the model for the builder
- *
- * @param model API model
- * @return builder
- */
- public Builder model(Model model) {
- this.model = model;
- return this;
- }
+ public void writePresignInterface(
+ GoWriter writer,
+ Model model,
+ SymbolProvider symbolProvider,
+ ServiceShape serviceShape
+ ) {
+ writer.writeDocs(
+ String.format("%s represents presigner interface used by presign url client",
+ presignerInterfaceSymbol.getName())
+ );
+ writer.openBlock("type $T interface {", "}", presignerInterfaceSymbol, () -> {
+ writer.write("PresignHTTP(");
+ writer.write("ctx context.Context, credentials aws.Credentials, r *http.Request, ");
+ writer.write("payloadHash string, service string, region string, signingTime time.Time, ");
+ writer.write(") (url string, signedHeader http.Header, err error)");
+ });
- /**
- * Sets the symbol provider for the builder
- *
- * @param symbolProvider the symbol provider
- * @return buidler
- */
- public Builder symbolProvider(SymbolProvider symbolProvider) {
- this.symbolProvider = symbolProvider;
- return this;
- }
+ writer.write("");
+ }
- /**
- * Sets the operation for the builder
- *
- * @param operation api operation
- * @return builder
- */
- public Builder operation(OperationShape operation) {
- this.operation = operation;
- return this;
- }
+ /**
+ * Writes the Presign client's type and methods.
+ *
+ * @param writer writer to write to
+ */
+ public void writePresignOptionType(
+ GoWriter writer,
+ Model model,
+ SymbolProvider symbolProvider,
+ ServiceShape serviceShape
+ ) {
+ writer.addUseImports(SmithyGoDependency.CONTEXT);
+ Symbol presignOptionSymbol = buildSymbol(PRESIGN_OPTIONS, true);
+
+ // generate presign options
+ writer.writeDocs(String.format("%s represents the presign client options", PRESIGN_OPTIONS));
+ writer.openBlock("type $T struct {", "}", presignOptionSymbol, () -> {
+ writer.write("");
+ writer.writeDocs(
+ "ClientOptions are list of functional options to mutate client options used by presign client"
+ );
+ writer.write("ClientOptions []func(*Options)");
+
+ writer.write("");
+ writer.writeDocs("Presigner is the presigner used by the presign url client");
+ writer.write("Presigner $T", presignerInterfaceSymbol);
+
+ // s3 service has an additional Expires options
+ if (isS3ServiceShape(model, serviceShape)) {
+ writer.write("");
+ writer.writeDocs(
+ String.format("Expires sets the expiration duration for the generated presign url. This should "
+ + "be the duration in seconds the presigned URL should be considered valid for. If "
+ + "not set or set to zero, presign url would default to expire after 900 seconds."
+ )
+ );
+ writer.write("Expires time.Duration");
+ }
+ });
- /**
- * Sets that the generated client type should be exported, defaults to false.
- *
- * @return builder
- */
- public Builder exported() {
- return this.exported(true);
- }
+ // generate WithPresignClientFromClientOptions Helper
+ Symbol presignOptionsFromClientOptionsInternal = buildSymbol(PRESIGN_OPTIONS_FROM_CLIENT_OPTIONS, false);
+ writer.writeDocs(
+ String.format("%s is a helper utility to retrieve a function that takes PresignOption as input",
+ PRESIGN_OPTIONS_FROM_CLIENT_OPTIONS)
+ );
+ writer.openBlock("func $L(optFns ...func(*Options)) func($P) {", "}",
+ PRESIGN_OPTIONS_FROM_CLIENT_OPTIONS, presignOptionSymbol, () -> {
+ writer.write("return $L(optFns).options", presignOptionsFromClientOptionsInternal.getName());
+ });
- /**
- * Sets if the generate client type should be exported or not.
- *
- * @param exported if exported
- * @return builder
- */
- public Builder exported(boolean exported) {
- this.exported = exported;
- return this;
- }
+ writer.insertTrailingNewline();
+
+ writer.write("type $L []func(*Options)", presignOptionsFromClientOptionsInternal.getName());
+ writer.openBlock("func (w $L) options (o $P) {", "}",
+ presignOptionsFromClientOptionsInternal.getName(), presignOptionSymbol, () -> {
+ writer.write("o.ClientOptions = append(o.ClientOptions, w...)");
+ }).insertTrailingNewline();
+
+
+ // s3 specific helpers
+ if (isS3ServiceShape(model, serviceShape)) {
+ // generate WithPresignExpires Helper
+ Symbol presignOptionsFromExpiresInternal = buildSymbol(PRESIGN_OPTIONS_FROM_EXPIRES, false);
+ writer.writeDocs(String.format(
+ "%s is a helper utility to append Expires value on presign options optional function",
+ PRESIGN_OPTIONS_FROM_EXPIRES));
+ writer.openBlock("func $L(dur time.Duration) func($P) {", "}",
+ PRESIGN_OPTIONS_FROM_EXPIRES, presignOptionSymbol, () -> {
+ writer.write("return $L(dur).options", presignOptionsFromExpiresInternal.getName());
+ });
- /**
- * Sets additional middleware mutator that will be generated into the client's convert to presign URL operation.
- * Used by the client to convert a API operation to a presign URL.
- *
- * @param middlewareHelpers list of middleware helpers to set
- * @return builder
- */
- public Builder convertToPresignMiddlewareHelpers(List middlewareHelpers) {
- this.convertToPresignMiddlewareHelpers.clear();
- this.convertToPresignMiddlewareHelpers.addAll(middlewareHelpers);
- return this;
- }
+ writer.insertTrailingNewline();
- /**
- * Adds a single middleware mutator that will be generated into the client's convert to presign URL operation.
- * Used by the client to convert API operation to a presigned URL.
- *
- * @param middlewareHelper the middleware helper to add
- * @return builder
- */
- public Builder addConvertToPresignMiddlewareHelpers(Symbol middlewareHelper) {
- this.convertToPresignMiddlewareHelpers.add(middlewareHelper);
- return this;
+ writer.write("type $L time.Duration", presignOptionsFromExpiresInternal.getName());
+ writer.openBlock("func (w $L) options (o $P) {", "}",
+ presignOptionsFromExpiresInternal.getName(), presignOptionSymbol, () -> {
+ writer.write("o.Expires = time.Duration(w)");
+ }).insertTrailingNewline();
}
+ }
- // TODO presign with expire can be supported with a builder param that enables expires param behavior.
-
- public AwsHttpPresignURLClientGenerator build() {
- return new AwsHttpPresignURLClientGenerator(this);
- }
+ private final boolean isS3ServiceShape(Model model, ServiceShape service) {
+ String serviceId = service.expectTrait(ServiceTrait.class).getSdkId();
+ return serviceId.equalsIgnoreCase("S3");
}
}
+
diff --git a/codegen/smithy-aws-go-codegen/src/main/java/software/amazon/smithy/aws/go/codegen/customization/PresignURLAutoFill.java b/codegen/smithy-aws-go-codegen/src/main/java/software/amazon/smithy/aws/go/codegen/customization/PresignURLAutoFill.java
index ef93b8c3de5..6a606746556 100644
--- a/codegen/smithy-aws-go-codegen/src/main/java/software/amazon/smithy/aws/go/codegen/customization/PresignURLAutoFill.java
+++ b/codegen/smithy-aws-go-codegen/src/main/java/software/amazon/smithy/aws/go/codegen/customization/PresignURLAutoFill.java
@@ -24,8 +24,6 @@
import java.util.TreeMap;
import java.util.logging.Logger;
import software.amazon.smithy.aws.go.codegen.AwsGoDependency;
-import software.amazon.smithy.aws.go.codegen.AwsHttpPresignURLClientGenerator;
-import software.amazon.smithy.codegen.core.CodegenException;
import software.amazon.smithy.codegen.core.Symbol;
import software.amazon.smithy.codegen.core.SymbolProvider;
import software.amazon.smithy.go.codegen.CodegenUtils;
@@ -54,12 +52,17 @@
import software.amazon.smithy.utils.MapUtils;
import software.amazon.smithy.utils.SetUtils;
+/**
+ * PresignURLAutoFill represents a runtime plugin integration class
+ * used generate customization to autofill a presign url as
+ * an unexported serialized input member
+ */
public class PresignURLAutoFill implements GoIntegration {
- private static final Logger LOGGER = Logger.getLogger(PresignURLAutoFill.class.getName());
-
- private final List runtimeClientPlugins = new ArrayList<>();
-
- private static final Map> SERVICE_TO_OPERATION_MAP = MapUtils.of(
+ /**
+ * Map of service shape to Set of operation shapes that need to have this
+ * presigned url auto fill customization.
+ */
+ public static final Map> SERVICE_TO_OPERATION_MAP = MapUtils.of(
ShapeId.from("com.amazonaws.rds#AmazonRDSv19"), SetUtils.of(
ShapeId.from("com.amazonaws.rds#CopyDBSnapshot"),
ShapeId.from("com.amazonaws.rds#CreateDBInstanceReadReplica"),
@@ -71,6 +74,82 @@ public class PresignURLAutoFill implements GoIntegration {
// TODO other services
);
+ private static final Logger LOGGER = Logger.getLogger(PresignURLAutoFill.class.getName());
+ private final List runtimeClientPlugins = new ArrayList<>();
+
+ private static void writeMemberSetter(
+ GoWriter writer,
+ SymbolProvider symbolprovider,
+ OperationShape operation,
+ StructureShape input,
+ MemberShape member
+ ) {
+ Symbol operationSymbol = symbolprovider.toSymbol(operation);
+ Symbol inputSymbol = symbolprovider.toSymbol(input);
+ String memberName = symbolprovider.toMemberName(member);
+
+ writer.openBlock("func $L(params interface{}, value string) error {", "}",
+ setterFuncName(operationSymbol.getName(), memberName),
+ () -> {
+ writer.addUseImports(SmithyGoDependency.FMT);
+ writer.write("input, ok := params.($P)", inputSymbol);
+ writer.openBlock("if !ok {", "}", () -> {
+ writer.write("return fmt.Errorf(\"expect $P type, got %T\", params)", inputSymbol);
+ });
+ writer.write("input.$L = &value", memberName);
+ writer.write("return nil");
+ });
+ }
+
+ private static void writeMemberGetter(
+ GoWriter writer,
+ SymbolProvider symbolprovider,
+ OperationShape operation,
+ StructureShape input,
+ MemberShape member
+ ) {
+ Symbol operationSymbol = symbolprovider.toSymbol(operation);
+ Symbol inputSymbol = symbolprovider.toSymbol(input);
+ String memberName = symbolprovider.toMemberName(member);
+
+ writer.openBlock("func $L(params interface{}) (string, bool, error) {", "}",
+ getterFuncName(operationSymbol.getName(), memberName),
+ () -> {
+ writer.addUseImports(SmithyGoDependency.FMT);
+ writer.write("input, ok := params.($P)", inputSymbol);
+ writer.openBlock("if !ok {", "}", () -> {
+ writer.write("return ``, false, fmt.Errorf(\"expect $P type, got %T\", params)", inputSymbol);
+ });
+ writer.openBlock("if input.$L == nil || len(*input.$L) == 0 {", "}", memberName, memberName,
+ () -> writer.write("return ``, false, nil")
+ );
+ writer.write("return *input.$L, true, nil", memberName);
+ });
+ }
+
+ private static String addPresignMiddlewareFuncName(String operationName) {
+ return String.format("add%sPresignURLMiddleware", operationName);
+ }
+
+ private static String getterFuncName(String operationName, String memberName) {
+ return String.format("get%s%s", operationName, memberName);
+ }
+
+ private static String setterFuncName(String operationName, String memberName) {
+ return String.format("set%s%s", operationName, memberName);
+ }
+
+ private static String copyInputFuncName(String inputName) {
+ return String.format("copy%sForPresign", inputName);
+ }
+
+ private static String presignFuncName(String operationName) {
+ return String.format("Presign%s", operationName);
+ }
+
+ private static String autofillPresignClient(String operationName) {
+ return String.format("presignAutoFill%sClient", operationName);
+ }
/**
* Updates the API model to add additional members to the operation input shape that are needed for presign url
@@ -164,6 +243,9 @@ public void writeAdditionalFiles(
// Generate the presign client
writePresignClientCustomization(writer, settings, model, symbolProvider, operation, input);
+
+ // Generate the autofill presign client and its PresignURL method
+ writeAutofillPresignClient(writer, symbolProvider, operation, input);
});
goDelegator.useShapeTestWriter(operation, (writer) -> {
@@ -250,56 +332,6 @@ private void writeMemberAccessor(
}
}
- private static void writeMemberSetter(
- GoWriter writer,
- SymbolProvider symbolprovider,
- OperationShape operation,
- StructureShape input,
- MemberShape member
- ) {
- Symbol operationSymbol = symbolprovider.toSymbol(operation);
- Symbol inputSymbol = symbolprovider.toSymbol(input);
- String memberName = symbolprovider.toMemberName(member);
-
- writer.openBlock("func $L(params interface{}, value string) error {", "}",
- setterFuncName(operationSymbol.getName(), memberName),
- () -> {
- writer.addUseImports(SmithyGoDependency.FMT);
- writer.write("input, ok := params.($P)", inputSymbol);
- writer.openBlock("if !ok {", "}", () -> {
- writer.write("return fmt.Errorf(\"expect $P type, got %T\", params)", inputSymbol);
- });
- writer.write("input.$L = &value", memberName);
- writer.write("return nil");
- });
- }
-
- private static void writeMemberGetter(
- GoWriter writer,
- SymbolProvider symbolprovider,
- OperationShape operation,
- StructureShape input,
- MemberShape member
- ) {
- Symbol operationSymbol = symbolprovider.toSymbol(operation);
- Symbol inputSymbol = symbolprovider.toSymbol(input);
- String memberName = symbolprovider.toMemberName(member);
-
- writer.openBlock("func $L(params interface{}) (string, bool, error) {", "}",
- getterFuncName(operationSymbol.getName(), memberName),
- () -> {
- writer.addUseImports(SmithyGoDependency.FMT);
- writer.write("input, ok := params.($P)", inputSymbol);
- writer.openBlock("if !ok {", "}", () -> {
- writer.write("return ``, false, fmt.Errorf(\"expect $P type, got %T\", params)", inputSymbol);
- });
- writer.openBlock("if input.$L == nil || len(*input.$L) == 0 {", "}", memberName, memberName,
- () -> writer.write("return ``, false, nil")
- );
- writer.write("return *input.$L, true, nil", memberName);
- });
- }
-
private void writePresignClientCustomization(
GoWriter writer,
GoSettings settings,
@@ -308,29 +340,6 @@ private void writePresignClientCustomization(
OperationShape operation,
StructureShape input
) {
- AwsHttpPresignURLClientGenerator.Builder clientGenBuilder = new AwsHttpPresignURLClientGenerator.Builder()
- .model(model)
- .symbolProvider(symbolProvider)
- .operation(operation);
-
- switch (settings.getProtocol().toString()) {
- case "aws.protocols#awsQuery":
- case "aws.protocols#ec2Query":
- Symbol queryAsGetMiddleware = SymbolUtils.createValueSymbolBuilder("AddAsGetRequestMiddleware",
- AwsGoDependency.AWS_QUERY_PROTOCOL)
- .build();
- clientGenBuilder.addConvertToPresignMiddlewareHelpers(queryAsGetMiddleware);
- break;
-
- default:
- LOGGER.warning("presign url customization does not know how to handle service "
- + settings.getService() + " using protocol "+ settings.getProtocol());
- }
-
- AwsHttpPresignURLClientGenerator gen = clientGenBuilder.build();
-
- gen.writePresignClientType(writer);
-
Symbol smithyStack = SymbolUtils.createPointableSymbolBuilder("Stack",
SmithyGoDependency.SMITHY_MIDDLEWARE).build();
Symbol operationSymbol = symbolProvider.toSymbol(operation);
@@ -347,8 +356,7 @@ private void writePresignClientCustomization(
AwsCustomGoDependency.PRESIGNEDURL_CUSTOMIZATION).build();
Symbol addMiddleware = SymbolUtils.createValueSymbolBuilder("AddMiddleware",
AwsCustomGoDependency.PRESIGNEDURL_CUSTOMIZATION).build();
- Symbol removeMiddleware = SymbolUtils.createValueSymbolBuilder("RemoveMiddleware",
- AwsCustomGoDependency.PRESIGNEDURL_CUSTOMIZATION).build();
+
// generate middleware mutator to wire up presign client with accessors and custom middleware.
writer.openBlock("func $L(stack $P, options Options) error {", "}",
@@ -357,49 +365,69 @@ private void writePresignClientCustomization(
() -> {
writer.openBlock("return $T(stack, $T{", "})", addMiddleware, addMiddlewareOptions, () -> {
writer.openBlock("Accessor: $T{", "},", parameterAccessor, () -> {
- writer.write("GetPresignedURL: $L,",
+ writer.write("GetPresignedURL: $L, \n",
getterFuncName(operationSymbol.getName(), presignURLMember));
- writer.write("GetSourceRegion: $L,",
+ writer.write("GetSourceRegion: $L, \n",
getterFuncName(operationSymbol.getName(), srcRegionMember));
- writer.write("CopyInput: $L,", copyInputFuncName(inputSymbol.getName()));
- writer.write("SetDestinationRegion: $L,",
+ writer.write("CopyInput: $L, \n", copyInputFuncName(inputSymbol.getName()));
+ writer.write("SetDestinationRegion: $L,\n",
setterFuncName(operationSymbol.getName(), dstRegionMember));
- writer.write("SetPresignedURL: $L,",
+ writer.write("SetPresignedURL: $L, \n",
setterFuncName(operationSymbol.getName(), presignURLMember));
});
- // Replace with type wrapping presigner for generic signature
- writer.write("Presigner: &$L{client: $T(options)},",
- opPresignClientWrapperName(operationSymbol.getName()),
- gen.getNewPresignClientSymbol());
+ writer.write("Presigner: &$L{ client: NewPresignClient(New(options))}, \n",
+ autofillPresignClient(operationSymbol.getName()));
});
});
+ }
+
+
+ private void writeAutofillPresignClient(
+ GoWriter writer,
+ SymbolProvider symbolprovider,
+ OperationShape operation,
+ StructureShape input
+ ) {
+ Symbol operationSymbol = symbolprovider.toSymbol(operation);
+ Symbol inputSymbol = symbolprovider.toSymbol(input);
+ Symbol removeMiddleware = SymbolUtils.createValueSymbolBuilder("RemoveMiddleware",
+ AwsCustomGoDependency.PRESIGNEDURL_CUSTOMIZATION).build();
- // Generate generic presign wrapper type for passing region in with op call.
+ // generate autofill presign client
writer.openBlock("type $L struct {", "}",
- opPresignClientWrapperName(operationSymbol.getName()),
- () -> {
- writer.write("client *$T", gen.getPresignClientSymbol());
+ autofillPresignClient(operationSymbol.getName()), () -> {
+ writer.write("client *PresignClient");
});
- writer.addUseImports(SmithyGoDependency.NET_HTTP);
- writer.openBlock(
- // TODO consider creating type for presign parameters for future compatibility.
- "func (c *$L) PresignURL(ctx context.Context, region string, params interface{}) "
- + "(string, http.Header, error) {", "}",
- opPresignClientWrapperName(operationSymbol.getName()),
- () -> {
+ writer.write("");
+
+ // generate PresignURL method that satisfies URLPresigner interface of middleware
+ writer.writeDocs("PresignURL is a middleware accessor that satisfies URLPresigner interface.");
+ writer.openBlock("func (c *$L) PresignURL(ctx context.Context, srcRegion string, params interface{}) "
+ + "(*v4.PresignedHTTPRequest, error) {", "}",
+
+ autofillPresignClient(operationSymbol.getName()), () -> {
+ writer.addUseImports(SmithyGoDependency.FMT);
+ // check input
writer.write("input, ok := params.($P)", inputSymbol);
writer.openBlock("if !ok {", "}", () -> {
- writer.write("return ``, nil, fmt.Errorf(\"expect $P type, got %T\", params)", inputSymbol);
+ writer.write("return nil, fmt.Errorf(\"expect $P type, got %T\", params)", inputSymbol);
});
- // TODO could be replaced with a `WithRegion` client option helper.
+ // generate client options
writer.openBlock("optFn := func(o *Options) {", "}", () -> {
- writer.write("o.Region = region");
+ writer.write("o.Region = srcRegion");
writer.write("o.APIOptions = append(o.APIOptions, $T)", removeMiddleware);
});
- writer.write("return c.client.Presign$L(ctx, input, optFn)", operationSymbol.getName());
+
+ // getPresignAPIOptions
+ writer.write("presignOptFn := WithPresignClientFromClientOptions(optFn)");
+
+ // call the exported function
+ writer.write("return c.client.$L(ctx, input, presignOptFn)",
+ presignFuncName(operationSymbol.getName()));
});
+ writer.write("");
}
private void writePresignClientCustomizationTest(
@@ -440,23 +468,4 @@ private void writePresignClientCustomizationTest(
writer.write(template);
}
- private static String opPresignClientWrapperName(String operationName) {
- return String.format("presignAutoFill%sClient", operationName);
- }
-
- private static String addPresignMiddlewareFuncName(String operationName) {
- return String.format("add%sPresignURLMiddleware", operationName);
- }
-
- private static String getterFuncName(String operationName, String memberName) {
- return String.format("get%s%s", operationName, memberName);
- }
-
- private static String setterFuncName(String operationName, String memberName) {
- return String.format("set%s%s", operationName, memberName);
- }
-
- private static String copyInputFuncName(String inputName) {
- return String.format("copy%sForPresign", inputName);
- }
}
diff --git a/codegen/smithy-aws-go-codegen/src/main/resources/META-INF/services/software.amazon.smithy.go.codegen.integration.GoIntegration b/codegen/smithy-aws-go-codegen/src/main/resources/META-INF/services/software.amazon.smithy.go.codegen.integration.GoIntegration
index a45b33d3479..7e1dfee480e 100644
--- a/codegen/smithy-aws-go-codegen/src/main/resources/META-INF/services/software.amazon.smithy.go.codegen.integration.GoIntegration
+++ b/codegen/smithy-aws-go-codegen/src/main/resources/META-INF/services/software.amazon.smithy.go.codegen.integration.GoIntegration
@@ -29,3 +29,4 @@ software.amazon.smithy.aws.go.codegen.customization.PresignURLAutoFill
software.amazon.smithy.aws.go.codegen.FilterStreamingOperations
software.amazon.smithy.aws.go.codegen.RequestResponseLogging
software.amazon.smithy.aws.go.codegen.customization.S3ControlEndpointResolver
+software.amazon.smithy.aws.go.codegen.AwsHttpPresignURLClientGenerator
diff --git a/example/service/s3/listObjects/go.mod b/example/service/s3/listObjects/go.mod
index 3bad0eece6d..e9f2a4f3273 100644
--- a/example/service/s3/listObjects/go.mod
+++ b/example/service/s3/listObjects/go.mod
@@ -23,3 +23,5 @@ replace github.com/aws/aws-sdk-go-v2/service/sts => ../../../../service/sts/
replace github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding => ../../../../service/internal/accept-encoding/
replace github.com/aws/aws-sdk-go-v2/service/internal/s3shared => ../../../../service/internal/s3shared/
+
+replace github.com/aws/aws-sdk-go-v2/service/internal/presigned-url => ../../../../service/internal/presigned-url/
diff --git a/example/service/s3/listObjects/go.sum b/example/service/s3/listObjects/go.sum
index 57ffc83dc1c..e1dc3ec43bf 100644
--- a/example/service/s3/listObjects/go.sum
+++ b/example/service/s3/listObjects/go.sum
@@ -5,6 +5,8 @@ github.com/awslabs/smithy-go v0.3.1-0.20201108010311-62c2a93810b4/go.mod h1:hPOQ
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/google/go-cmp v0.4.1 h1:/exdXoGamhu5ONeUJH0deniYLWYvQwW66yvlfiiKTu0=
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
+github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
diff --git a/feature/s3/manager/go.mod b/feature/s3/manager/go.mod
index 45c728cce68..db589b6bbca 100644
--- a/feature/s3/manager/go.mod
+++ b/feature/s3/manager/go.mod
@@ -8,7 +8,7 @@ require (
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v0.3.1 // indirect
github.com/aws/aws-sdk-go-v2/service/s3 v0.29.0
github.com/awslabs/smithy-go v0.3.1-0.20201108010311-62c2a93810b4
- github.com/google/go-cmp v0.4.1
+ github.com/google/go-cmp v0.5.2
)
replace github.com/aws/aws-sdk-go-v2 => ../../../
@@ -26,3 +26,5 @@ replace github.com/aws/aws-sdk-go-v2/service/sts => ../../../service/sts/
replace github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding => ../../../service/internal/accept-encoding/
replace github.com/aws/aws-sdk-go-v2/service/internal/s3shared => ../../../service/internal/s3shared/
+
+replace github.com/aws/aws-sdk-go-v2/service/internal/presigned-url => ../../../service/internal/presigned-url/
diff --git a/feature/s3/manager/go.sum b/feature/s3/manager/go.sum
index 2f07ad94cff..75ff214e0e0 100644
--- a/feature/s3/manager/go.sum
+++ b/feature/s3/manager/go.sum
@@ -6,6 +6,8 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/google/go-cmp v0.4.1 h1:/exdXoGamhu5ONeUJH0deniYLWYvQwW66yvlfiiKTu0=
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
+github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
diff --git a/service/ec2/api_client.go b/service/ec2/api_client.go
index 5b0ed68bc6a..3b4c1d0b7be 100644
--- a/service/ec2/api_client.go
+++ b/service/ec2/api_client.go
@@ -7,6 +7,7 @@ import (
cryptorand "crypto/rand"
"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"
@@ -246,3 +247,78 @@ func addRequestResponseLogging(stack *middleware.Stack, o Options) error {
LogResponseWithBody: o.ClientLogMode.IsResponseWithBody(),
}, middleware.After)
}
+
+// HTTPPresignerV4 represents presigner interface used by presign url client
+type HTTPPresignerV4 interface {
+ PresignHTTP(
+ ctx context.Context, credentials aws.Credentials, r *http.Request,
+ payloadHash string, service string, region string, signingTime time.Time,
+ ) (url string, signedHeader http.Header, err error)
+}
+
+// PresignOptions represents the presign client options
+type PresignOptions struct {
+
+ // ClientOptions are list of functional options to mutate client options used by
+ // presign client
+ ClientOptions []func(*Options)
+
+ // Presigner is the presigner used by the presign url client
+ Presigner HTTPPresignerV4
+}
+
+// WithPresignClientFromClientOptions is a helper utility to retrieve a function
+// that takes PresignOption as input
+func WithPresignClientFromClientOptions(optFns ...func(*Options)) func(*PresignOptions) {
+ return withPresignClientFromClientOptions(optFns).options
+}
+
+type withPresignClientFromClientOptions []func(*Options)
+
+func (w withPresignClientFromClientOptions) options(o *PresignOptions) {
+ o.ClientOptions = append(o.ClientOptions, w...)
+}
+
+// PresignClient represents the presign url client
+type PresignClient struct {
+ client *Client
+ presigner HTTPPresignerV4
+}
+
+// NewPresignClient generates a presign client using provided API Client and
+// presign options
+func NewPresignClient(c *Client, optFns ...func(*PresignOptions)) *PresignClient {
+ var presignOptions PresignOptions
+ for _, fn := range optFns {
+ fn(&presignOptions)
+ }
+ client := copyAPIClient(c, presignOptions.ClientOptions...)
+ if presignOptions.Presigner == nil {
+ presignOptions.Presigner = v4.NewSigner()
+ }
+
+ return &PresignClient{
+ client: client,
+ presigner: presignOptions.Presigner,
+ }
+}
+
+func copyAPIClient(c *Client, optFns ...func(*Options)) *Client {
+ return New(c.options, optFns...)
+}
+
+func (c *PresignClient) convertToPresignMiddleware(stack *middleware.Stack, options Options) (err error) {
+ stack.Finalize.Clear()
+ stack.Deserialize.Clear()
+ stack.Build.Remove((*awsmiddleware.ClientRequestID)(nil).ID())
+ err = stack.Finalize.Add(v4.NewPresignHTTPRequestMiddleware(options.Credentials, c.presigner), middleware.After)
+ if err != nil {
+ return err
+ }
+ // convert request to a GET request
+ err = query.AddAsGetRequestMiddleware(stack)
+ if err != nil {
+ return err
+ }
+ return nil
+}
diff --git a/service/ec2/api_op_CopySnapshot.go b/service/ec2/api_op_CopySnapshot.go
index 9426ea2a80a..dec72096519 100644
--- a/service/ec2/api_op_CopySnapshot.go
+++ b/service/ec2/api_op_CopySnapshot.go
@@ -6,13 +6,11 @@ import (
"context"
"fmt"
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"
"github.com/aws/aws-sdk-go-v2/service/ec2/types"
presignedurlcust "github.com/aws/aws-sdk-go-v2/service/internal/presigned-url"
"github.com/awslabs/smithy-go/middleware"
smithyhttp "github.com/awslabs/smithy-go/transport/http"
- "net/http"
)
// Copies a point-in-time snapshot of an EBS volume and stores it in Amazon S3. You
@@ -240,80 +238,39 @@ func setCopySnapshotdestinationRegion(params interface{}, value string) error {
input.destinationRegion = &value
return nil
}
-
-type copySnapshotHTTPPresignURLClient struct {
- client *Client
- presigner *v4.Signer
-}
-
-func newCopySnapshotHTTPPresignURLClient(options Options, optFns ...func(*Options)) *copySnapshotHTTPPresignURLClient {
- return ©SnapshotHTTPPresignURLClient{
- client: New(options, optFns...),
- presigner: v4.NewSigner(),
- }
-}
-func (c *copySnapshotHTTPPresignURLClient) PresignCopySnapshot(ctx context.Context, params *CopySnapshotInput, optFns ...func(*Options)) (string, http.Header, error) {
- if params == nil {
- params = &CopySnapshotInput{}
- }
-
- optFns = append(optFns, func(o *Options) {
- o.HTTPClient = &smithyhttp.NopClient{}
- })
-
- ctx = presignedurlcust.WithIsPresigning(ctx)
- result, _, err := c.client.invokeOperation(ctx, "CopySnapshot", params, optFns,
- addOperationCopySnapshotMiddlewares,
- c.convertToPresignMiddleware,
- )
- if err != nil {
- return ``, nil, err
- }
-
- out := result.(*v4.PresignedHTTPRequest)
- return out.URL, out.SignedHeader, nil
-}
-func (c *copySnapshotHTTPPresignURLClient) convertToPresignMiddleware(stack *middleware.Stack, options Options) (err error) {
- stack.Finalize.Clear()
- stack.Deserialize.Clear()
- stack.Build.Remove((*awsmiddleware.ClientRequestID)(nil).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
-}
func addCopySnapshotPresignURLMiddleware(stack *middleware.Stack, options Options) error {
return presignedurlcust.AddMiddleware(stack, presignedurlcust.Options{
Accessor: presignedurlcust.ParameterAccessor{
- GetPresignedURL: getCopySnapshotPresignedUrl,
- GetSourceRegion: getCopySnapshotSourceRegion,
- CopyInput: copyCopySnapshotInputForPresign,
+ GetPresignedURL: getCopySnapshotPresignedUrl,
+
+ GetSourceRegion: getCopySnapshotSourceRegion,
+
+ CopyInput: copyCopySnapshotInputForPresign,
+
SetDestinationRegion: setCopySnapshotdestinationRegion,
- SetPresignedURL: setCopySnapshotPresignedUrl,
+
+ SetPresignedURL: setCopySnapshotPresignedUrl,
},
- Presigner: &presignAutoFillCopySnapshotClient{client: newCopySnapshotHTTPPresignURLClient(options)},
+ Presigner: &presignAutoFillCopySnapshotClient{client: NewPresignClient(New(options))},
})
}
type presignAutoFillCopySnapshotClient struct {
- client *copySnapshotHTTPPresignURLClient
+ client *PresignClient
}
-func (c *presignAutoFillCopySnapshotClient) PresignURL(ctx context.Context, region string, params interface{}) (string, http.Header, error) {
+// PresignURL is a middleware accessor that satisfies URLPresigner interface.
+func (c *presignAutoFillCopySnapshotClient) PresignURL(ctx context.Context, srcRegion string, params interface{}) (*v4.PresignedHTTPRequest, error) {
input, ok := params.(*CopySnapshotInput)
if !ok {
- return ``, nil, fmt.Errorf("expect *CopySnapshotInput type, got %T", params)
+ return nil, fmt.Errorf("expect *CopySnapshotInput type, got %T", params)
}
optFn := func(o *Options) {
- o.Region = region
+ o.Region = srcRegion
o.APIOptions = append(o.APIOptions, presignedurlcust.RemoveMiddleware)
}
- return c.client.PresignCopySnapshot(ctx, input, optFn)
+ presignOptFn := WithPresignClientFromClientOptions(optFn)
+ return c.client.PresignCopySnapshot(ctx, input, presignOptFn)
}
func newServiceMetadataMiddleware_opCopySnapshot(region string) *awsmiddleware.RegisterServiceMetadata {
@@ -324,3 +281,35 @@ func newServiceMetadataMiddleware_opCopySnapshot(region string) *awsmiddleware.R
OperationName: "CopySnapshot",
}
}
+
+// PresignCopySnapshot is used to generate a presigned HTTP Request which contains
+// presigned URL, signed headers and HTTP method used.
+func (c *PresignClient) PresignCopySnapshot(ctx context.Context, params *CopySnapshotInput, optFns ...func(*PresignOptions)) (*v4.PresignedHTTPRequest, error) {
+ if params == nil {
+ params = &CopySnapshotInput{}
+ }
+ var presignOptions PresignOptions
+ for _, fn := range optFns {
+ fn(&presignOptions)
+ }
+ if len(optFns) != 0 {
+ c = NewPresignClient(c.client, optFns...)
+ }
+
+ clientOptFns := make([]func(o *Options), 0)
+ clientOptFns = append(clientOptFns, func(o *Options) {
+ o.HTTPClient = &smithyhttp.NopClient{}
+ })
+
+ ctx = presignedurlcust.WithIsPresigning(ctx)
+ result, _, err := c.client.invokeOperation(ctx, "CopySnapshot", params, clientOptFns,
+ addOperationCopySnapshotMiddlewares,
+ c.convertToPresignMiddleware,
+ )
+ if err != nil {
+ return nil, err
+ }
+
+ out := result.(*v4.PresignedHTTPRequest)
+ return out, nil
+}
diff --git a/service/internal/integrationtest/go.mod b/service/internal/integrationtest/go.mod
index eac1fcefbfb..3b835ba95d0 100644
--- a/service/internal/integrationtest/go.mod
+++ b/service/internal/integrationtest/go.mod
@@ -87,6 +87,7 @@ require (
github.com/aws/aws-sdk-go-v2/service/wafv2 v0.29.0
github.com/aws/aws-sdk-go-v2/service/workspaces v0.29.0
github.com/awslabs/smithy-go v0.3.1-0.20201120010914-c02b9493fe20
+ github.com/google/go-cmp v0.5.2
)
go 1.15
diff --git a/service/internal/integrationtest/s3/presign_test.go b/service/internal/integrationtest/s3/presign_test.go
new file mode 100644
index 00000000000..027329cd383
--- /dev/null
+++ b/service/internal/integrationtest/s3/presign_test.go
@@ -0,0 +1,156 @@
+// +build integration
+
+package s3
+
+import (
+ "bytes"
+ "context"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "net/http"
+ "strconv"
+ "testing"
+ "time"
+
+ v4 "github.com/aws/aws-sdk-go-v2/aws/signer/v4"
+ "github.com/aws/aws-sdk-go-v2/service/internal/integrationtest"
+ "github.com/aws/aws-sdk-go-v2/service/s3"
+ "github.com/google/go-cmp/cmp"
+)
+
+func TestInteg_PresignURL(t *testing.T) {
+ cases := map[string]struct {
+ body io.Reader
+ expires time.Duration
+ sha256Header string
+ expectedSignedHeader http.Header
+ }{
+ "standard": {
+ body: bytes.NewReader([]byte("Hello-world")),
+ expectedSignedHeader: http.Header{
+ "content-type": {"application/octet-stream"},
+ "content-length": {"11"},
+ },
+ },
+ "nil-body": {
+ expectedSignedHeader: http.Header{},
+ },
+ "empty-body": {
+ body: bytes.NewReader([]byte("")),
+ expectedSignedHeader: http.Header{
+ "content-type": {"application/octet-stream"},
+ },
+ },
+ }
+
+ for name, c := range cases {
+ t.Run(name, func(t *testing.T) {
+ key := integrationtest.UniqueID()
+
+ ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second)
+ defer cancelFn()
+
+ cfg, err := integrationtest.LoadConfigWithDefaultRegion("us-west-2")
+ if err != nil {
+ t.Fatalf("failed to load config, %v", err)
+ }
+
+ client := s3.NewFromConfig(cfg)
+
+ // construct a put object
+ putObjectInput := &s3.PutObjectInput{
+ Bucket: &setupMetadata.Buckets.Source.Name,
+ Key: &key,
+ Body: c.body,
+ }
+
+ presignerClient := s3.NewPresignClient(client, func(options *s3.PresignOptions) {
+ options.Expires = 600 * time.Second
+ })
+
+ presignRequest, err := presignerClient.PresignPutObject(ctx, putObjectInput)
+ if err != nil {
+ t.Fatalf("expect no error, got %v", err)
+ }
+
+ for k, v := range c.expectedSignedHeader {
+ value := presignRequest.SignedHeader[k]
+ if len(value) == 0 {
+ t.Fatalf("expected %v header to be present in presigned url, got %v", k, presignRequest.SignedHeader)
+ }
+
+ if diff := cmp.Diff(v, value); len(diff) != 0 {
+ t.Fatalf("expected %v header value to be %v got %v", k, v, value)
+ }
+ }
+
+ resp, err := sendHTTPRequest(presignRequest, putObjectInput.Body)
+ if err != nil {
+ t.Errorf("expect no error while sending HTTP request using presigned url, got %v", err)
+ }
+
+ defer resp.Body.Close()
+
+ if resp.StatusCode != http.StatusOK {
+ t.Fatalf("failed to put S3 object, %d:%s", resp.StatusCode, resp.Status)
+ }
+
+ // construct a get object
+ getObjectInput := &s3.GetObjectInput{
+ Bucket: &setupMetadata.Buckets.Source.Name,
+ Key: &key,
+ }
+
+ presignRequest, err = presignerClient.PresignGetObject(ctx, getObjectInput)
+ if err != nil {
+ t.Errorf("expect no error, got %v", err)
+ }
+
+ resp, err = sendHTTPRequest(presignRequest, nil)
+ if err != nil {
+ t.Errorf("expect no error while sending HTTP request using presigned url, got %v", err)
+ }
+
+ defer resp.Body.Close()
+
+ if resp.StatusCode != http.StatusOK {
+ t.Fatalf("failed to get S3 object, %d:%s", resp.StatusCode, resp.Status)
+ }
+ })
+ }
+}
+
+func sendHTTPRequest(presignRequest *v4.PresignedHTTPRequest, body io.Reader) (*http.Response, error) {
+ // create a http request
+ req, err := http.NewRequest(presignRequest.Method, presignRequest.URL, nil)
+ if err != nil {
+ return nil, fmt.Errorf("failed to build presigned request, %v", err)
+ }
+
+ // assign the signed headers onto the http request
+ for k, vs := range presignRequest.SignedHeader {
+ for _, v := range vs {
+ req.Header.Add(k, v)
+ }
+ }
+
+ // Need to ensure that the content length member is set of the HTTP Request
+ // or the request will NOT be transmitted correctly with a content length
+ // value across the wire.
+ if contLen := req.Header.Get("Content-Length"); len(contLen) > 0 {
+ req.ContentLength, _ = strconv.ParseInt(contLen, 10, 64)
+ }
+
+ // assign the request body if not nil
+ if body != nil {
+ req.Body = ioutil.NopCloser(body)
+ if req.ContentLength == 0 {
+ req.Body = nil
+ }
+ }
+
+ // Upload the object to S3.
+ resp, err := http.DefaultClient.Do(req)
+ return resp, err
+}
diff --git a/service/internal/presigned-url/middleware.go b/service/internal/presigned-url/middleware.go
index 8caf0219600..f68be9ef643 100644
--- a/service/internal/presigned-url/middleware.go
+++ b/service/internal/presigned-url/middleware.go
@@ -3,34 +3,45 @@ 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"
)
+// URLPresigner provides the interface to presign the input parameters in to a
+// presigned URL.
+type URLPresigner interface {
+ // PresignURL presigns a URL.
+ PresignURL(ctx context.Context, srcRegion string, params interface{}) (*v4.PresignedHTTPRequest, error)
+}
+
// ParameterAccessor provides an collection of accessor to for retrieving and
// setting the values needed to PresignedURL generation
type ParameterAccessor struct {
+ // GetPresignedURL accessor points to a function that retrieves a presigned url if present
GetPresignedURL func(interface{}) (string, bool, error)
+
+ // GetSourceRegion accessor points to a function that retrieves source region for presigned url
GetSourceRegion func(interface{}) (string, bool, error)
- CopyInput func(interface{}) (interface{}, error)
+ // CopyInput accessor points to a function that takes in an input, and returns a copy.
+ CopyInput func(interface{}) (interface{}, error)
+
+ // SetDestinationRegion accessor points to a function that sets destination region on api input struct
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,
- )
+ // SetPresignedURL accessor points to a function that sets presigned url on api input struct
+ SetPresignedURL func(interface{}, string) error
}
// Options provides the set of options needed by the presigned URL middleware.
type Options struct {
- Accessor ParameterAccessor
+ // Accessor are the parameter accessors used by this middleware
+ Accessor ParameterAccessor
+
+ // Presigner is the URLPresigner used by the middleware
Presigner URLPresigner
}
@@ -85,13 +96,13 @@ func (m *presign) HandleInitialize(
return out, metadata, fmt.Errorf("presign middleware failed, %w", err)
}
- presignedURL, _, err := m.options.Presigner.PresignURL(ctx, srcRegion, paramCpy)
+ presignedReq, err := m.options.Presigner.PresignURL(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/internal/presigned-url/middleware_test.go b/service/internal/presigned-url/middleware_test.go
index a81a5b1d83b..04c86a6a0b8 100644
--- a/service/internal/presigned-url/middleware_test.go
+++ b/service/internal/presigned-url/middleware_test.go
@@ -7,6 +7,8 @@ import (
"testing"
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"
smithyhttp "github.com/awslabs/smithy-go/transport/http"
"github.com/google/go-cmp/cmp"
@@ -124,7 +126,7 @@ func getURLPresignMiddlewareOptions() Options {
return nil
},
},
- Presigner: mockURLPresigner{},
+ Presigner: &mockURLPresigner{},
}
}
@@ -136,11 +138,14 @@ type mockURLPresignInput struct {
type mockURLPresigner struct{}
-func (mockURLPresigner) PresignURL(ctx context.Context, srcRegion string, params interface{}) (
- presignedURL string, signedHeader http.Header, err error,
+func (*mockURLPresigner) PresignURL(ctx context.Context, srcRegion string, params interface{}) (
+ req *v4.PresignedHTTPRequest, err error,
) {
in := params.(*mockURLPresignInput)
- return "https://example." + srcRegion + ".amazonaws.com/?DestinationRegion=" + in.DestinationRegion,
- http.Header{}, nil
+ return &v4.PresignedHTTPRequest{
+ URL: "https://example." + srcRegion + ".amazonaws.com/?DestinationRegion=" + in.DestinationRegion,
+ Method: "GET",
+ SignedHeader: http.Header{},
+ }, nil
}
diff --git a/service/rds/api_client.go b/service/rds/api_client.go
index 2be8c7cae95..cd36a219da5 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"
@@ -226,3 +227,78 @@ func addRequestResponseLogging(stack *middleware.Stack, o Options) error {
LogResponseWithBody: o.ClientLogMode.IsResponseWithBody(),
}, middleware.After)
}
+
+// HTTPPresignerV4 represents presigner interface used by presign url client
+type HTTPPresignerV4 interface {
+ PresignHTTP(
+ ctx context.Context, credentials aws.Credentials, r *http.Request,
+ payloadHash string, service string, region string, signingTime time.Time,
+ ) (url string, signedHeader http.Header, err error)
+}
+
+// PresignOptions represents the presign client options
+type PresignOptions struct {
+
+ // ClientOptions are list of functional options to mutate client options used by
+ // presign client
+ ClientOptions []func(*Options)
+
+ // Presigner is the presigner used by the presign url client
+ Presigner HTTPPresignerV4
+}
+
+// WithPresignClientFromClientOptions is a helper utility to retrieve a function
+// that takes PresignOption as input
+func WithPresignClientFromClientOptions(optFns ...func(*Options)) func(*PresignOptions) {
+ return withPresignClientFromClientOptions(optFns).options
+}
+
+type withPresignClientFromClientOptions []func(*Options)
+
+func (w withPresignClientFromClientOptions) options(o *PresignOptions) {
+ o.ClientOptions = append(o.ClientOptions, w...)
+}
+
+// PresignClient represents the presign url client
+type PresignClient struct {
+ client *Client
+ presigner HTTPPresignerV4
+}
+
+// NewPresignClient generates a presign client using provided API Client and
+// presign options
+func NewPresignClient(c *Client, optFns ...func(*PresignOptions)) *PresignClient {
+ var presignOptions PresignOptions
+ for _, fn := range optFns {
+ fn(&presignOptions)
+ }
+ client := copyAPIClient(c, presignOptions.ClientOptions...)
+ if presignOptions.Presigner == nil {
+ presignOptions.Presigner = v4.NewSigner()
+ }
+
+ return &PresignClient{
+ client: client,
+ presigner: presignOptions.Presigner,
+ }
+}
+
+func copyAPIClient(c *Client, optFns ...func(*Options)) *Client {
+ return New(c.options, optFns...)
+}
+
+func (c *PresignClient) convertToPresignMiddleware(stack *middleware.Stack, options Options) (err error) {
+ stack.Finalize.Clear()
+ stack.Deserialize.Clear()
+ stack.Build.Remove((*awsmiddleware.ClientRequestID)(nil).ID())
+ err = stack.Finalize.Add(v4.NewPresignHTTPRequestMiddleware(options.Credentials, c.presigner), middleware.After)
+ if err != nil {
+ return err
+ }
+ // convert request to a GET request
+ 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 441daece3b4..8662a7a11a8 100644
--- a/service/rds/api_op_CopyDBClusterSnapshot.go
+++ b/service/rds/api_op_CopyDBClusterSnapshot.go
@@ -6,13 +6,11 @@ import (
"context"
"fmt"
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
@@ -330,80 +328,39 @@ func setCopyDBClusterSnapshotdestinationRegion(params interface{}, value string)
input.destinationRegion = &value
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{}
- }
-
- 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
- }
-
- 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.ClientRequestID)(nil).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
-}
func addCopyDBClusterSnapshotPresignURLMiddleware(stack *middleware.Stack, options Options) error {
return presignedurlcust.AddMiddleware(stack, presignedurlcust.Options{
Accessor: presignedurlcust.ParameterAccessor{
- GetPresignedURL: getCopyDBClusterSnapshotPreSignedUrl,
- GetSourceRegion: getCopyDBClusterSnapshotSourceRegion,
- CopyInput: copyCopyDBClusterSnapshotInputForPresign,
+ GetPresignedURL: getCopyDBClusterSnapshotPreSignedUrl,
+
+ GetSourceRegion: getCopyDBClusterSnapshotSourceRegion,
+
+ CopyInput: copyCopyDBClusterSnapshotInputForPresign,
+
SetDestinationRegion: setCopyDBClusterSnapshotdestinationRegion,
- SetPresignedURL: setCopyDBClusterSnapshotPreSignedUrl,
+
+ SetPresignedURL: setCopyDBClusterSnapshotPreSignedUrl,
},
- Presigner: &presignAutoFillCopyDBClusterSnapshotClient{client: newCopyDBClusterSnapshotHTTPPresignURLClient(options)},
+ Presigner: &presignAutoFillCopyDBClusterSnapshotClient{client: NewPresignClient(New(options))},
})
}
type presignAutoFillCopyDBClusterSnapshotClient struct {
- client *copyDBClusterSnapshotHTTPPresignURLClient
+ client *PresignClient
}
-func (c *presignAutoFillCopyDBClusterSnapshotClient) PresignURL(ctx context.Context, region string, params interface{}) (string, http.Header, error) {
+// PresignURL is a middleware accessor that satisfies URLPresigner interface.
+func (c *presignAutoFillCopyDBClusterSnapshotClient) PresignURL(ctx context.Context, srcRegion string, params interface{}) (*v4.PresignedHTTPRequest, error) {
input, ok := params.(*CopyDBClusterSnapshotInput)
if !ok {
- return ``, nil, fmt.Errorf("expect *CopyDBClusterSnapshotInput type, got %T", params)
+ return nil, fmt.Errorf("expect *CopyDBClusterSnapshotInput type, got %T", params)
}
optFn := func(o *Options) {
- o.Region = region
+ o.Region = srcRegion
o.APIOptions = append(o.APIOptions, presignedurlcust.RemoveMiddleware)
}
- return c.client.PresignCopyDBClusterSnapshot(ctx, input, optFn)
+ presignOptFn := WithPresignClientFromClientOptions(optFn)
+ return c.client.PresignCopyDBClusterSnapshot(ctx, input, presignOptFn)
}
func newServiceMetadataMiddleware_opCopyDBClusterSnapshot(region string) *awsmiddleware.RegisterServiceMetadata {
@@ -414,3 +371,35 @@ func newServiceMetadataMiddleware_opCopyDBClusterSnapshot(region string) *awsmid
OperationName: "CopyDBClusterSnapshot",
}
}
+
+// PresignCopyDBClusterSnapshot is used to generate a presigned HTTP Request which
+// contains presigned URL, signed headers and HTTP method used.
+func (c *PresignClient) PresignCopyDBClusterSnapshot(ctx context.Context, params *CopyDBClusterSnapshotInput, optFns ...func(*PresignOptions)) (*v4.PresignedHTTPRequest, error) {
+ if params == nil {
+ params = &CopyDBClusterSnapshotInput{}
+ }
+ var presignOptions PresignOptions
+ for _, fn := range optFns {
+ fn(&presignOptions)
+ }
+ if len(optFns) != 0 {
+ c = NewPresignClient(c.client, optFns...)
+ }
+
+ clientOptFns := make([]func(o *Options), 0)
+ clientOptFns = append(clientOptFns, func(o *Options) {
+ o.HTTPClient = &smithyhttp.NopClient{}
+ })
+
+ ctx = presignedurlcust.WithIsPresigning(ctx)
+ result, _, err := c.client.invokeOperation(ctx, "CopyDBClusterSnapshot", params, clientOptFns,
+ addOperationCopyDBClusterSnapshotMiddlewares,
+ c.convertToPresignMiddleware,
+ )
+ if err != nil {
+ return nil, err
+ }
+
+ out := result.(*v4.PresignedHTTPRequest)
+ return out, nil
+}
diff --git a/service/rds/api_op_CopyDBSnapshot.go b/service/rds/api_op_CopyDBSnapshot.go
index 8f1fe711d46..a839500bc76 100644
--- a/service/rds/api_op_CopyDBSnapshot.go
+++ b/service/rds/api_op_CopyDBSnapshot.go
@@ -6,13 +6,11 @@ import (
"context"
"fmt"
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 the specified DB snapshot. The source DB snapshot must be in the
@@ -283,80 +281,39 @@ func setCopyDBSnapshotdestinationRegion(params interface{}, value string) error
input.destinationRegion = &value
return nil
}
-
-type copyDBSnapshotHTTPPresignURLClient struct {
- client *Client
- presigner *v4.Signer
-}
-
-func newCopyDBSnapshotHTTPPresignURLClient(options Options, optFns ...func(*Options)) *copyDBSnapshotHTTPPresignURLClient {
- return ©DBSnapshotHTTPPresignURLClient{
- client: New(options, optFns...),
- presigner: v4.NewSigner(),
- }
-}
-func (c *copyDBSnapshotHTTPPresignURLClient) PresignCopyDBSnapshot(ctx context.Context, params *CopyDBSnapshotInput, optFns ...func(*Options)) (string, http.Header, error) {
- if params == nil {
- params = &CopyDBSnapshotInput{}
- }
-
- optFns = append(optFns, func(o *Options) {
- o.HTTPClient = &smithyhttp.NopClient{}
- })
-
- ctx = presignedurlcust.WithIsPresigning(ctx)
- result, _, err := c.client.invokeOperation(ctx, "CopyDBSnapshot", params, optFns,
- addOperationCopyDBSnapshotMiddlewares,
- c.convertToPresignMiddleware,
- )
- if err != nil {
- return ``, nil, err
- }
-
- out := result.(*v4.PresignedHTTPRequest)
- return out.URL, out.SignedHeader, nil
-}
-func (c *copyDBSnapshotHTTPPresignURLClient) convertToPresignMiddleware(stack *middleware.Stack, options Options) (err error) {
- stack.Finalize.Clear()
- stack.Deserialize.Clear()
- stack.Build.Remove((*awsmiddleware.ClientRequestID)(nil).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
-}
func addCopyDBSnapshotPresignURLMiddleware(stack *middleware.Stack, options Options) error {
return presignedurlcust.AddMiddleware(stack, presignedurlcust.Options{
Accessor: presignedurlcust.ParameterAccessor{
- GetPresignedURL: getCopyDBSnapshotPreSignedUrl,
- GetSourceRegion: getCopyDBSnapshotSourceRegion,
- CopyInput: copyCopyDBSnapshotInputForPresign,
+ GetPresignedURL: getCopyDBSnapshotPreSignedUrl,
+
+ GetSourceRegion: getCopyDBSnapshotSourceRegion,
+
+ CopyInput: copyCopyDBSnapshotInputForPresign,
+
SetDestinationRegion: setCopyDBSnapshotdestinationRegion,
- SetPresignedURL: setCopyDBSnapshotPreSignedUrl,
+
+ SetPresignedURL: setCopyDBSnapshotPreSignedUrl,
},
- Presigner: &presignAutoFillCopyDBSnapshotClient{client: newCopyDBSnapshotHTTPPresignURLClient(options)},
+ Presigner: &presignAutoFillCopyDBSnapshotClient{client: NewPresignClient(New(options))},
})
}
type presignAutoFillCopyDBSnapshotClient struct {
- client *copyDBSnapshotHTTPPresignURLClient
+ client *PresignClient
}
-func (c *presignAutoFillCopyDBSnapshotClient) PresignURL(ctx context.Context, region string, params interface{}) (string, http.Header, error) {
+// PresignURL is a middleware accessor that satisfies URLPresigner interface.
+func (c *presignAutoFillCopyDBSnapshotClient) PresignURL(ctx context.Context, srcRegion string, params interface{}) (*v4.PresignedHTTPRequest, error) {
input, ok := params.(*CopyDBSnapshotInput)
if !ok {
- return ``, nil, fmt.Errorf("expect *CopyDBSnapshotInput type, got %T", params)
+ return nil, fmt.Errorf("expect *CopyDBSnapshotInput type, got %T", params)
}
optFn := func(o *Options) {
- o.Region = region
+ o.Region = srcRegion
o.APIOptions = append(o.APIOptions, presignedurlcust.RemoveMiddleware)
}
- return c.client.PresignCopyDBSnapshot(ctx, input, optFn)
+ presignOptFn := WithPresignClientFromClientOptions(optFn)
+ return c.client.PresignCopyDBSnapshot(ctx, input, presignOptFn)
}
func newServiceMetadataMiddleware_opCopyDBSnapshot(region string) *awsmiddleware.RegisterServiceMetadata {
@@ -367,3 +324,35 @@ func newServiceMetadataMiddleware_opCopyDBSnapshot(region string) *awsmiddleware
OperationName: "CopyDBSnapshot",
}
}
+
+// PresignCopyDBSnapshot is used to generate a presigned HTTP Request which
+// contains presigned URL, signed headers and HTTP method used.
+func (c *PresignClient) PresignCopyDBSnapshot(ctx context.Context, params *CopyDBSnapshotInput, optFns ...func(*PresignOptions)) (*v4.PresignedHTTPRequest, error) {
+ if params == nil {
+ params = &CopyDBSnapshotInput{}
+ }
+ var presignOptions PresignOptions
+ for _, fn := range optFns {
+ fn(&presignOptions)
+ }
+ if len(optFns) != 0 {
+ c = NewPresignClient(c.client, optFns...)
+ }
+
+ clientOptFns := make([]func(o *Options), 0)
+ clientOptFns = append(clientOptFns, func(o *Options) {
+ o.HTTPClient = &smithyhttp.NopClient{}
+ })
+
+ ctx = presignedurlcust.WithIsPresigning(ctx)
+ result, _, err := c.client.invokeOperation(ctx, "CopyDBSnapshot", params, clientOptFns,
+ addOperationCopyDBSnapshotMiddlewares,
+ c.convertToPresignMiddleware,
+ )
+ if err != nil {
+ return nil, err
+ }
+
+ out := result.(*v4.PresignedHTTPRequest)
+ return out, nil
+}
diff --git a/service/rds/api_op_CreateDBCluster.go b/service/rds/api_op_CreateDBCluster.go
index 85d3f03016d..78e89d9ed0f 100644
--- a/service/rds/api_op_CreateDBCluster.go
+++ b/service/rds/api_op_CreateDBCluster.go
@@ -6,13 +6,11 @@ import (
"context"
"fmt"
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"
)
// Creates a new Amazon Aurora DB cluster. You can use the
@@ -467,80 +465,39 @@ func setCreateDBClusterdestinationRegion(params interface{}, value string) error
input.destinationRegion = &value
return nil
}
-
-type createDBClusterHTTPPresignURLClient struct {
- client *Client
- presigner *v4.Signer
-}
-
-func newCreateDBClusterHTTPPresignURLClient(options Options, optFns ...func(*Options)) *createDBClusterHTTPPresignURLClient {
- return &createDBClusterHTTPPresignURLClient{
- client: New(options, optFns...),
- presigner: v4.NewSigner(),
- }
-}
-func (c *createDBClusterHTTPPresignURLClient) PresignCreateDBCluster(ctx context.Context, params *CreateDBClusterInput, optFns ...func(*Options)) (string, http.Header, error) {
- if params == nil {
- params = &CreateDBClusterInput{}
- }
-
- optFns = append(optFns, func(o *Options) {
- o.HTTPClient = &smithyhttp.NopClient{}
- })
-
- ctx = presignedurlcust.WithIsPresigning(ctx)
- result, _, err := c.client.invokeOperation(ctx, "CreateDBCluster", params, optFns,
- addOperationCreateDBClusterMiddlewares,
- c.convertToPresignMiddleware,
- )
- if err != nil {
- return ``, nil, err
- }
-
- out := result.(*v4.PresignedHTTPRequest)
- return out.URL, out.SignedHeader, nil
-}
-func (c *createDBClusterHTTPPresignURLClient) convertToPresignMiddleware(stack *middleware.Stack, options Options) (err error) {
- stack.Finalize.Clear()
- stack.Deserialize.Clear()
- stack.Build.Remove((*awsmiddleware.ClientRequestID)(nil).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
-}
func addCreateDBClusterPresignURLMiddleware(stack *middleware.Stack, options Options) error {
return presignedurlcust.AddMiddleware(stack, presignedurlcust.Options{
Accessor: presignedurlcust.ParameterAccessor{
- GetPresignedURL: getCreateDBClusterPreSignedUrl,
- GetSourceRegion: getCreateDBClusterSourceRegion,
- CopyInput: copyCreateDBClusterInputForPresign,
+ GetPresignedURL: getCreateDBClusterPreSignedUrl,
+
+ GetSourceRegion: getCreateDBClusterSourceRegion,
+
+ CopyInput: copyCreateDBClusterInputForPresign,
+
SetDestinationRegion: setCreateDBClusterdestinationRegion,
- SetPresignedURL: setCreateDBClusterPreSignedUrl,
+
+ SetPresignedURL: setCreateDBClusterPreSignedUrl,
},
- Presigner: &presignAutoFillCreateDBClusterClient{client: newCreateDBClusterHTTPPresignURLClient(options)},
+ Presigner: &presignAutoFillCreateDBClusterClient{client: NewPresignClient(New(options))},
})
}
type presignAutoFillCreateDBClusterClient struct {
- client *createDBClusterHTTPPresignURLClient
+ client *PresignClient
}
-func (c *presignAutoFillCreateDBClusterClient) PresignURL(ctx context.Context, region string, params interface{}) (string, http.Header, error) {
+// PresignURL is a middleware accessor that satisfies URLPresigner interface.
+func (c *presignAutoFillCreateDBClusterClient) PresignURL(ctx context.Context, srcRegion string, params interface{}) (*v4.PresignedHTTPRequest, error) {
input, ok := params.(*CreateDBClusterInput)
if !ok {
- return ``, nil, fmt.Errorf("expect *CreateDBClusterInput type, got %T", params)
+ return nil, fmt.Errorf("expect *CreateDBClusterInput type, got %T", params)
}
optFn := func(o *Options) {
- o.Region = region
+ o.Region = srcRegion
o.APIOptions = append(o.APIOptions, presignedurlcust.RemoveMiddleware)
}
- return c.client.PresignCreateDBCluster(ctx, input, optFn)
+ presignOptFn := WithPresignClientFromClientOptions(optFn)
+ return c.client.PresignCreateDBCluster(ctx, input, presignOptFn)
}
func newServiceMetadataMiddleware_opCreateDBCluster(region string) *awsmiddleware.RegisterServiceMetadata {
@@ -551,3 +508,35 @@ func newServiceMetadataMiddleware_opCreateDBCluster(region string) *awsmiddlewar
OperationName: "CreateDBCluster",
}
}
+
+// PresignCreateDBCluster is used to generate a presigned HTTP Request which
+// contains presigned URL, signed headers and HTTP method used.
+func (c *PresignClient) PresignCreateDBCluster(ctx context.Context, params *CreateDBClusterInput, optFns ...func(*PresignOptions)) (*v4.PresignedHTTPRequest, error) {
+ if params == nil {
+ params = &CreateDBClusterInput{}
+ }
+ var presignOptions PresignOptions
+ for _, fn := range optFns {
+ fn(&presignOptions)
+ }
+ if len(optFns) != 0 {
+ c = NewPresignClient(c.client, optFns...)
+ }
+
+ clientOptFns := make([]func(o *Options), 0)
+ clientOptFns = append(clientOptFns, func(o *Options) {
+ o.HTTPClient = &smithyhttp.NopClient{}
+ })
+
+ ctx = presignedurlcust.WithIsPresigning(ctx)
+ result, _, err := c.client.invokeOperation(ctx, "CreateDBCluster", params, clientOptFns,
+ addOperationCreateDBClusterMiddlewares,
+ c.convertToPresignMiddleware,
+ )
+ if err != nil {
+ return nil, err
+ }
+
+ out := result.(*v4.PresignedHTTPRequest)
+ return out, nil
+}
diff --git a/service/rds/api_op_CreateDBInstanceReadReplica.go b/service/rds/api_op_CreateDBInstanceReadReplica.go
index b995932f4c6..5fef4758eba 100644
--- a/service/rds/api_op_CreateDBInstanceReadReplica.go
+++ b/service/rds/api_op_CreateDBInstanceReadReplica.go
@@ -6,13 +6,11 @@ import (
"context"
"fmt"
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"
)
// Creates a new DB instance that acts as a read replica for an existing source DB
@@ -477,80 +475,39 @@ func setCreateDBInstanceReadReplicadestinationRegion(params interface{}, value s
input.destinationRegion = &value
return nil
}
-
-type createDBInstanceReadReplicaHTTPPresignURLClient struct {
- client *Client
- presigner *v4.Signer
-}
-
-func newCreateDBInstanceReadReplicaHTTPPresignURLClient(options Options, optFns ...func(*Options)) *createDBInstanceReadReplicaHTTPPresignURLClient {
- return &createDBInstanceReadReplicaHTTPPresignURLClient{
- client: New(options, optFns...),
- presigner: v4.NewSigner(),
- }
-}
-func (c *createDBInstanceReadReplicaHTTPPresignURLClient) PresignCreateDBInstanceReadReplica(ctx context.Context, params *CreateDBInstanceReadReplicaInput, optFns ...func(*Options)) (string, http.Header, error) {
- if params == nil {
- params = &CreateDBInstanceReadReplicaInput{}
- }
-
- optFns = append(optFns, func(o *Options) {
- o.HTTPClient = &smithyhttp.NopClient{}
- })
-
- ctx = presignedurlcust.WithIsPresigning(ctx)
- result, _, err := c.client.invokeOperation(ctx, "CreateDBInstanceReadReplica", params, optFns,
- addOperationCreateDBInstanceReadReplicaMiddlewares,
- c.convertToPresignMiddleware,
- )
- if err != nil {
- return ``, nil, err
- }
-
- out := result.(*v4.PresignedHTTPRequest)
- return out.URL, out.SignedHeader, nil
-}
-func (c *createDBInstanceReadReplicaHTTPPresignURLClient) convertToPresignMiddleware(stack *middleware.Stack, options Options) (err error) {
- stack.Finalize.Clear()
- stack.Deserialize.Clear()
- stack.Build.Remove((*awsmiddleware.ClientRequestID)(nil).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
-}
func addCreateDBInstanceReadReplicaPresignURLMiddleware(stack *middleware.Stack, options Options) error {
return presignedurlcust.AddMiddleware(stack, presignedurlcust.Options{
Accessor: presignedurlcust.ParameterAccessor{
- GetPresignedURL: getCreateDBInstanceReadReplicaPreSignedUrl,
- GetSourceRegion: getCreateDBInstanceReadReplicaSourceRegion,
- CopyInput: copyCreateDBInstanceReadReplicaInputForPresign,
+ GetPresignedURL: getCreateDBInstanceReadReplicaPreSignedUrl,
+
+ GetSourceRegion: getCreateDBInstanceReadReplicaSourceRegion,
+
+ CopyInput: copyCreateDBInstanceReadReplicaInputForPresign,
+
SetDestinationRegion: setCreateDBInstanceReadReplicadestinationRegion,
- SetPresignedURL: setCreateDBInstanceReadReplicaPreSignedUrl,
+
+ SetPresignedURL: setCreateDBInstanceReadReplicaPreSignedUrl,
},
- Presigner: &presignAutoFillCreateDBInstanceReadReplicaClient{client: newCreateDBInstanceReadReplicaHTTPPresignURLClient(options)},
+ Presigner: &presignAutoFillCreateDBInstanceReadReplicaClient{client: NewPresignClient(New(options))},
})
}
type presignAutoFillCreateDBInstanceReadReplicaClient struct {
- client *createDBInstanceReadReplicaHTTPPresignURLClient
+ client *PresignClient
}
-func (c *presignAutoFillCreateDBInstanceReadReplicaClient) PresignURL(ctx context.Context, region string, params interface{}) (string, http.Header, error) {
+// PresignURL is a middleware accessor that satisfies URLPresigner interface.
+func (c *presignAutoFillCreateDBInstanceReadReplicaClient) PresignURL(ctx context.Context, srcRegion string, params interface{}) (*v4.PresignedHTTPRequest, error) {
input, ok := params.(*CreateDBInstanceReadReplicaInput)
if !ok {
- return ``, nil, fmt.Errorf("expect *CreateDBInstanceReadReplicaInput type, got %T", params)
+ return nil, fmt.Errorf("expect *CreateDBInstanceReadReplicaInput type, got %T", params)
}
optFn := func(o *Options) {
- o.Region = region
+ o.Region = srcRegion
o.APIOptions = append(o.APIOptions, presignedurlcust.RemoveMiddleware)
}
- return c.client.PresignCreateDBInstanceReadReplica(ctx, input, optFn)
+ presignOptFn := WithPresignClientFromClientOptions(optFn)
+ return c.client.PresignCreateDBInstanceReadReplica(ctx, input, presignOptFn)
}
func newServiceMetadataMiddleware_opCreateDBInstanceReadReplica(region string) *awsmiddleware.RegisterServiceMetadata {
@@ -561,3 +518,35 @@ func newServiceMetadataMiddleware_opCreateDBInstanceReadReplica(region string) *
OperationName: "CreateDBInstanceReadReplica",
}
}
+
+// PresignCreateDBInstanceReadReplica is used to generate a presigned HTTP Request
+// which contains presigned URL, signed headers and HTTP method used.
+func (c *PresignClient) PresignCreateDBInstanceReadReplica(ctx context.Context, params *CreateDBInstanceReadReplicaInput, optFns ...func(*PresignOptions)) (*v4.PresignedHTTPRequest, error) {
+ if params == nil {
+ params = &CreateDBInstanceReadReplicaInput{}
+ }
+ var presignOptions PresignOptions
+ for _, fn := range optFns {
+ fn(&presignOptions)
+ }
+ if len(optFns) != 0 {
+ c = NewPresignClient(c.client, optFns...)
+ }
+
+ clientOptFns := make([]func(o *Options), 0)
+ clientOptFns = append(clientOptFns, func(o *Options) {
+ o.HTTPClient = &smithyhttp.NopClient{}
+ })
+
+ ctx = presignedurlcust.WithIsPresigning(ctx)
+ result, _, err := c.client.invokeOperation(ctx, "CreateDBInstanceReadReplica", params, clientOptFns,
+ addOperationCreateDBInstanceReadReplicaMiddlewares,
+ c.convertToPresignMiddleware,
+ )
+ if err != nil {
+ return nil, err
+ }
+
+ out := result.(*v4.PresignedHTTPRequest)
+ return out, nil
+}
diff --git a/service/rds/go.sum b/service/rds/go.sum
index d39b60a4034..26564e07dc7 100644
--- a/service/rds/go.sum
+++ b/service/rds/go.sum
@@ -1,3 +1,10 @@
+github.com/aws/aws-sdk-go-v2 v0.28.0/go.mod h1:P9h1Cf+uOpElAT533QXKOzrpFaOlm8JMorThJNUfQ6Q=
+github.com/aws/aws-sdk-go-v2 v0.29.0 h1:V/KKvuMO2hwHRg2SXJc5aasBHhD1AWbS6KMWg/Ueq1w=
+github.com/aws/aws-sdk-go-v2 v0.29.0/go.mod h1:4d1/Ee0vCwCF7BfG1hCT3zu82493cRy5+VZ8JHvMPf0=
+github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v0.1.0 h1:r6FAnK2190ahYhtzj0HRcxCWlb7SE9/zI8v73npLkYU=
+github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v0.1.0/go.mod h1:aLwdZO0CArC1N9dbBxt7C2PBxkPktM0sDsr9iS7A9SY=
+github.com/awslabs/smithy-go v0.2.1/go.mod h1:hPOQwnmBLHsUphH13tVSjQhTAFma0/0XoZGbBcOuABI=
+github.com/awslabs/smithy-go v0.3.0/go.mod h1:hPOQwnmBLHsUphH13tVSjQhTAFma0/0XoZGbBcOuABI=
github.com/awslabs/smithy-go v0.3.1-0.20201104233911-38864709e183/go.mod h1:hPOQwnmBLHsUphH13tVSjQhTAFma0/0XoZGbBcOuABI=
github.com/awslabs/smithy-go v0.3.1-0.20201108010311-62c2a93810b4 h1:Aj5dOF+lDoEhU92no7YZF0IokuWGjiNrcm/DGIG3iII=
github.com/awslabs/smithy-go v0.3.1-0.20201108010311-62c2a93810b4/go.mod h1:hPOQwnmBLHsUphH13tVSjQhTAFma0/0XoZGbBcOuABI=
@@ -12,5 +19,6 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
diff --git a/service/s3/api_client.go b/service/s3/api_client.go
index 2825f4011d6..b52d80b46d0 100644
--- a/service/s3/api_client.go
+++ b/service/s3/api_client.go
@@ -11,6 +11,7 @@ import (
awshttp "github.com/aws/aws-sdk-go-v2/aws/transport/http"
acceptencodingcust "github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding"
"github.com/aws/aws-sdk-go-v2/service/internal/s3shared"
+ s3cust "github.com/aws/aws-sdk-go-v2/service/s3/internal/customizations"
smithy "github.com/awslabs/smithy-go"
"github.com/awslabs/smithy-go/logging"
"github.com/awslabs/smithy-go/middleware"
@@ -258,3 +259,98 @@ func addRequestResponseLogging(stack *middleware.Stack, o Options) error {
LogResponseWithBody: o.ClientLogMode.IsResponseWithBody(),
}, middleware.After)
}
+
+// HTTPPresignerV4 represents presigner interface used by presign url client
+type HTTPPresignerV4 interface {
+ PresignHTTP(
+ ctx context.Context, credentials aws.Credentials, r *http.Request,
+ payloadHash string, service string, region string, signingTime time.Time,
+ ) (url string, signedHeader http.Header, err error)
+}
+
+// PresignOptions represents the presign client options
+type PresignOptions struct {
+
+ // ClientOptions are list of functional options to mutate client options used by
+ // presign client
+ ClientOptions []func(*Options)
+
+ // Presigner is the presigner used by the presign url client
+ Presigner HTTPPresignerV4
+
+ // Expires sets the expiration duration for the generated presign url. This should
+ // be the duration in seconds the presigned URL should be considered valid for. If
+ // not set or set to zero, presign url would default to expire after 900 seconds.
+ Expires time.Duration
+}
+
+// WithPresignClientFromClientOptions is a helper utility to retrieve a function
+// that takes PresignOption as input
+func WithPresignClientFromClientOptions(optFns ...func(*Options)) func(*PresignOptions) {
+ return withPresignClientFromClientOptions(optFns).options
+}
+
+type withPresignClientFromClientOptions []func(*Options)
+
+func (w withPresignClientFromClientOptions) options(o *PresignOptions) {
+ o.ClientOptions = append(o.ClientOptions, w...)
+}
+
+// WithPresignExpires is a helper utility to append Expires value on presign
+// options optional function
+func WithPresignExpires(dur time.Duration) func(*PresignOptions) {
+ return withPresignExpires(dur).options
+}
+
+type withPresignExpires time.Duration
+
+func (w withPresignExpires) options(o *PresignOptions) {
+ o.Expires = time.Duration(w)
+}
+
+// PresignClient represents the presign url client
+type PresignClient struct {
+ client *Client
+ presigner HTTPPresignerV4
+ expires time.Duration
+}
+
+// NewPresignClient generates a presign client using provided API Client and
+// presign options
+func NewPresignClient(c *Client, optFns ...func(*PresignOptions)) *PresignClient {
+ var presignOptions PresignOptions
+ for _, fn := range optFns {
+ fn(&presignOptions)
+ }
+ client := copyAPIClient(c, presignOptions.ClientOptions...)
+ if presignOptions.Presigner == nil {
+ presignOptions.Presigner = v4.NewSigner()
+ }
+
+ return &PresignClient{
+ client: client,
+ presigner: presignOptions.Presigner,
+ expires: presignOptions.Expires,
+ }
+}
+
+func copyAPIClient(c *Client, optFns ...func(*Options)) *Client {
+ return New(c.options, optFns...)
+}
+
+func (c *PresignClient) convertToPresignMiddleware(stack *middleware.Stack, options Options) (err error) {
+ stack.Finalize.Clear()
+ stack.Deserialize.Clear()
+ stack.Build.Remove((*awsmiddleware.ClientRequestID)(nil).ID())
+ err = stack.Finalize.Add(v4.NewPresignHTTPRequestMiddleware(options.Credentials, c.presigner), middleware.After)
+ if err != nil {
+ return err
+ }
+ // add middleware to set expiration for s3 presigned url, if expiration is set to
+ // 0, this middleware sets a default expiration of 900 seconds
+ err = stack.Build.Add(&s3cust.AddExpiresOnPresignedURL{Expires: c.expires}, middleware.After)
+ if err != nil {
+ return err
+ }
+ return nil
+}
diff --git a/service/s3/api_op_GetObject.go b/service/s3/api_op_GetObject.go
index 76a279a0d6d..cb641f6ef04 100644
--- a/service/s3/api_op_GetObject.go
+++ b/service/s3/api_op_GetObject.go
@@ -6,6 +6,7 @@ import (
"context"
awsmiddleware "github.com/aws/aws-sdk-go-v2/aws/middleware"
"github.com/aws/aws-sdk-go-v2/aws/signer/v4"
+ presignedurlcust "github.com/aws/aws-sdk-go-v2/service/internal/presigned-url"
s3cust "github.com/aws/aws-sdk-go-v2/service/s3/internal/customizations"
"github.com/aws/aws-sdk-go-v2/service/s3/types"
"github.com/awslabs/smithy-go/middleware"
@@ -473,3 +474,35 @@ func addGetObjectUpdateEndpoint(stack *middleware.Stack, options Options) error
UseARNRegion: options.UseARNRegion,
})
}
+
+// PresignGetObject is used to generate a presigned HTTP Request which contains
+// presigned URL, signed headers and HTTP method used.
+func (c *PresignClient) PresignGetObject(ctx context.Context, params *GetObjectInput, optFns ...func(*PresignOptions)) (*v4.PresignedHTTPRequest, error) {
+ if params == nil {
+ params = &GetObjectInput{}
+ }
+ var presignOptions PresignOptions
+ for _, fn := range optFns {
+ fn(&presignOptions)
+ }
+ if len(optFns) != 0 {
+ c = NewPresignClient(c.client, optFns...)
+ }
+
+ clientOptFns := make([]func(o *Options), 0)
+ clientOptFns = append(clientOptFns, func(o *Options) {
+ o.HTTPClient = &smithyhttp.NopClient{}
+ })
+
+ ctx = presignedurlcust.WithIsPresigning(ctx)
+ result, _, err := c.client.invokeOperation(ctx, "GetObject", params, clientOptFns,
+ addOperationGetObjectMiddlewares,
+ c.convertToPresignMiddleware,
+ )
+ if err != nil {
+ return nil, err
+ }
+
+ out := result.(*v4.PresignedHTTPRequest)
+ return out, nil
+}
diff --git a/service/s3/api_op_PutObject.go b/service/s3/api_op_PutObject.go
index 65a55f775ee..8e5f4c92723 100644
--- a/service/s3/api_op_PutObject.go
+++ b/service/s3/api_op_PutObject.go
@@ -6,6 +6,7 @@ import (
"context"
awsmiddleware "github.com/aws/aws-sdk-go-v2/aws/middleware"
"github.com/aws/aws-sdk-go-v2/aws/signer/v4"
+ presignedurlcust "github.com/aws/aws-sdk-go-v2/service/internal/presigned-url"
s3cust "github.com/aws/aws-sdk-go-v2/service/s3/internal/customizations"
"github.com/aws/aws-sdk-go-v2/service/s3/types"
"github.com/awslabs/smithy-go/middleware"
@@ -424,3 +425,42 @@ func addPutObjectUpdateEndpoint(stack *middleware.Stack, options Options) error
UseARNRegion: options.UseARNRegion,
})
}
+
+// PresignPutObject is used to generate a presigned HTTP Request which contains
+// presigned URL, signed headers and HTTP method used.
+func (c *PresignClient) PresignPutObject(ctx context.Context, params *PutObjectInput, optFns ...func(*PresignOptions)) (*v4.PresignedHTTPRequest, error) {
+ if params == nil {
+ params = &PutObjectInput{}
+ }
+ var presignOptions PresignOptions
+ for _, fn := range optFns {
+ fn(&presignOptions)
+ }
+ if len(optFns) != 0 {
+ c = NewPresignClient(c.client, optFns...)
+ }
+
+ clientOptFns := make([]func(o *Options), 0)
+ clientOptFns = append(clientOptFns, func(o *Options) {
+ o.HTTPClient = &smithyhttp.NopClient{}
+ })
+
+ ctx = presignedurlcust.WithIsPresigning(ctx)
+ result, _, err := c.client.invokeOperation(ctx, "PutObject", params, clientOptFns,
+ addOperationPutObjectMiddlewares,
+ c.convertToPresignMiddleware,
+ addPutObjectPayloadAsUnsigned,
+ )
+ if err != nil {
+ return nil, err
+ }
+
+ out := result.(*v4.PresignedHTTPRequest)
+ return out, nil
+}
+
+func addPutObjectPayloadAsUnsigned(stack *middleware.Stack, options Options) error {
+ v4.RemoveContentSHA256HeaderMiddleware(stack)
+ v4.RemoveComputePayloadSHA256Middleware(stack)
+ return v4.AddUnsignedPayloadMiddleware(stack)
+}
diff --git a/service/s3/go.mod b/service/s3/go.mod
index 5882d405dd2..1b09d08a705 100644
--- a/service/s3/go.mod
+++ b/service/s3/go.mod
@@ -5,12 +5,16 @@ go 1.15
require (
github.com/aws/aws-sdk-go-v2 v0.29.1-0.20201113222241-726e4a15683d
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v0.3.1-0.20201113222241-726e4a15683d
+ github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v0.1.1-0.20201113222241-726e4a15683d
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v0.3.1-0.20201113222241-726e4a15683d
github.com/awslabs/smithy-go v0.3.1-0.20201108010311-62c2a93810b4
+ github.com/google/go-cmp v0.5.2
)
replace github.com/aws/aws-sdk-go-v2 => ../../
replace github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding => ../../service/internal/accept-encoding/
+replace github.com/aws/aws-sdk-go-v2/service/internal/presigned-url => ../../service/internal/presigned-url/
+
replace github.com/aws/aws-sdk-go-v2/service/internal/s3shared => ../../service/internal/s3shared/
diff --git a/service/s3/go.sum b/service/s3/go.sum
index 57ffc83dc1c..e1dc3ec43bf 100644
--- a/service/s3/go.sum
+++ b/service/s3/go.sum
@@ -5,6 +5,8 @@ github.com/awslabs/smithy-go v0.3.1-0.20201108010311-62c2a93810b4/go.mod h1:hPOQ
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/google/go-cmp v0.4.1 h1:/exdXoGamhu5ONeUJH0deniYLWYvQwW66yvlfiiKTu0=
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
+github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
diff --git a/service/s3/internal/configtesting/go.mod b/service/s3/internal/configtesting/go.mod
index 306d989e686..7e5f425a344 100644
--- a/service/s3/internal/configtesting/go.mod
+++ b/service/s3/internal/configtesting/go.mod
@@ -22,3 +22,5 @@ replace github.com/aws/aws-sdk-go-v2/service/internal/s3shared => ../../../../se
replace github.com/aws/aws-sdk-go-v2/service/sts => ../../../../service/sts/
replace github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding => ../../../../service/internal/accept-encoding/
+
+replace github.com/aws/aws-sdk-go-v2/service/internal/presigned-url => ../../../../service/internal/presigned-url/
diff --git a/service/s3/internal/configtesting/go.sum b/service/s3/internal/configtesting/go.sum
index 57ffc83dc1c..e1dc3ec43bf 100644
--- a/service/s3/internal/configtesting/go.sum
+++ b/service/s3/internal/configtesting/go.sum
@@ -5,6 +5,8 @@ github.com/awslabs/smithy-go v0.3.1-0.20201108010311-62c2a93810b4/go.mod h1:hPOQ
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/google/go-cmp v0.4.1 h1:/exdXoGamhu5ONeUJH0deniYLWYvQwW66yvlfiiKTu0=
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
+github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
diff --git a/service/s3/internal/customizations/presign_test.go b/service/s3/internal/customizations/presign_test.go
new file mode 100644
index 00000000000..f67df2f5d5e
--- /dev/null
+++ b/service/s3/internal/customizations/presign_test.go
@@ -0,0 +1,180 @@
+package customizations_test
+
+import (
+ "bytes"
+ "context"
+ "net/http"
+ "strings"
+ "testing"
+
+ "github.com/google/go-cmp/cmp"
+
+ "github.com/aws/aws-sdk-go-v2/aws"
+ "github.com/aws/aws-sdk-go-v2/internal/awstesting/unit"
+ "github.com/aws/aws-sdk-go-v2/service/s3"
+)
+
+func TestPutObject_PresignURL(t *testing.T) {
+ cases := map[string]struct {
+ input s3.PutObjectInput
+ options s3.PresignOptions
+ expectPresignedURLHost string
+ expectRequestURIQuery []string
+ expectSignedHeader http.Header
+ expectMethod string
+ expectError string
+ }{
+ "standard case": {
+ input: s3.PutObjectInput{
+ Bucket: aws.String("mock-bucket"),
+ Key: aws.String("mockkey"),
+ Body: strings.NewReader("hello-world"),
+ },
+ expectPresignedURLHost: "https://mock-bucket.s3.us-west-2.amazonaws.com/mockkey?",
+ expectRequestURIQuery: []string{
+ "X-Amz-Expires=900",
+ "X-Amz-Credential",
+ "X-Amz-Date",
+ "x-id=PutObject",
+ "X-Amz-Signature",
+ },
+ expectMethod: "PUT",
+ expectSignedHeader: http.Header{
+ "content-length": []string{"11"},
+ "content-type": []string{"application/octet-stream"},
+ "host": []string{"mock-bucket.s3.us-west-2.amazonaws.com"},
+ },
+ },
+ "seekable payload": {
+ input: s3.PutObjectInput{
+ Bucket: aws.String("mock-bucket"),
+ Key: aws.String("mockkey"),
+ Body: bytes.NewReader([]byte("hello-world")),
+ },
+ expectPresignedURLHost: "https://mock-bucket.s3.us-west-2.amazonaws.com/mockkey?",
+ expectRequestURIQuery: []string{
+ "X-Amz-Expires=900",
+ "X-Amz-Credential",
+ "X-Amz-Date",
+ "x-id=PutObject",
+ "X-Amz-Signature",
+ },
+ expectMethod: "PUT",
+ expectSignedHeader: http.Header{
+ "content-length": []string{"11"},
+ "content-type": []string{"application/octet-stream"},
+ "host": []string{"mock-bucket.s3.us-west-2.amazonaws.com"},
+ },
+ },
+ "unseekable payload": {
+ // unseekable payload succeeds as we disable content sha256 computation for streaming input
+ input: s3.PutObjectInput{
+ Bucket: aws.String("mock-bucket"),
+ Key: aws.String("mockkey"),
+ Body: bytes.NewBuffer([]byte(`hello-world`)),
+ },
+ expectPresignedURLHost: "https://mock-bucket.s3.us-west-2.amazonaws.com/mockkey?",
+ expectRequestURIQuery: []string{
+ "X-Amz-Expires=900",
+ "X-Amz-Credential",
+ "X-Amz-Date",
+ "x-id=PutObject",
+ "X-Amz-Signature",
+ },
+ expectMethod: "PUT",
+ expectSignedHeader: http.Header{
+ "content-length": []string{"11"},
+ "content-type": []string{"application/octet-stream"},
+ "host": []string{"mock-bucket.s3.us-west-2.amazonaws.com"},
+ },
+ },
+ "empty body": {
+ input: s3.PutObjectInput{
+ Bucket: aws.String("mock-bucket"),
+ Key: aws.String("mockkey"),
+ Body: bytes.NewReader([]byte(``)),
+ },
+ expectPresignedURLHost: "https://mock-bucket.s3.us-west-2.amazonaws.com/mockkey?",
+ expectRequestURIQuery: []string{
+ "X-Amz-Expires=900",
+ "X-Amz-Credential",
+ "X-Amz-Date",
+ "x-id=PutObject",
+ "X-Amz-Signature",
+ },
+ expectMethod: "PUT",
+ expectSignedHeader: http.Header{
+ "content-type": {"application/octet-stream"},
+ "host": []string{"mock-bucket.s3.us-west-2.amazonaws.com"},
+ },
+ },
+ "nil body": {
+ input: s3.PutObjectInput{
+ Bucket: aws.String("mock-bucket"),
+ Key: aws.String("mockkey"),
+ },
+ expectPresignedURLHost: "https://mock-bucket.s3.us-west-2.amazonaws.com/mockkey?",
+ expectRequestURIQuery: []string{
+ "X-Amz-Expires=900",
+ "X-Amz-Credential",
+ "X-Amz-Date",
+ "x-id=PutObject",
+ "X-Amz-Signature",
+ },
+ expectMethod: "PUT",
+ expectSignedHeader: http.Header{
+ "host": []string{"mock-bucket.s3.us-west-2.amazonaws.com"},
+ },
+ },
+ }
+
+ for name, c := range cases {
+ t.Run(name, func(t *testing.T) {
+ ctx := context.Background()
+ cfg := aws.Config{
+ Region: "us-west-2",
+ Credentials: unit.StubCredentialsProvider{},
+ Retryer: aws.NoOpRetryer{},
+ }
+ presignClient := s3.NewPresignClient(s3.NewFromConfig(cfg), func(options *s3.PresignOptions) {
+ options = &c.options
+ })
+
+ req, err := presignClient.PresignPutObject(ctx, &c.input)
+ if err != nil {
+ if len(c.expectError) == 0 {
+ t.Fatalf("expected no error, got %v", err)
+ }
+ // if expect error, match error and skip rest
+ if e, a := c.expectError, err.Error(); !strings.Contains(a, e) {
+ t.Fatalf("expected error to be %s, got %s", e, a)
+ }
+ } else {
+ if len(c.expectError) != 0 {
+ t.Fatalf("expected error to be %v, got none", c.expectError)
+ }
+ }
+
+ if e, a := c.expectPresignedURLHost, req.URL; !strings.Contains(a, e) {
+ t.Fatalf("expected presigned url to contain host %s, got %s", e, a)
+ }
+
+ if len(c.expectRequestURIQuery) != 0 {
+ for _, label := range c.expectRequestURIQuery {
+ if e, a := label, req.URL; !strings.Contains(a, e) {
+ t.Fatalf("expected presigned url to contain %v label in url: %v", label, req.URL)
+ }
+ }
+ }
+
+ if e, a := c.expectSignedHeader, req.SignedHeader; len(cmp.Diff(e, a)) != 0 {
+ t.Fatalf("expected signed header to be %s, got %s, \n diff : %s", e, a, cmp.Diff(e, a))
+ }
+
+ if e, a := c.expectMethod, req.Method; !strings.EqualFold(e, a) {
+ t.Fatalf("expected presigning Method to be %s, got %s", e, a)
+ }
+
+ })
+ }
+}
diff --git a/service/s3/internal/customizations/presigned_expires.go b/service/s3/internal/customizations/presigned_expires.go
new file mode 100644
index 00000000000..2ea6318d4a9
--- /dev/null
+++ b/service/s3/internal/customizations/presigned_expires.go
@@ -0,0 +1,49 @@
+package customizations
+
+import (
+ "context"
+ "fmt"
+ "strconv"
+ "time"
+
+ "github.com/awslabs/smithy-go/middleware"
+ smithyhttp "github.com/awslabs/smithy-go/transport/http"
+)
+
+// AddExpiresOnPresignedURL represents a build middleware used to assign
+// expiration on a presigned URL.
+type AddExpiresOnPresignedURL struct {
+
+ // Expires is time.Duration within which presigned url should be expired.
+ // This should be the duration in seconds the presigned URL should be considered valid for.
+ // By default the S3 presigned url expires in 15 minutes ie. 900 seconds.
+ Expires time.Duration
+}
+
+// ID representing the middleware
+func (*AddExpiresOnPresignedURL) ID() string {
+ return "S3:AddExpiresOnPresignedURL"
+}
+
+// HandleBuild handles the build step middleware behavior
+func (m *AddExpiresOnPresignedURL) HandleBuild(ctx context.Context, in middleware.BuildInput, next middleware.BuildHandler) (
+ out middleware.BuildOutput, metadata middleware.Metadata, err error,
+) {
+ // if expiration is unset skip this middleware
+ if m.Expires == 0 {
+ // default to 15 * time.Minutes
+ m.Expires = 15 * time.Minute
+ }
+
+ req, ok := in.Request.(*smithyhttp.Request)
+ if !ok {
+ return out, metadata, fmt.Errorf("unknown transport type %T", req)
+ }
+
+ // set S3 X-AMZ-Expires header
+ query := req.URL.Query()
+ query.Set("X-Amz-Expires", strconv.FormatInt(int64(m.Expires/time.Second), 10))
+ req.URL.RawQuery = query.Encode()
+
+ return next.HandleBuild(ctx, in)
+}