From 323717bcf1b2a62a9791dd74a2d205362daa298b Mon Sep 17 00:00:00 2001 From: niksis02 Date: Mon, 12 May 2025 23:30:47 +0400 Subject: [PATCH] fix: fixes the LastModified date formatting in CopyObject result. Fixes #1276 Creates the custom `s3response.CopyObjectOutput` type to handle the `LastModified` date property formatting correctly. It uses `time.RFC3339` to format the date to match the format that s3 uses. --- backend/azure/azure.go | 40 +++++++------- backend/backend.go | 6 +- backend/posix/posix.go | 80 +++++++++++++-------------- backend/s3proxy/s3.go | 32 ++++++++++- s3api/controllers/backend_moq_test.go | 6 +- s3api/controllers/base_test.go | 6 +- s3response/s3response.go | 33 ++++++++--- 7 files changed, 124 insertions(+), 79 deletions(-) diff --git a/backend/azure/azure.go b/backend/azure/azure.go index 2c6e7db..46a7cc3 100644 --- a/backend/azure/azure.go +++ b/backend/azure/azure.go @@ -823,14 +823,14 @@ func (az *Azure) DeleteObjects(ctx context.Context, input *s3.DeleteObjectsInput }, nil } -func (az *Azure) CopyObject(ctx context.Context, input s3response.CopyObjectInput) (*s3.CopyObjectOutput, error) { +func (az *Azure) CopyObject(ctx context.Context, input s3response.CopyObjectInput) (s3response.CopyObjectOutput, error) { dstClient, err := az.getBlobClient(*input.Bucket, *input.Key) if err != nil { - return nil, err + return s3response.CopyObjectOutput{}, err } if strings.Join([]string{*input.Bucket, *input.Key}, "/") == *input.CopySource { if input.MetadataDirective != types.MetadataDirectiveReplace { - return nil, s3err.GetAPIError(s3err.ErrInvalidCopyDest) + return s3response.CopyObjectOutput{}, s3err.GetAPIError(s3err.ErrInvalidCopyDest) } // Set object meta http headers @@ -842,7 +842,7 @@ func (az *Azure) CopyObject(ctx context.Context, input s3response.CopyObjectInpu BlobContentType: input.ContentType, }, nil) if err != nil { - return nil, azureErrToS3Err(err) + return s3response.CopyObjectOutput{}, azureErrToS3Err(err) } meta := input.Metadata @@ -857,14 +857,14 @@ func (az *Azure) CopyObject(ctx context.Context, input s3response.CopyObjectInpu // Set object metadata _, err = dstClient.SetMetadata(ctx, parseMetadata(meta), nil) if err != nil { - return nil, azureErrToS3Err(err) + return s3response.CopyObjectOutput{}, azureErrToS3Err(err) } // Set object legal hold if input.ObjectLockLegalHoldStatus != "" { err = az.PutObjectLegalHold(ctx, *input.Bucket, *input.Key, "", input.ObjectLockLegalHoldStatus == types.ObjectLockLegalHoldStatusOn) if err != nil { - return nil, azureErrToS3Err(err) + return s3response.CopyObjectOutput{}, azureErrToS3Err(err) } } // Set object retention @@ -878,11 +878,11 @@ func (az *Azure) CopyObject(ctx context.Context, input s3response.CopyObjectInpu retParsed, err := json.Marshal(retention) if err != nil { - return nil, fmt.Errorf("parse object retention: %w", err) + return s3response.CopyObjectOutput{}, fmt.Errorf("parse object retention: %w", err) } err = az.PutObjectRetention(ctx, *input.Bucket, *input.Key, "", true, retParsed) if err != nil { - return nil, azureErrToS3Err(err) + return s3response.CopyObjectOutput{}, azureErrToS3Err(err) } } @@ -890,16 +890,16 @@ func (az *Azure) CopyObject(ctx context.Context, input s3response.CopyObjectInpu if input.TaggingDirective == types.TaggingDirectiveReplace { tags, err := backend.ParseObjectTags(getString(input.Tagging)) if err != nil { - return nil, err + return s3response.CopyObjectOutput{}, err } _, err = dstClient.SetTags(ctx, tags, nil) if err != nil { - return nil, azureErrToS3Err(err) + return s3response.CopyObjectOutput{}, azureErrToS3Err(err) } } - return &s3.CopyObjectOutput{ - CopyObjectResult: &types.CopyObjectResult{ + return s3response.CopyObjectOutput{ + CopyObjectResult: &s3response.CopyObjectResult{ LastModified: res.LastModified, ETag: (*string)(res.ETag), }, @@ -908,13 +908,13 @@ func (az *Azure) CopyObject(ctx context.Context, input s3response.CopyObjectInpu srcBucket, srcObj, _, err := backend.ParseCopySource(*input.CopySource) if err != nil { - return nil, err + return s3response.CopyObjectOutput{}, err } // Get the source object downloadResp, err := az.client.DownloadStream(ctx, srcBucket, srcObj, nil) if err != nil { - return nil, azureErrToS3Err(err) + return s3response.CopyObjectOutput{}, azureErrToS3Err(err) } pInput := s3response.PutObjectInput{ @@ -952,28 +952,28 @@ func (az *Azure) CopyObject(ctx context.Context, input s3response.CopyObjectInpu // Create the destination object resp, err := az.PutObject(ctx, pInput) if err != nil { - return nil, err + return s3response.CopyObjectOutput{}, err } // Copy the object tagging, if tagging directive is "COPY" if input.TaggingDirective == types.TaggingDirectiveCopy { srcClient, err := az.getBlobClient(srcBucket, srcObj) if err != nil { - return nil, err + return s3response.CopyObjectOutput{}, err } res, err := srcClient.GetTags(ctx, nil) if err != nil { - return nil, azureErrToS3Err(err) + return s3response.CopyObjectOutput{}, azureErrToS3Err(err) } _, err = dstClient.SetTags(ctx, parseAzTags(res.BlobTagSet), nil) if err != nil { - return nil, azureErrToS3Err(err) + return s3response.CopyObjectOutput{}, azureErrToS3Err(err) } } - return &s3.CopyObjectOutput{ - CopyObjectResult: &types.CopyObjectResult{ + return s3response.CopyObjectOutput{ + CopyObjectResult: &s3response.CopyObjectResult{ ETag: &resp.ETag, }, }, nil diff --git a/backend/backend.go b/backend/backend.go index 1ddfc7a..de9ea2b 100644 --- a/backend/backend.go +++ b/backend/backend.go @@ -65,7 +65,7 @@ type Backend interface { GetObject(context.Context, *s3.GetObjectInput) (*s3.GetObjectOutput, error) GetObjectAcl(context.Context, *s3.GetObjectAclInput) (*s3.GetObjectAclOutput, error) GetObjectAttributes(context.Context, *s3.GetObjectAttributesInput) (s3response.GetObjectAttributesResponse, error) - CopyObject(context.Context, s3response.CopyObjectInput) (*s3.CopyObjectOutput, error) + CopyObject(context.Context, s3response.CopyObjectInput) (s3response.CopyObjectOutput, error) ListObjects(context.Context, *s3.ListObjectsInput) (s3response.ListObjectsResult, error) ListObjectsV2(context.Context, *s3.ListObjectsV2Input) (s3response.ListObjectsV2Result, error) DeleteObject(context.Context, *s3.DeleteObjectInput) (*s3.DeleteObjectOutput, error) @@ -200,8 +200,8 @@ func (BackendUnsupported) GetObjectAcl(context.Context, *s3.GetObjectAclInput) ( func (BackendUnsupported) GetObjectAttributes(context.Context, *s3.GetObjectAttributesInput) (s3response.GetObjectAttributesResponse, error) { return s3response.GetObjectAttributesResponse{}, s3err.GetAPIError(s3err.ErrNotImplemented) } -func (BackendUnsupported) CopyObject(context.Context, s3response.CopyObjectInput) (*s3.CopyObjectOutput, error) { - return nil, s3err.GetAPIError(s3err.ErrNotImplemented) +func (BackendUnsupported) CopyObject(context.Context, s3response.CopyObjectInput) (s3response.CopyObjectOutput, error) { + return s3response.CopyObjectOutput{}, s3err.GetAPIError(s3err.ErrNotImplemented) } func (BackendUnsupported) ListObjects(context.Context, *s3.ListObjectsInput) (s3response.ListObjectsResult, error) { return s3response.ListObjectsResult{}, s3err.GetAPIError(s3err.ErrNotImplemented) diff --git a/backend/posix/posix.go b/backend/posix/posix.go index 4c66b45..4ca6a52 100644 --- a/backend/posix/posix.go +++ b/backend/posix/posix.go @@ -3855,51 +3855,51 @@ func (p *Posix) GetObjectAttributes(ctx context.Context, input *s3.GetObjectAttr }, nil } -func (p *Posix) CopyObject(ctx context.Context, input s3response.CopyObjectInput) (*s3.CopyObjectOutput, error) { +func (p *Posix) CopyObject(ctx context.Context, input s3response.CopyObjectInput) (s3response.CopyObjectOutput, error) { if input.Bucket == nil { - return nil, s3err.GetAPIError(s3err.ErrInvalidBucketName) + return s3response.CopyObjectOutput{}, s3err.GetAPIError(s3err.ErrInvalidBucketName) } if input.Key == nil { - return nil, s3err.GetAPIError(s3err.ErrInvalidCopyDest) + return s3response.CopyObjectOutput{}, s3err.GetAPIError(s3err.ErrInvalidCopyDest) } if input.CopySource == nil { - return nil, s3err.GetAPIError(s3err.ErrInvalidCopySource) + return s3response.CopyObjectOutput{}, s3err.GetAPIError(s3err.ErrInvalidCopySource) } if input.ExpectedBucketOwner == nil { - return nil, s3err.GetAPIError(s3err.ErrInvalidRequest) + return s3response.CopyObjectOutput{}, s3err.GetAPIError(s3err.ErrInvalidRequest) } srcBucket, srcObject, srcVersionId, err := backend.ParseCopySource(*input.CopySource) if err != nil { - return nil, err + return s3response.CopyObjectOutput{}, err } dstBucket := *input.Bucket dstObject := *input.Key _, err = os.Stat(srcBucket) if errors.Is(err, fs.ErrNotExist) { - return nil, s3err.GetAPIError(s3err.ErrNoSuchBucket) + return s3response.CopyObjectOutput{}, s3err.GetAPIError(s3err.ErrNoSuchBucket) } if err != nil { - return nil, fmt.Errorf("stat bucket: %w", err) + return s3response.CopyObjectOutput{}, fmt.Errorf("stat bucket: %w", err) } vStatus, err := p.getBucketVersioningStatus(ctx, srcBucket) if err != nil { - return nil, err + return s3response.CopyObjectOutput{}, err } vEnabled := p.isBucketVersioningEnabled(vStatus) if srcVersionId != "" { if !p.versioningEnabled() || !vEnabled { - return nil, s3err.GetAPIError(s3err.ErrInvalidVersionId) + return s3response.CopyObjectOutput{}, s3err.GetAPIError(s3err.ErrInvalidVersionId) } vId, err := p.meta.RetrieveAttribute(nil, srcBucket, srcObject, versionIdKey) if errors.Is(err, fs.ErrNotExist) || errors.Is(err, syscall.ENOTDIR) { - return nil, s3err.GetAPIError(s3err.ErrNoSuchKey) + return s3response.CopyObjectOutput{}, s3err.GetAPIError(s3err.ErrNoSuchKey) } if err != nil && !errors.Is(err, meta.ErrNoSuchKey) { - return nil, fmt.Errorf("get src object version id: %w", err) + return s3response.CopyObjectOutput{}, fmt.Errorf("get src object version id: %w", err) } if string(vId) != srcVersionId { @@ -3910,37 +3910,37 @@ func (p *Posix) CopyObject(ctx context.Context, input s3response.CopyObjectInput _, err = os.Stat(dstBucket) if errors.Is(err, fs.ErrNotExist) { - return nil, s3err.GetAPIError(s3err.ErrNoSuchBucket) + return s3response.CopyObjectOutput{}, s3err.GetAPIError(s3err.ErrNoSuchBucket) } if err != nil { - return nil, fmt.Errorf("stat bucket: %w", err) + return s3response.CopyObjectOutput{}, fmt.Errorf("stat bucket: %w", err) } objPath := joinPathWithTrailer(srcBucket, srcObject) f, err := os.Open(objPath) if errors.Is(err, fs.ErrNotExist) || errors.Is(err, syscall.ENOTDIR) { if p.versioningEnabled() && vEnabled { - return nil, s3err.GetAPIError(s3err.ErrNoSuchVersion) + return s3response.CopyObjectOutput{}, s3err.GetAPIError(s3err.ErrNoSuchVersion) } - return nil, s3err.GetAPIError(s3err.ErrNoSuchKey) + return s3response.CopyObjectOutput{}, s3err.GetAPIError(s3err.ErrNoSuchKey) } if errors.Is(err, syscall.ENAMETOOLONG) { - return nil, s3err.GetAPIError(s3err.ErrKeyTooLong) + return s3response.CopyObjectOutput{}, s3err.GetAPIError(s3err.ErrKeyTooLong) } if err != nil { - return nil, fmt.Errorf("open object: %w", err) + return s3response.CopyObjectOutput{}, fmt.Errorf("open object: %w", err) } defer f.Close() fi, err := f.Stat() if err != nil { - return nil, fmt.Errorf("stat object: %w", err) + return s3response.CopyObjectOutput{}, fmt.Errorf("stat object: %w", err) } if strings.HasSuffix(srcObject, "/") && !fi.IsDir() { - return nil, s3err.GetAPIError(s3err.ErrNoSuchKey) + return s3response.CopyObjectOutput{}, s3err.GetAPIError(s3err.ErrNoSuchKey) } if !strings.HasSuffix(srcObject, "/") && fi.IsDir() { - return nil, s3err.GetAPIError(s3err.ErrNoSuchKey) + return s3response.CopyObjectOutput{}, s3err.GetAPIError(s3err.ErrNoSuchKey) } mdmap := make(map[string]string) @@ -3958,7 +3958,7 @@ func (p *Posix) CopyObject(ctx context.Context, input s3response.CopyObjectInput dstObjdPath := joinPathWithTrailer(dstBucket, dstObject) if dstObjdPath == objPath { if input.MetadataDirective == types.MetadataDirectiveCopy { - return &s3.CopyObjectOutput{}, s3err.GetAPIError(s3err.ErrInvalidCopyDest) + return s3response.CopyObjectOutput{}, s3err.GetAPIError(s3err.ErrInvalidCopyDest) } // Delete the object metadata @@ -3966,7 +3966,7 @@ func (p *Posix) CopyObject(ctx context.Context, input s3response.CopyObjectInput err := p.meta.DeleteAttribute(dstBucket, dstObject, fmt.Sprintf("%v.%v", metaHdr, k)) if err != nil && !errors.Is(err, meta.ErrNoSuchKey) { - return nil, fmt.Errorf("delete user metadata: %w", err) + return s3response.CopyObjectOutput{}, fmt.Errorf("delete user metadata: %w", err) } } // Store the new metadata @@ -3974,13 +3974,13 @@ func (p *Posix) CopyObject(ctx context.Context, input s3response.CopyObjectInput err := p.meta.StoreAttribute(nil, dstBucket, dstObject, fmt.Sprintf("%v.%v", metaHdr, k), []byte(v)) if err != nil { - return nil, fmt.Errorf("set user attr %q: %w", k, err) + return s3response.CopyObjectOutput{}, fmt.Errorf("set user attr %q: %w", k, err) } } checksums, err := p.retrieveChecksums(nil, dstBucket, dstObject) if err != nil && !errors.Is(err, meta.ErrNoSuchKey) { - return nil, fmt.Errorf("get obj checksums: %w", err) + return s3response.CopyObjectOutput{}, fmt.Errorf("get obj checksums: %w", err) } chType = checksums.Type @@ -3991,18 +3991,18 @@ func (p *Posix) CopyObject(ctx context.Context, input s3response.CopyObjectInput if checksums.Algorithm != input.ChecksumAlgorithm { f, err := os.Open(dstObjdPath) if err != nil { - return nil, fmt.Errorf("open obj file: %w", err) + return s3response.CopyObjectOutput{}, fmt.Errorf("open obj file: %w", err) } defer f.Close() hashReader, err := utils.NewHashReader(f, "", utils.HashType(strings.ToLower(string(input.ChecksumAlgorithm)))) if err != nil { - return nil, fmt.Errorf("initialize hash reader: %w", err) + return s3response.CopyObjectOutput{}, fmt.Errorf("initialize hash reader: %w", err) } _, err = hashReader.Read(nil) if err != nil { - return nil, fmt.Errorf("read err: %w", err) + return s3response.CopyObjectOutput{}, fmt.Errorf("read err: %w", err) } checksums = s3response.Checksum{} @@ -4032,7 +4032,7 @@ func (p *Posix) CopyObject(ctx context.Context, input s3response.CopyObjectInput err = p.storeChecksums(f, dstBucket, dstObject, checksums) if err != nil { - return nil, fmt.Errorf("store checksum: %w", err) + return s3response.CopyObjectOutput{}, fmt.Errorf("store checksum: %w", err) } } } @@ -4041,7 +4041,7 @@ func (p *Posix) CopyObject(ctx context.Context, input s3response.CopyObjectInput etag = string(b) vId, _ := p.meta.RetrieveAttribute(nil, dstBucket, dstObject, versionIdKey) if errors.Is(err, fs.ErrNotExist) || errors.Is(err, syscall.ENOTDIR) { - return nil, s3err.GetAPIError(s3err.ErrNoSuchKey) + return s3response.CopyObjectOutput{}, s3err.GetAPIError(s3err.ErrNoSuchKey) } version = backend.GetPtrFromString(string(vId)) @@ -4056,18 +4056,18 @@ func (p *Posix) CopyObject(ctx context.Context, input s3response.CopyObjectInput Expires: input.Expires, }) if err != nil { - return nil, err + return s3response.CopyObjectOutput{}, err } if input.TaggingDirective == types.TaggingDirectiveReplace { tags, err := backend.ParseObjectTags(getString(input.Tagging)) if err != nil { - return nil, err + return s3response.CopyObjectOutput{}, err } err = p.PutObjectTagging(ctx, dstBucket, dstObject, tags) if err != nil { - return nil, err + return s3response.CopyObjectOutput{}, err } } } else { @@ -4075,7 +4075,7 @@ func (p *Posix) CopyObject(ctx context.Context, input s3response.CopyObjectInput checksums, err := p.retrieveChecksums(f, srcBucket, srcObject) if err != nil && !errors.Is(err, meta.ErrNoSuchKey) { - return nil, fmt.Errorf("get obj checksum: %w", err) + return s3response.CopyObjectOutput{}, fmt.Errorf("get obj checksum: %w", err) } // If any checksum algorithm is provided, replace, otherwise @@ -4121,7 +4121,7 @@ func (p *Posix) CopyObject(ctx context.Context, input s3response.CopyObjectInput res, err := p.PutObject(ctx, putObjectInput) if err != nil { - return nil, err + return s3response.CopyObjectOutput{}, err } // copy the source object tagging after the destination object @@ -4129,12 +4129,12 @@ func (p *Posix) CopyObject(ctx context.Context, input s3response.CopyObjectInput if input.TaggingDirective == types.TaggingDirectiveCopy { tagging, err := p.meta.RetrieveAttribute(nil, srcBucket, srcObject, tagHdr) if err != nil && !errors.Is(err, meta.ErrNoSuchKey) { - return nil, fmt.Errorf("get source object tagging: %w", err) + return s3response.CopyObjectOutput{}, fmt.Errorf("get source object tagging: %w", err) } if err == nil { err := p.meta.StoreAttribute(nil, dstBucket, dstObject, tagHdr, tagging) if err != nil { - return nil, fmt.Errorf("set destination object tagging: %w", err) + return s3response.CopyObjectOutput{}, fmt.Errorf("set destination object tagging: %w", err) } } } @@ -4151,11 +4151,11 @@ func (p *Posix) CopyObject(ctx context.Context, input s3response.CopyObjectInput fi, err = os.Stat(dstObjdPath) if err != nil { - return nil, fmt.Errorf("stat dst object: %w", err) + return s3response.CopyObjectOutput{}, fmt.Errorf("stat dst object: %w", err) } - return &s3.CopyObjectOutput{ - CopyObjectResult: &types.CopyObjectResult{ + return s3response.CopyObjectOutput{ + CopyObjectResult: &s3response.CopyObjectResult{ ETag: &etag, LastModified: backend.GetTimePtr(fi.ModTime()), ChecksumCRC32: crc32, diff --git a/backend/s3proxy/s3.go b/backend/s3proxy/s3.go index 09ff40c..2ed6b36 100644 --- a/backend/s3proxy/s3.go +++ b/backend/s3proxy/s3.go @@ -1089,9 +1089,9 @@ func (s *S3Proxy) GetObjectAttributes(ctx context.Context, input *s3.GetObjectAt }, handleError(err) } -func (s *S3Proxy) CopyObject(ctx context.Context, input s3response.CopyObjectInput) (*s3.CopyObjectOutput, error) { +func (s *S3Proxy) CopyObject(ctx context.Context, input s3response.CopyObjectInput) (s3response.CopyObjectOutput, error) { if *input.Bucket == s.metaBucket { - return nil, s3err.GetAPIError(s3err.ErrAccessDenied) + return s3response.CopyObjectOutput{}, s3err.GetAPIError(s3err.ErrAccessDenied) } if input.CacheControl != nil && *input.CacheControl == "" { input.CacheControl = nil @@ -1227,7 +1227,33 @@ func (s *S3Proxy) CopyObject(ctx context.Context, input s3response.CopyObjectInp StorageClass: input.StorageClass, TaggingDirective: input.TaggingDirective, }) - return out, handleError(err) + if err != nil { + return s3response.CopyObjectOutput{}, handleError(err) + } + if out.CopyObjectResult == nil { + out.CopyObjectResult = &types.CopyObjectResult{} + } + return s3response.CopyObjectOutput{ + BucketKeyEnabled: out.BucketKeyEnabled, + CopyObjectResult: &s3response.CopyObjectResult{ + ChecksumCRC32: out.CopyObjectResult.ChecksumCRC32, + ChecksumCRC32C: out.CopyObjectResult.ChecksumCRC32C, + ChecksumCRC64NVME: out.CopyObjectResult.ChecksumCRC64NVME, + ChecksumSHA1: out.CopyObjectResult.ChecksumSHA1, + ChecksumSHA256: out.CopyObjectResult.ChecksumSHA256, + ChecksumType: out.CopyObjectResult.ChecksumType, + ETag: out.CopyObjectResult.ETag, + LastModified: out.CopyObjectResult.LastModified, + }, + CopySourceVersionId: out.CopySourceVersionId, + Expiration: out.Expiration, + SSECustomerAlgorithm: out.SSECustomerAlgorithm, + SSECustomerKeyMD5: out.SSECustomerKeyMD5, + SSEKMSEncryptionContext: out.SSEKMSEncryptionContext, + SSEKMSKeyId: out.SSEKMSKeyId, + ServerSideEncryption: out.ServerSideEncryption, + VersionId: out.VersionId, + }, handleError(err) } func (s *S3Proxy) ListObjects(ctx context.Context, input *s3.ListObjectsInput) (s3response.ListObjectsResult, error) { diff --git a/s3api/controllers/backend_moq_test.go b/s3api/controllers/backend_moq_test.go index 5a67724..5d2e9a2 100644 --- a/s3api/controllers/backend_moq_test.go +++ b/s3api/controllers/backend_moq_test.go @@ -32,7 +32,7 @@ var _ backend.Backend = &BackendMock{} // CompleteMultipartUploadFunc: func(contextMoqParam context.Context, completeMultipartUploadInput *s3.CompleteMultipartUploadInput) (s3response.CompleteMultipartUploadResult, string, error) { // panic("mock out the CompleteMultipartUpload method") // }, -// CopyObjectFunc: func(contextMoqParam context.Context, copyObjectInput s3response.CopyObjectInput) (*s3.CopyObjectOutput, error) { +// CopyObjectFunc: func(contextMoqParam context.Context, copyObjectInput s3response.CopyObjectInput) (s3response.CopyObjectOutput, error) { // panic("mock out the CopyObject method") // }, // CreateBucketFunc: func(contextMoqParam context.Context, createBucketInput *s3.CreateBucketInput, defaultACL []byte) error { @@ -202,7 +202,7 @@ type BackendMock struct { CompleteMultipartUploadFunc func(contextMoqParam context.Context, completeMultipartUploadInput *s3.CompleteMultipartUploadInput) (s3response.CompleteMultipartUploadResult, string, error) // CopyObjectFunc mocks the CopyObject method. - CopyObjectFunc func(contextMoqParam context.Context, copyObjectInput s3response.CopyObjectInput) (*s3.CopyObjectOutput, error) + CopyObjectFunc func(contextMoqParam context.Context, copyObjectInput s3response.CopyObjectInput) (s3response.CopyObjectOutput, error) // CreateBucketFunc mocks the CreateBucket method. CreateBucketFunc func(contextMoqParam context.Context, createBucketInput *s3.CreateBucketInput, defaultACL []byte) error @@ -940,7 +940,7 @@ func (mock *BackendMock) CompleteMultipartUploadCalls() []struct { } // CopyObject calls CopyObjectFunc. -func (mock *BackendMock) CopyObject(contextMoqParam context.Context, copyObjectInput s3response.CopyObjectInput) (*s3.CopyObjectOutput, error) { +func (mock *BackendMock) CopyObject(contextMoqParam context.Context, copyObjectInput s3response.CopyObjectInput) (s3response.CopyObjectOutput, error) { if mock.CopyObjectFunc == nil { panic("BackendMock.CopyObjectFunc: method is nil but Backend.CopyObject was just called") } diff --git a/s3api/controllers/base_test.go b/s3api/controllers/base_test.go index f8e60c3..648a297 100644 --- a/s3api/controllers/base_test.go +++ b/s3api/controllers/base_test.go @@ -974,9 +974,9 @@ func TestS3ApiController_PutActions(t *testing.T) { PutObjectAclFunc: func(context.Context, *s3.PutObjectAclInput) error { return nil }, - CopyObjectFunc: func(context.Context, s3response.CopyObjectInput) (*s3.CopyObjectOutput, error) { - return &s3.CopyObjectOutput{ - CopyObjectResult: &types.CopyObjectResult{}, + CopyObjectFunc: func(context.Context, s3response.CopyObjectInput) (s3response.CopyObjectOutput, error) { + return s3response.CopyObjectOutput{ + CopyObjectResult: &s3response.CopyObjectResult{}, }, nil }, PutObjectFunc: func(context.Context, s3response.PutObjectInput) (s3response.PutObjectOutput, error) { diff --git a/s3response/s3response.go b/s3response/s3response.go index be0c944..43435a6 100644 --- a/s3response/s3response.go +++ b/s3response/s3response.go @@ -344,23 +344,42 @@ type CanonicalUser struct { DisplayName string } +type CopyObjectOutput struct { + BucketKeyEnabled *bool + CopyObjectResult *CopyObjectResult + CopySourceVersionId *string + Expiration *string + SSECustomerAlgorithm *string + SSECustomerKeyMD5 *string + SSEKMSEncryptionContext *string + SSEKMSKeyId *string + ServerSideEncryption types.ServerSideEncryption + VersionId *string +} + type CopyObjectResult struct { - XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ CopyObjectResult" json:"-"` - LastModified time.Time - ETag string - CopySourceVersionId string `xml:"-"` + XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ CopyObjectResult" json:"-"` + ChecksumCRC32 *string + ChecksumCRC32C *string + ChecksumCRC64NVME *string + ChecksumSHA1 *string + ChecksumSHA256 *string + ChecksumType types.ChecksumType + ETag *string + LastModified *time.Time } func (r CopyObjectResult) MarshalXML(e *xml.Encoder, start xml.StartElement) error { type Alias CopyObjectResult aux := &struct { - LastModified string `xml:"LastModified"` + LastModified string `xml:"LastModified,omitempty"` *Alias }{ Alias: (*Alias)(&r), } - - aux.LastModified = r.LastModified.UTC().Format(iso8601TimeFormat) + if r.LastModified != nil { + aux.LastModified = r.LastModified.UTC().Format(time.RFC3339) + } return e.EncodeElement(aux, start) }