diff --git a/feature/s3/transfermanager/api_op_UploadObject.go b/feature/s3/transfermanager/api_op_UploadObject.go index 3e8d978153c..65a88a9b940 100644 --- a/feature/s3/transfermanager/api_op_UploadObject.go +++ b/feature/s3/transfermanager/api_op_UploadObject.go @@ -6,6 +6,7 @@ import ( "fmt" "io" "log" + "net/http" "sort" "sync" "time" @@ -874,8 +875,11 @@ func (u *uploader) singleUpload(ctx context.Context, r io.Reader, sz int, cleanU params := u.in.mapSingleUploadInput(r, u.options.ChecksumAlgorithm) objectSize := int64(sz) + var loc recordLocationClient + opts := append(clientOptions, loc.WrapClient()) + u.progressEmitter.Start(ctx, u.in, objectSize) - out, err := u.options.S3.PutObject(ctx, params, clientOptions...) + out, err := u.options.S3.PutObject(ctx, params, opts...) if err != nil { u.progressEmitter.Failed(ctx, err) return nil, err @@ -883,6 +887,7 @@ func (u *uploader) singleUpload(ctx context.Context, r io.Reader, sz int, cleanU var output UploadObjectOutput output.mapFromPutObjectOutput(out, u.in.Bucket, u.in.Key, objectSize) + output.Location = nzstring(loc.location) u.progressEmitter.BytesTransferred(ctx, objectSize) u.progressEmitter.Complete(ctx, &output) @@ -962,9 +967,15 @@ func (cp completedParts) Swap(i, j int) { func (u *multiUploader) upload(ctx context.Context, firstBuf io.Reader, firstBuflen int, cleanup func(), clientOptions ...func(*s3.Options)) (*UploadObjectOutput, error) { params := u.uploader.in.mapCreateMultipartUploadInput(u.options.ChecksumAlgorithm) - // Create a multipart + // We are **ignoring** the output.Location here for backwards compat. + // + // In output.Location S3 URL-encodes the key (e.g. "a/b" -> "a%2Fb"). v1 + // (feature/s3/manager) used recordLocationClient which did not do that. We + // are electing to preserve that behavior here. + var loc recordLocationClient u.progressEmitter.Start(ctx, u.in, u.objectSize) - resp, err := u.uploader.options.S3.CreateMultipartUpload(ctx, params, clientOptions...) + resp, err := u.uploader.options.S3.CreateMultipartUpload(ctx, params, + append(clientOptions, loc.WrapClient())...) if err != nil { cleanup() u.progressEmitter.Failed(ctx, err) @@ -1026,6 +1037,7 @@ func (u *multiUploader) upload(ctx context.Context, firstBuf io.Reader, firstBuf var out UploadObjectOutput out.mapFromCompleteMultipartUploadOutput(completeOut, params.Bucket, u.uploadID, u.progressEmitter.bytesTransferred.Load(), u.parts) + out.Location = nzstring(loc.location) u.progressEmitter.Complete(ctx, &out) return &out, nil @@ -1166,3 +1178,34 @@ func getOrAddRequestUserAgent(stack *smithymiddleware.Stack) (*middleware.Reques return ua, nil } + +type httpClient interface { + Do(r *http.Request) (*http.Response, error) +} + +type recordLocationClient struct { + httpClient + location string +} + +func (c *recordLocationClient) WrapClient() func(o *s3.Options) { + return func(o *s3.Options) { + c.httpClient = o.HTTPClient + o.HTTPClient = c + } +} + +func (c *recordLocationClient) Do(r *http.Request) (resp *http.Response, err error) { + resp, err = c.httpClient.Do(r) + if err != nil { + return resp, err + } + + if resp.Request != nil && resp.Request.URL != nil { + url := *resp.Request.URL + url.RawQuery = "" + c.location = url.String() + } + + return resp, err +} diff --git a/feature/s3/transfermanager/api_op_UploadObject_test.go b/feature/s3/transfermanager/api_op_UploadObject_test.go index e519ddb00e8..ba8783b06f1 100644 --- a/feature/s3/transfermanager/api_op_UploadObject_test.go +++ b/feature/s3/transfermanager/api_op_UploadObject_test.go @@ -59,6 +59,10 @@ func TestUploadOrderMulti(t *testing.T) { t.Errorf("expect %q, got %q", "VERSION-ID", aws.ToString(resp.VersionID)) } + if e, a := "https://mock.amazonaws.com/key", aws.ToString(resp.Location); e != a { + t.Errorf("expect %q, got %q", e, a) + } + // Validate input values // UploadPart @@ -334,6 +338,10 @@ func TestUploadOrderSingle(t *testing.T) { t.Errorf("expect %q, got %q", e, aws.ToString(resp.VersionID)) } + if e, a := "https://mock.amazonaws.com/key", aws.ToString(resp.Location); e != a { + t.Errorf("expect %q, got %q", e, a) + } + if len(aws.ToString(resp.UploadID)) > 0 { t.Errorf("expect empty string, got %q", aws.ToString(resp.UploadID)) } diff --git a/feature/s3/transfermanager/setup_integ_test.go b/feature/s3/transfermanager/setup_integ_test.go index 910ecd177c2..0b82ec8c206 100644 --- a/feature/s3/transfermanager/setup_integ_test.go +++ b/feature/s3/transfermanager/setup_integ_test.go @@ -48,7 +48,7 @@ var s3TransferManagerClient *Client var stsClient *sts.Client // http client setting to use for integ testing -var httpClient *http.Client +var testHTTPClient *http.Client var region = "us-west-2" @@ -86,7 +86,7 @@ func TestMain(m *testing.M) { flag.BoolVar(&verifyTLS, "verify-tls", true, "verify server TLS certificate") flag.Parse() - httpClient = &http.Client{ + testHTTPClient = &http.Client{ Transport: &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: verifyTLS}, }, @@ -101,7 +101,7 @@ func TestMain(m *testing.M) { } // assign the http client - cfg.HTTPClient = httpClient + cfg.HTTPClient = testHTTPClient // create a s3 client s3cfg := cfg.Copy()