mirror of
https://github.com/versity/versitygw.git
synced 2026-07-20 15:02:23 +00:00
9f786b3c2c
Fixes #2123 Fixes #2120 Fixes #2116 Fixes #2111 Fixes #2108 Fixes #2086 Fixes #2085 Fixes #2083 Fixes #2081 Fixes #2080 Fixes #2073 Fixes #2072 Fixes #2071 Fixes #2069 Fixes #2044 Fixes #2043 Fixes #2042 Fixes #2041 Fixes #2040 Fixes #2039 Fixes #2036 Fixes #2035 Fixes #2034 Fixes #2028 Fixes #2020 Fixes #1842 Fixes #1810 Fixes #1780 Fixes #1775 Fixes #1736 Fixes #1705 Fixes #1663 Fixes #1645 Fixes #1583 Fixes #1526 Fixes #1514 Fixes #1493 Fixes #1487 Fixes #959 Fixes #779 Closes #823 Closes #85 Refactor global S3 error handling around structured error types and centralized XML response generation. All S3 errors now share the common APIError base for the fields every error has: Code, HTTP status code, and Message. Non-traditional errors that need AWS-compatible XML fields now have dedicated typed errors in the s3err package. Each typed error implements the shared S3Error behavior so controllers and middleware can handle errors consistently while still emitting error-specific XML fields. Add a dedicated InvalidArgumentError type because InvalidArgument is used widely across request validation, auth, copy source handling, object lock validation, multipart validation, and header parsing. The new InvalidArgument path uses explicit InvalidArgErrorCode constants with predefined descriptions and ArgumentName values, keeping call sites readable while preserving the correct InvalidArgument XML shape and optional ArgumentValue. New structured errors added in s3err: - `AccessForbiddenError`: Method, ResourceType - `BadDigestError`: CalculatedDigest, ExpectedDigest - `BucketError`: BucketName - `ContentSHA256MismatchError`: ClientComputedContentSHA256, S3ComputedContentSHA256 - `EntityTooLargeError`: ProposedSize, MaxSizeAllowed - `EntityTooSmallError`: ProposedSize, MinSizeAllowed - `ExpiredPresignedURLError`: ServerTime, XAmzExpires, Expires - `InvalidAccessKeyIdError`: AWSAccessKeyId - `InvalidArgumentError`: Description, ArgumentName, ArgumentValue - `InvalidChunkSizeError`: Chunk, BadChunkSize - `InvalidDigestError`: ContentMD5 - `InvalidLocationConstraintError`: LocationConstraint - `InvalidPartError`: UploadId, PartNumber, ETag - `InvalidRangeError`: RangeRequested, ActualObjectSize - `InvalidTagError`: TagKey, TagValue - `KeyTooLongError`: Size, MaxSizeAllowed - `MetadataTooLargeError`: Size, MaxSizeAllowed - `MethodNotAllowedError`: Method, ResourceType, AllowedMethods - `NoSuchUploadError`: UploadId - `NoSuchVersionError`: Key, VersionId - `NotImplementedError`: Header, AdditionalMessage - `PreconditionFailedError`: Condition - `RequestTimeTooSkewedError`: RequestTime, ServerTime, MaxAllowedSkewMilliseconds - `SignatureDoesNotMatchError`: AWSAccessKeyId, StringToSign, SignatureProvided, StringToSignBytes, CanonicalRequest, CanonicalRequestBytes Fix CompleteMultipartUpload validation in the Azure backend so missing or empty `ETag` values return the appropriate S3 error instead of allowing a gateway panic. Fix presigned authentication expiration validation to compare server time in `UTC`, matching the `UTC` timestamp used by presigned URL signing. Add request ID and host ID support across S3 requests. Each request now receives AWS S3-like identifiers, returned in response headers as `x-amz-request-id` and `x-amz-id-2` and included in all XML error responses as RequestId and HostId. The generated ID structure is designed to resemble AWS S3 request IDs and host IDs. The request signature calculation/validation for streaming uploads was previously delayed until the request body was fully read, both for Authorization header authentication and presigned URLs. Now, the signature is validated immediately in the authorization middlewares without reading the request body, since the signature calculation itself does not depend on the request body. Instead, only the `x-amz-content-sha256` SHA-256 hash calculation is delayed.
3946 lines
110 KiB
Go
3946 lines
110 KiB
Go
// Copyright 2023 Versity Software
|
|
// This file is licensed under the Apache License, Version 2.0
|
|
// (the "License"); you may not use this file except in compliance
|
|
// with the License. You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing,
|
|
// software distributed under the License is distributed on an
|
|
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
// KIND, either express or implied. See the License for the
|
|
// specific language governing permissions and limitations
|
|
// under the License.
|
|
|
|
package integration
|
|
|
|
import (
|
|
"context"
|
|
"crypto/sha256"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/aws/aws-sdk-go-v2/service/s3"
|
|
"github.com/aws/aws-sdk-go-v2/service/s3/types"
|
|
"github.com/versity/versitygw/s3err"
|
|
)
|
|
|
|
func Versioning_DeleteBucket_not_empty(s *S3Conf) error {
|
|
testName := "Versioning_DeleteBucket_not_empty"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
obj := "my-obj"
|
|
_, err := createObjVersions(s3client, bucket, obj, 2)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
_, err = s3client.DeleteBucket(ctx, &s3.DeleteBucketInput{
|
|
Bucket: &bucket,
|
|
})
|
|
cancel()
|
|
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrVersionedBucketNotEmpty)); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}, withVersioning(types.BucketVersioningStatusEnabled))
|
|
}
|
|
|
|
func Versioning_PutObject_suspended_null_versionId_obj(s *S3Conf) error {
|
|
testName := "Versioning_PutObject_suspended_null_versionId_obj"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
obj := "my-obj"
|
|
out, err := putObjectWithData(1222, &s3.PutObjectInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
}, s3client)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if getString(out.res.VersionId) != nullVersionId {
|
|
return fmt.Errorf("expected the uploaded object versionId to be %v, instead got %v",
|
|
nullVersionId, getString(out.res.VersionId))
|
|
}
|
|
|
|
return nil
|
|
}, withVersioning(types.BucketVersioningStatusSuspended))
|
|
}
|
|
|
|
func Versioning_PutObject_null_versionId_obj(s *S3Conf) error {
|
|
testName := "Versioning_PutObject_null_versionId_obj"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
obj, lgth := "my-obj", int64(1234)
|
|
out, err := putObjectWithData(lgth, &s3.PutObjectInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
}, s3client)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Enable bucket versioning
|
|
err = putBucketVersioningStatus(s3client, bucket, types.BucketVersioningStatusEnabled)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
versions, err := createObjVersions(s3client, bucket, obj, 4)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
versions = append(versions, types.ObjectVersion{
|
|
ETag: out.res.ETag,
|
|
IsLatest: getBoolPtr(false),
|
|
Key: &obj,
|
|
Size: &lgth,
|
|
VersionId: &nullVersionId,
|
|
StorageClass: types.ObjectVersionStorageClassStandard,
|
|
})
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
res, err := s3client.ListObjectVersions(ctx, &s3.ListObjectVersionsInput{
|
|
Bucket: &bucket,
|
|
})
|
|
cancel()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if !compareVersions(versions, res.Versions) {
|
|
return fmt.Errorf("expected the listed versions to be %v, instead got %v",
|
|
versions, res.Versions)
|
|
}
|
|
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func Versioning_PutObject_overwrite_null_versionId_obj(s *S3Conf) error {
|
|
testName := "Versioning_PutObject_overwrite_null_versionId_obj"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
obj := "my-obj"
|
|
_, err := putObjectWithData(int64(1233), &s3.PutObjectInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
}, s3client)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Enable bucket versioning
|
|
err = putBucketVersioningStatus(s3client, bucket, types.BucketVersioningStatusEnabled)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
versions, err := createObjVersions(s3client, bucket, obj, 4)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Set bucket versioning status to Suspended
|
|
err = putBucketVersioningStatus(s3client, bucket, types.BucketVersioningStatusSuspended)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
lgth := int64(3200)
|
|
out, err := putObjectWithData(lgth, &s3.PutObjectInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
}, s3client)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if getString(out.res.VersionId) != nullVersionId {
|
|
return fmt.Errorf("expected the uploaded object versionId to be %v, insted got %v",
|
|
nullVersionId, getString(out.res.VersionId))
|
|
}
|
|
|
|
versions[0].IsLatest = getBoolPtr(false)
|
|
|
|
versions = append([]types.ObjectVersion{
|
|
{
|
|
ETag: out.res.ETag,
|
|
IsLatest: getBoolPtr(true),
|
|
Key: &obj,
|
|
Size: &lgth,
|
|
VersionId: &nullVersionId,
|
|
StorageClass: types.ObjectVersionStorageClassStandard,
|
|
ChecksumType: out.res.ChecksumType,
|
|
},
|
|
}, versions...)
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
res, err := s3client.ListObjectVersions(ctx, &s3.ListObjectVersionsInput{
|
|
Bucket: &bucket,
|
|
})
|
|
cancel()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if !compareVersions(versions, res.Versions) {
|
|
return fmt.Errorf("expected the listed versions to be %v, instead got %v",
|
|
versions, res.Versions)
|
|
}
|
|
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func Versioning_PutObject_success(s *S3Conf) error {
|
|
testName := "Versioning_PutObject_success"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
res, err := s3client.PutObject(ctx, &s3.PutObjectInput{
|
|
Bucket: &bucket,
|
|
Key: getPtr("my-obj"),
|
|
})
|
|
cancel()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if res.VersionId == nil || *res.VersionId == "" {
|
|
return fmt.Errorf("expected the versionId to be returned")
|
|
}
|
|
|
|
return nil
|
|
}, withVersioning(types.BucketVersioningStatusEnabled))
|
|
}
|
|
|
|
func Versioning_CopyObject_invalid_versionId(s *S3Conf) error {
|
|
testName := "Versioning_CopyObject_invalid_versionId"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
dstObj, srcObj := "dst-obj", "src-obj"
|
|
|
|
srcObjLen := int64(2345)
|
|
_, err := putObjectWithData(srcObjLen, &s3.PutObjectInput{
|
|
Bucket: &bucket,
|
|
Key: &srcObj,
|
|
}, s3client)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
_, err = s3client.CopyObject(ctx, &s3.CopyObjectInput{
|
|
Bucket: &bucket,
|
|
Key: &dstObj,
|
|
CopySource: getPtr(fmt.Sprintf("%v/%v?versionId=invalid_versionId", bucket, srcObj)),
|
|
})
|
|
cancel()
|
|
return checkApiErr(err, s3err.GetInvalidArgumentErr(s3err.InvalidArgVersionId, "invalid_versionId"))
|
|
}, withVersioning(types.BucketVersioningStatusEnabled))
|
|
}
|
|
|
|
func Versioning_CopyObject_success(s *S3Conf) error {
|
|
testName := "Versioning_CopyObject_success"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
dstObj := "dst-obj"
|
|
srcBucket, srcObj := getBucketName(), "src-obj"
|
|
|
|
if err := setup(s, srcBucket); err != nil {
|
|
return err
|
|
}
|
|
|
|
dstObjVersions, err := createObjVersions(s3client, bucket, dstObj, 1)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
srcObjLen := int64(2345)
|
|
_, err = putObjectWithData(srcObjLen, &s3.PutObjectInput{
|
|
Bucket: &srcBucket,
|
|
Key: &srcObj,
|
|
}, s3client)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
out, err := s3client.CopyObject(ctx, &s3.CopyObjectInput{
|
|
Bucket: &bucket,
|
|
Key: &dstObj,
|
|
CopySource: getPtr(fmt.Sprintf("%v/%v", srcBucket, srcObj)),
|
|
})
|
|
cancel()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := teardown(s, srcBucket); err != nil {
|
|
return err
|
|
}
|
|
|
|
if out.VersionId == nil || *out.VersionId == "" {
|
|
return fmt.Errorf("expected non empty versionId in the result")
|
|
}
|
|
|
|
dstObjVersions[0].IsLatest = getBoolPtr(false)
|
|
versions := append([]types.ObjectVersion{
|
|
{
|
|
ETag: out.CopyObjectResult.ETag,
|
|
IsLatest: getBoolPtr(true),
|
|
Key: &dstObj,
|
|
Size: &srcObjLen,
|
|
VersionId: out.VersionId,
|
|
StorageClass: types.ObjectVersionStorageClassStandard,
|
|
ChecksumType: out.CopyObjectResult.ChecksumType,
|
|
},
|
|
}, dstObjVersions...)
|
|
|
|
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
|
|
res, err := s3client.ListObjectVersions(ctx, &s3.ListObjectVersionsInput{
|
|
Bucket: &bucket,
|
|
})
|
|
cancel()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if !compareVersions(versions, res.Versions) {
|
|
return fmt.Errorf("expected the resulting versions to be %v, instead got %v",
|
|
versions, res.Versions)
|
|
}
|
|
|
|
return nil
|
|
}, withVersioning(types.BucketVersioningStatusEnabled))
|
|
}
|
|
|
|
func Versioning_CopyObject_non_existing_version_id(s *S3Conf) error {
|
|
testName := "Versioning_CopyObject_non_existing_version_id"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
dstBucket, dstObj := getBucketName(), "my-obj"
|
|
srcObj := "my-obj"
|
|
|
|
if err := setup(s, dstBucket); err != nil {
|
|
return err
|
|
}
|
|
|
|
_, err := createObjVersions(s3client, bucket, srcObj, 1)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
_, err = s3client.CopyObject(ctx, &s3.CopyObjectInput{
|
|
Bucket: &dstBucket,
|
|
Key: &dstObj,
|
|
CopySource: getPtr(fmt.Sprintf("%v/%v?versionId=01BX5ZZKBKACTAV9WEVGEMMVRZ",
|
|
bucket, srcObj)),
|
|
})
|
|
cancel()
|
|
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchVersion)); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := teardown(s, dstBucket); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}, withVersioning(types.BucketVersioningStatusEnabled))
|
|
}
|
|
|
|
func Versioning_CopyObject_from_an_object_version(s *S3Conf) error {
|
|
testName := "Versioning_CopyObject_from_an_object_version"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
srcBucket, srcObj, dstObj := getBucketName(), "my-obj", "my-dst-obj"
|
|
if err := setup(s, srcBucket, withVersioning(types.BucketVersioningStatusEnabled)); err != nil {
|
|
return err
|
|
}
|
|
|
|
srcObjVersions, err := createObjVersions(s3client, srcBucket, srcObj, 1)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
srcObjVersion := srcObjVersions[0]
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
out, err := s3client.CopyObject(ctx, &s3.CopyObjectInput{
|
|
Bucket: &bucket,
|
|
Key: &dstObj,
|
|
CopySource: getPtr(fmt.Sprintf("%v/%v?versionId=%v", srcBucket, srcObj, *srcObjVersion.VersionId)),
|
|
})
|
|
cancel()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := teardown(s, srcBucket); err != nil {
|
|
return err
|
|
}
|
|
|
|
if out.VersionId == nil || *out.VersionId == "" {
|
|
return fmt.Errorf("expected non empty versionId")
|
|
}
|
|
if out.CopySourceVersionId == nil {
|
|
return fmt.Errorf("expected non nil CopySourceVersionId")
|
|
}
|
|
if *out.CopySourceVersionId != *srcObjVersion.VersionId {
|
|
return fmt.Errorf("expected the SourceVersionId to be %v, instead got %v",
|
|
*srcObjVersion.VersionId, *out.CopySourceVersionId)
|
|
}
|
|
|
|
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
|
|
res, err := s3client.HeadObject(ctx, &s3.HeadObjectInput{
|
|
Bucket: &bucket,
|
|
Key: &dstObj,
|
|
VersionId: out.VersionId,
|
|
})
|
|
cancel()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if res.ContentLength == nil {
|
|
return fmt.Errorf("expected non nil ContentLength")
|
|
}
|
|
if res.VersionId == nil {
|
|
return fmt.Errorf("expected non nil VersionId")
|
|
}
|
|
if *res.ContentLength != *srcObjVersion.Size {
|
|
return fmt.Errorf("expected the copied object size to be %v, instead got %v",
|
|
*srcObjVersion.Size, *res.ContentLength)
|
|
}
|
|
if *res.VersionId != *out.VersionId {
|
|
return fmt.Errorf("expected the copied object versionId to be %v, instead got %v",
|
|
*out.VersionId, *res.VersionId)
|
|
}
|
|
|
|
return nil
|
|
}, withVersioning(types.BucketVersioningStatusEnabled))
|
|
}
|
|
|
|
func Versioning_CopyObject_special_chars(s *S3Conf) error {
|
|
testName := "Versioning_CopyObject_special_chars"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
srcObj, dstBucket, dstObj := "foo?bar", getBucketName(), "bar&foo"
|
|
err := setup(s, dstBucket)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
srcObjVersions, err := createObjVersions(s3client, bucket, srcObj, 1)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
srcObjVersionId := *srcObjVersions[0].VersionId
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
res, err := s3client.CopyObject(ctx, &s3.CopyObjectInput{
|
|
Bucket: &bucket,
|
|
Key: &dstObj,
|
|
CopySource: getPtr(fmt.Sprintf("%v/%v?versionId=%v", bucket, srcObj, srcObjVersionId)),
|
|
})
|
|
cancel()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if res.VersionId == nil || *res.VersionId == "" {
|
|
return fmt.Errorf("expected non empty versionId")
|
|
}
|
|
if res.CopySourceVersionId == nil {
|
|
return fmt.Errorf("expected non nil CopySourceVersionId")
|
|
}
|
|
if *res.CopySourceVersionId != srcObjVersionId {
|
|
return fmt.Errorf("expected the SourceVersionId to be %v, instead got %v",
|
|
srcObjVersionId, *res.CopySourceVersionId)
|
|
}
|
|
|
|
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
|
|
out, err := s3client.HeadObject(ctx, &s3.HeadObjectInput{
|
|
Bucket: &bucket,
|
|
Key: &dstObj,
|
|
VersionId: res.VersionId,
|
|
})
|
|
cancel()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if out.VersionId == nil {
|
|
return fmt.Errorf("expected non nil VersionId")
|
|
}
|
|
if *out.VersionId != *res.VersionId {
|
|
return fmt.Errorf("expected the copied object versionId to be %v, instead got %v",
|
|
*res.VersionId, *out.VersionId)
|
|
}
|
|
|
|
err = teardown(s, dstBucket)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}, withVersioning(types.BucketVersioningStatusEnabled))
|
|
}
|
|
|
|
func Versioning_HeadObject_invalid_versionId(s *S3Conf) error {
|
|
testName := "Versioning_HeadObject_invalid_versionId"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
obj := "my-obj"
|
|
_, err := putObjectWithData(10, &s3.PutObjectInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
}, s3client)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
_, err = s3client.HeadObject(ctx, &s3.HeadObjectInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
VersionId: getPtr("invalid_versionId"),
|
|
})
|
|
cancel()
|
|
if err := checkSdkApiErr(err, "BadRequest"); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}, withVersioning(types.BucketVersioningStatusEnabled))
|
|
}
|
|
|
|
func Versioning_HeadObject_non_existing_object_version(s *S3Conf) error {
|
|
testName := "Versioning_HeadObject_non_existing_object_version"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
dLen := int64(2000)
|
|
obj := "my-obj"
|
|
_, err := putObjectWithData(dLen, &s3.PutObjectInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
}, s3client)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
_, err = s3client.HeadObject(ctx, &s3.HeadObjectInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
VersionId: getPtr("01G65Z755AFWAKHE12NY0CQ9FH"),
|
|
})
|
|
cancel()
|
|
if err := checkSdkApiErr(err, "NotFound"); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func Versioning_HeadObject_invalid_parent(s *S3Conf) error {
|
|
testName := "Versioning_HeadObject_invalid_parent"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
dLen := int64(2000)
|
|
obj := "not-a-dir"
|
|
r, err := putObjectWithData(dLen, &s3.PutObjectInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
}, s3client)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
obj = "not-a-dir/bad-obj"
|
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
_, err = s3client.HeadObject(ctx, &s3.HeadObjectInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
VersionId: r.res.VersionId,
|
|
})
|
|
cancel()
|
|
if err := checkSdkApiErr(err, "NotFound"); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func Versioning_HeadObject_success(s *S3Conf) error {
|
|
testName := "Versioning_HeadObject_success"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
dLen := int64(2000)
|
|
obj := "my-obj"
|
|
r, err := putObjectWithData(dLen, &s3.PutObjectInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
}, s3client)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
out, err := s3client.HeadObject(ctx, &s3.HeadObjectInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
VersionId: r.res.VersionId,
|
|
})
|
|
cancel()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if out.ContentLength == nil {
|
|
return fmt.Errorf("expected non nil ContentLength")
|
|
}
|
|
if out.VersionId == nil {
|
|
return fmt.Errorf("expected non nil VersionId")
|
|
}
|
|
if *out.ContentLength != dLen {
|
|
return fmt.Errorf("expected the object content-length to be %v, instead got %v",
|
|
dLen, *out.ContentLength)
|
|
}
|
|
if *out.VersionId != *r.res.VersionId {
|
|
return fmt.Errorf("expected the versionId to be %v, instead got %v",
|
|
*r.res.VersionId, *out.VersionId)
|
|
}
|
|
|
|
return nil
|
|
}, withVersioning(types.BucketVersioningStatusEnabled))
|
|
}
|
|
|
|
func Versioning_HeadObject_without_versionId(s *S3Conf) error {
|
|
testName := "Versioning_HeadObject_without_versionId"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
obj := "my-obj"
|
|
versions, err := createObjVersions(s3client, bucket, obj, 3)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
lastVersion := versions[0]
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
res, err := s3client.HeadObject(ctx, &s3.HeadObjectInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
})
|
|
cancel()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if getString(res.VersionId) != *lastVersion.VersionId {
|
|
return fmt.Errorf("expected versionId to be %v, instead got %v",
|
|
*lastVersion.VersionId, getString(res.VersionId))
|
|
}
|
|
|
|
return nil
|
|
}, withVersioning(types.BucketVersioningStatusEnabled))
|
|
}
|
|
|
|
func Versioning_HeadObject_delete_marker(s *S3Conf) error {
|
|
testName := "Versioning_HeadObject_delete_marker"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
dLen := int64(2000)
|
|
obj := "my-obj"
|
|
_, err := putObjectWithData(dLen, &s3.PutObjectInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
}, s3client)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
out, err := s3client.DeleteObject(ctx, &s3.DeleteObjectInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
})
|
|
cancel()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if out.VersionId == nil || *out.VersionId == "" {
|
|
return fmt.Errorf("expected non empty versionId")
|
|
}
|
|
|
|
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
|
|
_, err = s3client.HeadObject(ctx, &s3.HeadObjectInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
VersionId: out.VersionId,
|
|
})
|
|
cancel()
|
|
if err := checkSdkApiErr(err, "MethodNotAllowed"); err != nil {
|
|
return err
|
|
}
|
|
|
|
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
|
|
_, err = s3client.HeadObject(ctx, &s3.HeadObjectInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
})
|
|
cancel()
|
|
if err := checkSdkApiErr(err, "NotFound"); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}, withVersioning(types.BucketVersioningStatusEnabled))
|
|
}
|
|
|
|
func Versioning_GetObject_invalid_versionId(s *S3Conf) error {
|
|
testName := "Versioning_GetObject_invalid_versionId"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
obj := "my-obj"
|
|
|
|
_, err := putObjectWithData(10, &s3.PutObjectInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
}, s3client)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
_, err = s3client.GetObject(ctx, &s3.GetObjectInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
VersionId: getPtr("invalid_version_id"),
|
|
})
|
|
cancel()
|
|
return checkApiErr(err, s3err.GetInvalidArgumentErr(s3err.InvalidArgVersionId, "invalid_version_id"))
|
|
}, withVersioning(types.BucketVersioningStatusEnabled))
|
|
}
|
|
|
|
func Versioning_GetObject_non_existing_object_version(s *S3Conf) error {
|
|
testName := "Versioning_GetObject_non_existing_object_version"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
dLen := int64(2000)
|
|
obj := "my-obj"
|
|
_, err := putObjectWithData(dLen, &s3.PutObjectInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
}, s3client)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
_, err = s3client.GetObject(ctx, &s3.GetObjectInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
VersionId: getPtr("01G65Z755AFWAKHE12NY0CQ9FH"),
|
|
})
|
|
cancel()
|
|
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchVersion)); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}, withVersioning(types.BucketVersioningStatusEnabled))
|
|
}
|
|
|
|
func Versioning_GetObject_success(s *S3Conf) error {
|
|
testName := "Versioning_GetObject_success"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
dLen := int64(2000)
|
|
obj := "my-obj"
|
|
r, err := putObjectWithData(dLen, &s3.PutObjectInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
}, s3client)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Get the object by versionId
|
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
out, err := s3client.GetObject(ctx, &s3.GetObjectInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
VersionId: r.res.VersionId,
|
|
})
|
|
defer cancel()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if out.ContentLength == nil {
|
|
return fmt.Errorf("expected non nil ContentLength")
|
|
}
|
|
if out.VersionId == nil {
|
|
return fmt.Errorf("expected non nil VersionId")
|
|
}
|
|
if *out.ContentLength != dLen {
|
|
return fmt.Errorf("expected the object content-length to be %v, instead got %v",
|
|
dLen, *out.ContentLength)
|
|
}
|
|
if *out.VersionId != *r.res.VersionId {
|
|
return fmt.Errorf("expected the versionId to be %v, instead got %v",
|
|
*r.res.VersionId, *out.VersionId)
|
|
}
|
|
|
|
bdy, err := io.ReadAll(out.Body)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
out.Body.Close()
|
|
|
|
outCsum := sha256.Sum256(bdy)
|
|
if outCsum != r.csum {
|
|
return fmt.Errorf("incorrect output content")
|
|
}
|
|
|
|
// Get the object without versionId
|
|
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
|
|
out, err = s3client.GetObject(ctx, &s3.GetObjectInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
})
|
|
defer cancel()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if out.ContentLength == nil {
|
|
return fmt.Errorf("expected non nil ContentLength")
|
|
}
|
|
if out.VersionId == nil {
|
|
return fmt.Errorf("expected non nil VersionId")
|
|
}
|
|
if *out.ContentLength != dLen {
|
|
return fmt.Errorf("expected the object content-length to be %v, instead got %v",
|
|
dLen, *out.ContentLength)
|
|
}
|
|
if *out.VersionId != *r.res.VersionId {
|
|
return fmt.Errorf("expected the versionId to be %v, instead got %v",
|
|
*r.res.VersionId, *out.VersionId)
|
|
}
|
|
|
|
bdy, err = io.ReadAll(out.Body)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
out.Body.Close()
|
|
|
|
outCsum = sha256.Sum256(bdy)
|
|
if outCsum != r.csum {
|
|
return fmt.Errorf("incorrect output content")
|
|
}
|
|
|
|
return nil
|
|
}, withVersioning(types.BucketVersioningStatusEnabled))
|
|
}
|
|
|
|
func Versioning_GetObject_delete_marker_without_versionId(s *S3Conf) error {
|
|
testName := "Versioning_GetObject_delete_marker_without_versionId"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
obj := "my-obj"
|
|
_, err := putObjectWithData(1234, &s3.PutObjectInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
}, s3client)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = putBucketVersioningStatus(s3client, bucket, types.BucketVersioningStatusEnabled)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
_, err = s3client.DeleteObject(ctx, &s3.DeleteObjectInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
})
|
|
cancel()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
|
|
_, err = s3client.GetObject(ctx, &s3.GetObjectInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
})
|
|
cancel()
|
|
if err := checkSdkApiErr(err, "NoSuchKey"); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func Versioning_GetObject_delete_marker(s *S3Conf) error {
|
|
testName := "Versioning_GetObject_delete_marker"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
dLen := int64(2000)
|
|
obj := "my-obj"
|
|
_, err := putObjectWithData(dLen, &s3.PutObjectInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
}, s3client)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
out, err := s3client.DeleteObject(ctx, &s3.DeleteObjectInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
})
|
|
cancel()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if out.VersionId == nil || *out.VersionId == "" {
|
|
return fmt.Errorf("expected non empty versionId")
|
|
}
|
|
|
|
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
|
|
_, err = s3client.GetObject(ctx, &s3.GetObjectInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
VersionId: out.VersionId,
|
|
})
|
|
cancel()
|
|
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrMethodNotAllowed)); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}, withVersioning(types.BucketVersioningStatusEnabled))
|
|
}
|
|
|
|
func Versioning_GetObject_null_versionId_obj(s *S3Conf) error {
|
|
testName := "Versioning_GetObject_null_versionId_obj"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
obj, lgth := "my-obj", int64(234)
|
|
out, err := putObjectWithData(lgth, &s3.PutObjectInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
}, s3client)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = putBucketVersioningStatus(s3client, bucket, types.BucketVersioningStatusEnabled)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
res, err := s3client.GetObject(ctx, &s3.GetObjectInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
VersionId: &nullVersionId,
|
|
})
|
|
cancel()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if res.ContentLength == nil {
|
|
return fmt.Errorf("expected non nil ContentLength")
|
|
}
|
|
if res.VersionId == nil {
|
|
return fmt.Errorf("expected non nil VersionId")
|
|
}
|
|
if res.ETag == nil {
|
|
return fmt.Errorf("expected non nil ETag")
|
|
}
|
|
if *res.ContentLength != lgth {
|
|
return fmt.Errorf("expected the Content-Length to be %v, instead got %v",
|
|
lgth, *res.ContentLength)
|
|
}
|
|
if *res.VersionId != nullVersionId {
|
|
return fmt.Errorf("expected the versionId to be %v, insted got %v",
|
|
nullVersionId, *res.VersionId)
|
|
}
|
|
if *res.ETag != *out.res.ETag {
|
|
return fmt.Errorf("expecte the ETag to be %v, instead got %v",
|
|
*out.res.ETag, *res.ETag)
|
|
}
|
|
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func Versioning_GetObjectAttributes_invalid_versionId(s *S3Conf) error {
|
|
testName := "Versioning_GetObjectAttributes_invalid_versionId"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
obj := "my-obj"
|
|
_, err := createObjVersions(s3client, bucket, obj, 1)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
_, err = s3client.GetObjectAttributes(ctx, &s3.GetObjectAttributesInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
VersionId: getPtr("invalid_versionId"),
|
|
ObjectAttributes: []types.ObjectAttributes{
|
|
types.ObjectAttributesEtag,
|
|
},
|
|
})
|
|
cancel()
|
|
return checkApiErr(err, s3err.GetInvalidArgumentErr(s3err.InvalidArgVersionId, "invalid_versionId"))
|
|
})
|
|
}
|
|
|
|
func Versioning_GetObjectAttributes_object_version(s *S3Conf) error {
|
|
testName := "Versioning_GetObjectAttributes_object_version"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
obj := "my-obj"
|
|
versions, err := createObjVersions(s3client, bucket, obj, 1)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
version := versions[0]
|
|
|
|
getObjAttrs := func(versionId *string) (*s3.GetObjectAttributesOutput, error) {
|
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
res, err := s3client.GetObjectAttributes(ctx, &s3.GetObjectAttributesInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
VersionId: versionId,
|
|
ObjectAttributes: []types.ObjectAttributes{
|
|
types.ObjectAttributesEtag,
|
|
},
|
|
})
|
|
cancel()
|
|
return res, err
|
|
}
|
|
|
|
// By specifying the versionId
|
|
res, err := getObjAttrs(version.VersionId)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if getString(res.ETag) != strings.Trim(*version.ETag, "\"") {
|
|
return fmt.Errorf("expected the uploaded object ETag to be %v, instead got %v",
|
|
strings.Trim(*version.ETag, "\""), getString(res.ETag))
|
|
}
|
|
if getString(res.VersionId) != *version.VersionId {
|
|
return fmt.Errorf("expected the uploaded versionId to be %v, instead got %v",
|
|
*version.VersionId, getString(res.VersionId))
|
|
}
|
|
|
|
// Without versionId
|
|
res, err = getObjAttrs(nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if getString(res.ETag) != strings.Trim(*version.ETag, "\"") {
|
|
return fmt.Errorf("expected the uploaded object ETag to be %v, instead got %v",
|
|
strings.Trim(*version.ETag, "\""), getString(res.ETag))
|
|
}
|
|
if getString(res.VersionId) != *version.VersionId {
|
|
return fmt.Errorf("expected the uploaded object versionId to be %v, instead got %v",
|
|
*version.VersionId, getString(res.VersionId))
|
|
}
|
|
|
|
return nil
|
|
}, withVersioning(types.BucketVersioningStatusEnabled))
|
|
}
|
|
|
|
func Versioning_GetObjectAttributes_delete_marker(s *S3Conf) error {
|
|
testName := "Versioning_GetObjectAttributes_delete_marker"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
obj := "my-obj"
|
|
_, err := createObjVersions(s3client, bucket, obj, 1)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
res, err := s3client.DeleteObject(ctx, &s3.DeleteObjectInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
})
|
|
cancel()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
|
|
_, err = s3client.GetObjectAttributes(ctx, &s3.GetObjectAttributesInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
VersionId: res.VersionId,
|
|
ObjectAttributes: []types.ObjectAttributes{
|
|
types.ObjectAttributesEtag,
|
|
},
|
|
})
|
|
cancel()
|
|
if err := checkSdkApiErr(err, "NoSuchKey"); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}, withVersioning(types.BucketVersioningStatusEnabled))
|
|
}
|
|
|
|
func Versioning_DeleteObject_invalid_versionId(s *S3Conf) error {
|
|
testName := "Versioning_DeleteObject_invalid_versionId"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
obj := "my-obj"
|
|
_, err := putObjectWithData(3, &s3.PutObjectInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
}, s3client)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
_, err = s3client.DeleteObject(ctx, &s3.DeleteObjectInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
VersionId: getPtr("invalid_versionId"),
|
|
})
|
|
cancel()
|
|
return checkApiErr(err, s3err.GetInvalidArgumentErr(s3err.InvalidArgVersionId, "invalid_versionId"))
|
|
})
|
|
}
|
|
|
|
func Versioning_DeleteObject_delete_object_version(s *S3Conf) error {
|
|
testName := "Versioning_DeleteObject_delete_object_version"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
oLen := int64(1000)
|
|
obj := "my-obj"
|
|
r, err := putObjectWithData(oLen, &s3.PutObjectInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
}, s3client)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
versionId := r.res.VersionId
|
|
if versionId == nil || *versionId == "" {
|
|
return fmt.Errorf("expected non empty versionId")
|
|
}
|
|
|
|
_, err = putObjects(s3client, []string{obj}, bucket)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
out, err := s3client.DeleteObject(ctx, &s3.DeleteObjectInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
VersionId: versionId,
|
|
})
|
|
cancel()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if out.VersionId == nil {
|
|
return fmt.Errorf("expected non nil versionId")
|
|
}
|
|
if *out.VersionId != *versionId {
|
|
return fmt.Errorf("expected deleted object versionId to be %v, instead got %v",
|
|
*versionId, *out.VersionId)
|
|
}
|
|
|
|
return nil
|
|
}, withVersioning(types.BucketVersioningStatusEnabled))
|
|
}
|
|
|
|
func Versioning_DeleteObject_non_existing_object(s *S3Conf) error {
|
|
testName := "Versioning_DeleteObject_non_existing_object"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
obj := "my-obj"
|
|
|
|
ctx, canel := context.WithTimeout(context.Background(), shortTimeout)
|
|
_, err := s3client.DeleteObject(ctx, &s3.DeleteObjectInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
})
|
|
canel()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ctx, canel = context.WithTimeout(context.Background(), shortTimeout)
|
|
_, err = s3client.DeleteObject(ctx, &s3.DeleteObjectInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
VersionId: getPtr("non_existing_version_id"),
|
|
})
|
|
canel()
|
|
if err := checkApiErr(err, s3err.GetInvalidArgumentErr(s3err.InvalidArgVersionId, "non_existing_version_id")); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}, withVersioning(types.BucketVersioningStatusEnabled))
|
|
}
|
|
|
|
func Versioning_DeleteObject_delete_a_delete_marker(s *S3Conf) error {
|
|
testName := "Versioning_DeleteObject_delete_a_delete_marker"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
oLen := int64(1000)
|
|
obj := "my-obj"
|
|
_, err := putObjectWithData(oLen, &s3.PutObjectInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
}, s3client)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
out, err := s3client.DeleteObject(ctx, &s3.DeleteObjectInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
})
|
|
cancel()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if out.DeleteMarker == nil || !*out.DeleteMarker {
|
|
return fmt.Errorf("expected the response DeleteMarker to be true")
|
|
}
|
|
if out.VersionId == nil || *out.VersionId == "" {
|
|
return fmt.Errorf("expected non empty versionId")
|
|
}
|
|
|
|
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
|
|
res, err := s3client.DeleteObject(ctx, &s3.DeleteObjectInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
VersionId: out.VersionId,
|
|
})
|
|
cancel()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if res.DeleteMarker == nil || !*res.DeleteMarker {
|
|
return fmt.Errorf("expected the response DeleteMarker to be true")
|
|
}
|
|
if res.VersionId == nil {
|
|
return fmt.Errorf("expected non empty versionId")
|
|
}
|
|
if *res.VersionId != *out.VersionId {
|
|
return fmt.Errorf("expected the versionId to be %v, instead got %v",
|
|
*out.VersionId, *res.VersionId)
|
|
}
|
|
|
|
return nil
|
|
}, withVersioning(types.BucketVersioningStatusEnabled))
|
|
}
|
|
|
|
func Versioning_Delete_null_versionId_object(s *S3Conf) error {
|
|
testName := "Versioning_Delete_null_versionId_object"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
obj, nObjLgth := "my-obj", int64(3211)
|
|
_, err := putObjectWithData(nObjLgth, &s3.PutObjectInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
}, s3client)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = putBucketVersioningStatus(s3client, bucket, types.BucketVersioningStatusEnabled)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
_, err = createObjVersions(s3client, bucket, obj, 3)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
res, err := s3client.DeleteObject(ctx, &s3.DeleteObjectInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
VersionId: getPtr(nullVersionId),
|
|
})
|
|
cancel()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if getString(res.VersionId) != nullVersionId {
|
|
return fmt.Errorf("expected the versionId to be %v, instead got %v",
|
|
nullVersionId, getString(res.VersionId))
|
|
}
|
|
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func Versioning_DeleteObject_nested_dir_object(s *S3Conf) error {
|
|
testName := "Versioning_DeleteObject_nested_dir_object"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
obj := "foo/bar/baz"
|
|
out, err := putObjectWithData(1000, &s3.PutObjectInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
}, s3client)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
res, err := s3client.DeleteObject(ctx, &s3.DeleteObjectInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
VersionId: out.res.VersionId,
|
|
})
|
|
cancel()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if getString(res.VersionId) != getString(out.res.VersionId) {
|
|
return fmt.Errorf("expected the versionId to be %v, instead got %v",
|
|
getString(out.res.VersionId), getString(res.VersionId))
|
|
}
|
|
|
|
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
|
|
_, err = s3client.DeleteBucket(ctx, &s3.DeleteBucketInput{
|
|
Bucket: &bucket,
|
|
})
|
|
cancel()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Then create the bucket back to not get error on teardown
|
|
if err := setup(s, bucket); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}, withLock())
|
|
}
|
|
|
|
func Versioning_DeleteObject_non_existing_objects(s *S3Conf) error {
|
|
testName := "Versioning_DeleteObject_non_existing_objects"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
out, err := putObjectWithData(2, &s3.PutObjectInput{
|
|
Bucket: &bucket,
|
|
Key: getPtr("foo"),
|
|
}, s3client)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
versionId := getString(out.res.VersionId)
|
|
|
|
for _, test := range []struct {
|
|
key string
|
|
versionId string
|
|
}{
|
|
{"foo/bar", "01KF2YVN948NAZ4JJR4X1AAVRA"},
|
|
{"foo/bar/baz", "01KF2YVN948NAZ4JJR4X1AAVRA"},
|
|
{"hello", "01KF2YVN948NAZ4JJR4X1AAVRA"},
|
|
{"hello/world", "01KF2YVN948NAZ4JJR4X1AAVRA"},
|
|
{"foo/bar/baz/quxx", versionId},
|
|
{"foo", versionId},
|
|
} {
|
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
res, err := s3client.DeleteObject(ctx, &s3.DeleteObjectInput{
|
|
Bucket: &bucket,
|
|
Key: &test.key,
|
|
VersionId: &test.versionId,
|
|
})
|
|
cancel()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if getString(res.VersionId) != test.versionId {
|
|
return fmt.Errorf("expected the versionId to be %s, instead got %s", test.versionId, getString(res.VersionId))
|
|
}
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
resp, err := s3client.ListObjectVersions(ctx, &s3.ListObjectVersionsInput{
|
|
Bucket: &bucket,
|
|
})
|
|
cancel()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if len(resp.Versions) != 0 {
|
|
return fmt.Errorf("expected empty object versions, instead got %v", resp.Versions)
|
|
}
|
|
if len(resp.DeleteMarkers) != 0 {
|
|
return fmt.Errorf("expected empty delete markers list, insead got %v", resp.DeleteMarkers)
|
|
}
|
|
|
|
return nil
|
|
}, withLock())
|
|
}
|
|
|
|
func Versioning_DeleteObject_suspended(s *S3Conf) error {
|
|
testName := "Versioning_DeleteObject_suspended"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
obj := "my-obj"
|
|
versions, err := createObjVersions(s3client, bucket, obj, 1)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
versions[0].IsLatest = getBoolPtr(false)
|
|
|
|
err = putBucketVersioningStatus(s3client, bucket, types.BucketVersioningStatusSuspended)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for range 5 {
|
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
res, err := s3client.DeleteObject(ctx, &s3.DeleteObjectInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
})
|
|
cancel()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if res.DeleteMarker == nil {
|
|
return fmt.Errorf("expected the delete marker to be true")
|
|
}
|
|
if !*res.DeleteMarker {
|
|
return fmt.Errorf("expected the delete marker to be true, instead got %v",
|
|
*res.DeleteMarker)
|
|
}
|
|
if res.VersionId == nil {
|
|
return fmt.Errorf("expected non nil versionId")
|
|
}
|
|
if *res.VersionId != nullVersionId {
|
|
return fmt.Errorf("expected the versionId to be %v, instead got %v",
|
|
nullVersionId, *res.VersionId)
|
|
}
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
res, err := s3client.ListObjectVersions(ctx, &s3.ListObjectVersionsInput{
|
|
Bucket: &bucket,
|
|
})
|
|
cancel()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
delMarkers := []types.DeleteMarkerEntry{
|
|
{
|
|
IsLatest: getBoolPtr(true),
|
|
Key: &obj,
|
|
VersionId: &nullVersionId,
|
|
},
|
|
}
|
|
|
|
if !compareVersions(versions, res.Versions) {
|
|
return fmt.Errorf("expected the versions to be %v, instead got %v",
|
|
versions, res.Versions)
|
|
}
|
|
if !compareDelMarkers(res.DeleteMarkers, delMarkers) {
|
|
return fmt.Errorf("expected the delete markers to be %v, instead got %v",
|
|
delMarkers, res.DeleteMarkers)
|
|
}
|
|
|
|
return nil
|
|
}, withVersioning(types.BucketVersioningStatusEnabled))
|
|
}
|
|
|
|
func Versioning_DeleteObjects_success(s *S3Conf) error {
|
|
testName := "Versioning_DeleteObjects_success"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
obj1, obj2, obj3 := "foo", "bar", "baz"
|
|
|
|
obj1Version, err := createObjVersions(s3client, bucket, obj1, 1)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
obj2Version, err := createObjVersions(s3client, bucket, obj2, 1)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
obj3Version, err := createObjVersions(s3client, bucket, obj3, 1)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
out, err := s3client.DeleteObjects(ctx, &s3.DeleteObjectsInput{
|
|
Bucket: &bucket,
|
|
Delete: &types.Delete{
|
|
Objects: []types.ObjectIdentifier{
|
|
{
|
|
Key: obj1Version[0].Key,
|
|
VersionId: obj1Version[0].VersionId,
|
|
},
|
|
{
|
|
Key: obj2Version[0].Key,
|
|
},
|
|
{
|
|
Key: obj3Version[0].Key,
|
|
},
|
|
},
|
|
},
|
|
})
|
|
cancel()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
delResult := []types.DeletedObject{
|
|
{
|
|
Key: obj1Version[0].Key,
|
|
VersionId: obj1Version[0].VersionId,
|
|
DeleteMarker: getBoolPtr(false),
|
|
},
|
|
{
|
|
Key: obj2Version[0].Key,
|
|
DeleteMarker: getBoolPtr(true),
|
|
},
|
|
{
|
|
Key: obj3Version[0].Key,
|
|
DeleteMarker: getBoolPtr(true),
|
|
},
|
|
}
|
|
|
|
if len(out.Errors) != 0 {
|
|
return fmt.Errorf("errors occurred during the deletion: %v",
|
|
out.Errors)
|
|
}
|
|
if !compareDelObjects(delResult, out.Deleted) {
|
|
return fmt.Errorf("expected the deleted objects to be %v, instead got %v",
|
|
delResult, out.Deleted)
|
|
}
|
|
|
|
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
|
|
res, err := s3client.ListObjectVersions(ctx, &s3.ListObjectVersionsInput{
|
|
Bucket: &bucket,
|
|
})
|
|
cancel()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
obj2Version[0].IsLatest = getBoolPtr(false)
|
|
obj3Version[0].IsLatest = getBoolPtr(false)
|
|
versions := append(obj2Version, obj3Version...)
|
|
|
|
delMarkers := []types.DeleteMarkerEntry{
|
|
{
|
|
IsLatest: getBoolPtr(true),
|
|
Key: out.Deleted[1].Key,
|
|
VersionId: out.Deleted[1].DeleteMarkerVersionId,
|
|
},
|
|
{
|
|
IsLatest: getBoolPtr(true),
|
|
Key: out.Deleted[2].Key,
|
|
VersionId: out.Deleted[2].DeleteMarkerVersionId,
|
|
},
|
|
}
|
|
if !compareVersions(versions, res.Versions) {
|
|
return fmt.Errorf("expected the resulting versions to be %v, instead got %v",
|
|
versions, res.Versions)
|
|
}
|
|
if !compareDelMarkers(delMarkers, res.DeleteMarkers) {
|
|
return fmt.Errorf("expected the resulting delete markers to be %v, instead got %v",
|
|
delMarkers, res.DeleteMarkers)
|
|
}
|
|
|
|
return nil
|
|
}, withVersioning(types.BucketVersioningStatusEnabled))
|
|
}
|
|
|
|
func Versioning_DeleteObjects_delete_deleteMarkers(s *S3Conf) error {
|
|
testName := "Versioning_DeleteObjects_delete_deleteMarkers"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
obj1, obj2 := "foo", "bar"
|
|
|
|
obj1Version, err := createObjVersions(s3client, bucket, obj1, 1)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
obj2Version, err := createObjVersions(s3client, bucket, obj2, 1)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
out, err := s3client.DeleteObjects(ctx, &s3.DeleteObjectsInput{
|
|
Bucket: &bucket,
|
|
Delete: &types.Delete{
|
|
Objects: []types.ObjectIdentifier{
|
|
{
|
|
Key: obj1Version[0].Key,
|
|
},
|
|
{
|
|
Key: obj2Version[0].Key,
|
|
},
|
|
},
|
|
},
|
|
})
|
|
cancel()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
delResult := []types.DeletedObject{
|
|
{
|
|
Key: obj1Version[0].Key,
|
|
DeleteMarker: getBoolPtr(true),
|
|
},
|
|
{
|
|
Key: obj2Version[0].Key,
|
|
DeleteMarker: getBoolPtr(true),
|
|
},
|
|
}
|
|
|
|
if len(out.Errors) != 0 {
|
|
return fmt.Errorf("errors occurred during the deletion: %v",
|
|
out.Errors)
|
|
}
|
|
if !compareDelObjects(delResult, out.Deleted) {
|
|
return fmt.Errorf("expected the deleted objects to be %v, instead got %v",
|
|
delResult, out.Deleted)
|
|
}
|
|
|
|
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
|
|
res, err := s3client.DeleteObjects(ctx, &s3.DeleteObjectsInput{
|
|
Bucket: &bucket,
|
|
Delete: &types.Delete{
|
|
Objects: []types.ObjectIdentifier{
|
|
{
|
|
Key: out.Deleted[0].Key,
|
|
VersionId: out.Deleted[0].VersionId,
|
|
},
|
|
{
|
|
Key: out.Deleted[1].Key,
|
|
VersionId: out.Deleted[1].VersionId,
|
|
},
|
|
},
|
|
},
|
|
})
|
|
cancel()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(out.Errors) != 0 {
|
|
return fmt.Errorf("errors occurred during the deletion: %v",
|
|
out.Errors)
|
|
}
|
|
|
|
delResult = []types.DeletedObject{
|
|
{
|
|
Key: out.Deleted[0].Key,
|
|
DeleteMarker: getBoolPtr(true),
|
|
DeleteMarkerVersionId: out.Deleted[0].VersionId,
|
|
VersionId: out.Deleted[0].VersionId,
|
|
},
|
|
{
|
|
Key: out.Deleted[1].Key,
|
|
DeleteMarker: getBoolPtr(true),
|
|
DeleteMarkerVersionId: out.Deleted[1].VersionId,
|
|
VersionId: out.Deleted[1].VersionId,
|
|
},
|
|
}
|
|
|
|
if !compareDelObjects(delResult, res.Deleted) {
|
|
return fmt.Errorf("expected the deleted objects to be %v, instead got %v",
|
|
delResult, res.Deleted)
|
|
}
|
|
|
|
return nil
|
|
}, withVersioning(types.BucketVersioningStatusEnabled))
|
|
}
|
|
|
|
func Versioning_Multipart_Upload_success(s *S3Conf) error {
|
|
testName := "Versioning_Multipart_Upload_success"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
obj := "my-obj"
|
|
out, err := createMp(s3client, bucket, obj)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
objSize := int64(25 * 1024 * 1024)
|
|
parts, _, err := uploadParts(s3client, objSize, 5, bucket, obj, *out.UploadId)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
compParts := []types.CompletedPart{}
|
|
for _, el := range parts {
|
|
compParts = append(compParts, types.CompletedPart{
|
|
ETag: el.ETag,
|
|
PartNumber: el.PartNumber,
|
|
})
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
res, err := s3client.CompleteMultipartUpload(ctx, &s3.CompleteMultipartUploadInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
UploadId: out.UploadId,
|
|
MultipartUpload: &types.CompletedMultipartUpload{
|
|
Parts: compParts,
|
|
},
|
|
})
|
|
cancel()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if res.Key == nil {
|
|
return fmt.Errorf("expected the object key to be %v, instead got nil",
|
|
obj)
|
|
}
|
|
if *res.Key != obj {
|
|
return fmt.Errorf("expected object key to be %v, instead got %v",
|
|
obj, *res.Key)
|
|
}
|
|
if res.Bucket == nil {
|
|
return fmt.Errorf("expected the bucket name to be %v, instead got nil",
|
|
bucket)
|
|
}
|
|
if *res.Bucket != bucket {
|
|
return fmt.Errorf("expected the bucket name to be %v, instead got %v",
|
|
bucket, *res.Bucket)
|
|
}
|
|
if res.ETag == nil || *res.ETag == "" {
|
|
return fmt.Errorf("expected non-empty ETag")
|
|
}
|
|
if res.VersionId == nil || *res.VersionId == "" {
|
|
return fmt.Errorf("expected non-empty versionId")
|
|
}
|
|
|
|
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
|
|
resp, err := s3client.HeadObject(ctx, &s3.HeadObjectInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
VersionId: res.VersionId,
|
|
})
|
|
cancel()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if resp.ETag == nil || *resp.ETag == "" {
|
|
return fmt.Errorf("expected (head object) non-empty ETag")
|
|
}
|
|
if *resp.ETag != *res.ETag {
|
|
return fmt.Errorf("expected the uploaded object etag to be %v, instead got %v",
|
|
*res.ETag, *resp.ETag)
|
|
}
|
|
if resp.ContentLength == nil {
|
|
return fmt.Errorf("expected (head object) non nil content length")
|
|
}
|
|
if *resp.ContentLength != int64(objSize) {
|
|
return fmt.Errorf("expected the uploaded object size to be %v, instead got %v",
|
|
objSize, resp.ContentLength)
|
|
}
|
|
if resp.VersionId == nil {
|
|
return fmt.Errorf("expected (head object) non nil versionId")
|
|
}
|
|
if *resp.VersionId != *res.VersionId {
|
|
return fmt.Errorf("expected the versionId to be %v, instead got %v",
|
|
*res.VersionId, *resp.VersionId)
|
|
}
|
|
|
|
return nil
|
|
}, withVersioning(types.BucketVersioningStatusEnabled))
|
|
}
|
|
|
|
func Versioning_Multipart_Upload_overwrite_an_object(s *S3Conf) error {
|
|
testName := "Versioning_Multipart_Upload_overwrite_an_object"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
obj := "my-obj"
|
|
|
|
objVersions, err := createObjVersions(s3client, bucket, obj, 2)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
out, err := createMp(s3client, bucket, obj)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
objSize := int64(25 * 1024 * 1024)
|
|
parts, _, err := uploadParts(s3client, objSize, 5, bucket, obj, *out.UploadId)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
compParts := []types.CompletedPart{}
|
|
for _, el := range parts {
|
|
compParts = append(compParts, types.CompletedPart{
|
|
ETag: el.ETag,
|
|
PartNumber: el.PartNumber,
|
|
})
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
res, err := s3client.CompleteMultipartUpload(ctx, &s3.CompleteMultipartUploadInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
UploadId: out.UploadId,
|
|
MultipartUpload: &types.CompletedMultipartUpload{
|
|
Parts: compParts,
|
|
},
|
|
})
|
|
cancel()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if res.Key == nil {
|
|
return fmt.Errorf("expected the object key to be %v, instead got nil",
|
|
obj)
|
|
}
|
|
if *res.Key != obj {
|
|
return fmt.Errorf("expected object key to be %v, instead got %v",
|
|
obj, *res.Key)
|
|
}
|
|
if res.Bucket == nil {
|
|
return fmt.Errorf("expected the bucket name to be %v, instead got nil",
|
|
bucket)
|
|
}
|
|
if *res.Bucket != bucket {
|
|
return fmt.Errorf("expected the bucket name to be %v, instead got %v",
|
|
bucket, *res.Bucket)
|
|
}
|
|
if res.ETag == nil || *res.ETag == "" {
|
|
return fmt.Errorf("expected non-empty ETag")
|
|
}
|
|
if res.VersionId == nil || *res.VersionId == "" {
|
|
return fmt.Errorf("expected non-empty versionId")
|
|
}
|
|
|
|
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
|
|
resp, err := s3client.ListObjectVersions(ctx, &s3.ListObjectVersionsInput{
|
|
Bucket: &bucket,
|
|
})
|
|
cancel()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
size := int64(objSize)
|
|
|
|
objVersions[0].IsLatest = getBoolPtr(false)
|
|
versions := append([]types.ObjectVersion{
|
|
{
|
|
Key: &obj,
|
|
VersionId: res.VersionId,
|
|
ETag: res.ETag,
|
|
IsLatest: getBoolPtr(true),
|
|
Size: &size,
|
|
StorageClass: types.ObjectVersionStorageClassStandard,
|
|
},
|
|
}, objVersions...)
|
|
|
|
if !compareVersions(versions, resp.Versions) {
|
|
return fmt.Errorf("expected the resulting versions to be %v, instead got %v",
|
|
versions, resp.Versions)
|
|
}
|
|
|
|
return nil
|
|
}, withVersioning(types.BucketVersioningStatusEnabled))
|
|
}
|
|
|
|
func Versioning_UploadPartCopy_invalid_versionId(s *S3Conf) error {
|
|
testName := "Versioning_UploadPartCopy_invalid_versionId"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
dstObj, srcObj := "dst-obj", "src-obj"
|
|
_, err := putObjectWithData(10, &s3.PutObjectInput{
|
|
Bucket: &bucket,
|
|
Key: &srcObj,
|
|
}, s3client)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
mp, err := createMp(s3client, bucket, dstObj)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
_, err = s3client.UploadPartCopy(ctx, &s3.UploadPartCopyInput{
|
|
Bucket: &bucket,
|
|
Key: &dstObj,
|
|
UploadId: mp.UploadId,
|
|
PartNumber: getPtr(int32(1)),
|
|
CopySource: getPtr(fmt.Sprintf("%v/%v?versionId=invalid_versionId",
|
|
bucket, srcObj)),
|
|
})
|
|
cancel()
|
|
return checkApiErr(err, s3err.GetInvalidArgumentErr(s3err.InvalidArgVersionId, "invalid_versionId"))
|
|
})
|
|
}
|
|
|
|
func Versioning_UploadPartCopy_non_existing_versionId(s *S3Conf) error {
|
|
testName := "Versioning_UploadPartCopy_non_existing_versionId"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
dstBucket, dstObj, srcObj := getBucketName(), "dst-obj", "src-obj"
|
|
|
|
lgth := int64(100)
|
|
_, err := putObjectWithData(lgth, &s3.PutObjectInput{
|
|
Bucket: &bucket,
|
|
Key: &srcObj,
|
|
}, s3client)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := setup(s, dstBucket); err != nil {
|
|
return err
|
|
}
|
|
|
|
mp, err := createMp(s3client, dstBucket, dstObj)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
pNumber := int32(1)
|
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
_, err = s3client.UploadPartCopy(ctx, &s3.UploadPartCopyInput{
|
|
Bucket: &dstBucket,
|
|
Key: &dstObj,
|
|
UploadId: mp.UploadId,
|
|
PartNumber: &pNumber,
|
|
CopySource: getPtr(fmt.Sprintf("%v/%v?versionId=01BX5ZZKBKACTAV9WEVGEMMVS0",
|
|
bucket, srcObj)),
|
|
})
|
|
cancel()
|
|
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchVersion)); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := teardown(s, dstBucket); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}, withVersioning(types.BucketVersioningStatusEnabled))
|
|
}
|
|
|
|
func Versioning_UploadPartCopy_from_an_object_version(s *S3Conf) error {
|
|
testName := "Versioning_UploadPartCopy_from_an_object_version"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
srcObj, dstBucket, obj := "my-obj", getBucketName(), "dst-obj"
|
|
err := setup(s, dstBucket)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
srcObjVersions, err := createObjVersions(s3client, bucket, srcObj, 1)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
srcObjVersion := srcObjVersions[0]
|
|
|
|
out, err := createMp(s3client, dstBucket, obj)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
partNumber := int32(1)
|
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
copyOut, err := s3client.UploadPartCopy(ctx, &s3.UploadPartCopyInput{
|
|
Bucket: &dstBucket,
|
|
CopySource: getPtr(fmt.Sprintf("%v/%v?versionId=%v", bucket, srcObj, *srcObjVersion.VersionId)),
|
|
UploadId: out.UploadId,
|
|
Key: &obj,
|
|
PartNumber: &partNumber,
|
|
})
|
|
cancel()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if getString(copyOut.CopySourceVersionId) != getString(srcObjVersion.VersionId) {
|
|
return fmt.Errorf("expected the copy-source-version-id to be %v, instead got %v",
|
|
getString(srcObjVersion.VersionId), getString(copyOut.CopySourceVersionId))
|
|
}
|
|
|
|
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
|
|
res, err := s3client.ListParts(ctx, &s3.ListPartsInput{
|
|
Bucket: &dstBucket,
|
|
Key: &obj,
|
|
UploadId: out.UploadId,
|
|
})
|
|
cancel()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if len(res.Parts) != 1 {
|
|
return fmt.Errorf("expected parts to be 1, instead got %v",
|
|
len(res.Parts))
|
|
}
|
|
if res.Parts[0].PartNumber == nil {
|
|
return fmt.Errorf("expected part-number to be non nil")
|
|
}
|
|
if *res.Parts[0].PartNumber != partNumber {
|
|
return fmt.Errorf("expected part-number to be %v, instead got %v",
|
|
partNumber, res.Parts[0].PartNumber)
|
|
}
|
|
if res.Parts[0].Size == nil {
|
|
return fmt.Errorf("expected part size to be non nil")
|
|
}
|
|
if *res.Parts[0].Size != *srcObjVersion.Size {
|
|
return fmt.Errorf("expected part size to be %v, instead got %v",
|
|
*srcObjVersion.Size, res.Parts[0].Size)
|
|
}
|
|
if getString(res.Parts[0].ETag) != getString(copyOut.CopyPartResult.ETag) {
|
|
return fmt.Errorf("expected part etag to be %v, instead got %v",
|
|
getString(copyOut.CopyPartResult.ETag), getString(res.Parts[0].ETag))
|
|
}
|
|
|
|
if err := teardown(s, dstBucket); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}, withVersioning(types.BucketVersioningStatusEnabled))
|
|
}
|
|
|
|
func Versioning_Enable_object_lock(s *S3Conf) error {
|
|
testName := "Versioning_Enable_object_lock"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
res, err := s3client.GetBucketVersioning(ctx, &s3.GetBucketVersioningInput{
|
|
Bucket: &bucket,
|
|
})
|
|
cancel()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if res.Status != types.BucketVersioningStatusEnabled {
|
|
return fmt.Errorf("expected the bucket versioning status to be %v, instead got %v",
|
|
types.BucketVersioningStatusEnabled, res.Status)
|
|
}
|
|
|
|
return nil
|
|
}, withLock())
|
|
}
|
|
|
|
func Versioning_object_lock_not_enabled_on_bucket_creation(s *S3Conf) error {
|
|
testName := "Versioning_not_enabled_on_bucket_creation"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
_, err := s3client.PutObjectLockConfiguration(ctx, &s3.PutObjectLockConfigurationInput{
|
|
Bucket: &bucket,
|
|
ObjectLockConfiguration: &types.ObjectLockConfiguration{
|
|
ObjectLockEnabled: types.ObjectLockEnabledEnabled,
|
|
Rule: &types.ObjectLockRule{
|
|
DefaultRetention: &types.DefaultRetention{
|
|
Mode: types.ObjectLockRetentionModeCompliance,
|
|
Days: getPtr(int32(10)),
|
|
},
|
|
},
|
|
},
|
|
})
|
|
cancel()
|
|
return checkApiErr(err, s3err.GetAPIError(s3err.ErrObjectLockConfigurationNotAllowed))
|
|
})
|
|
}
|
|
|
|
func Versioning_status_switch_to_suspended_with_object_lock(s *S3Conf) error {
|
|
testName := "Versioning_status_switch_to_suspended_with_object_lock"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
err := putBucketVersioningStatus(s3client, bucket, types.BucketVersioningStatusSuspended)
|
|
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrSuspendedVersioningNotAllowed)); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}, withLock())
|
|
}
|
|
|
|
func Versioning_PutObjectRetention_invalid_versionId(s *S3Conf) error {
|
|
testName := "Versioning_PutObjectRetention_invalid_versionId"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
obj := "my-obj"
|
|
_, err := createObjVersions(s3client, bucket, obj, 1)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
rDate := time.Now().Add(time.Hour * 48)
|
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
_, err = s3client.PutObjectRetention(ctx, &s3.PutObjectRetentionInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
VersionId: getPtr("invalid_version_id"),
|
|
Retention: &types.ObjectLockRetention{
|
|
Mode: types.ObjectLockRetentionModeGovernance,
|
|
RetainUntilDate: &rDate,
|
|
},
|
|
})
|
|
cancel()
|
|
return checkApiErr(err, s3err.GetInvalidArgumentErr(s3err.InvalidArgVersionId, "invalid_version_id"))
|
|
}, withLock())
|
|
}
|
|
|
|
func Versioning_PutObjectRetention_non_existing_object_version(s *S3Conf) error {
|
|
testName := "Versioning_PutObjectRetention_non_existing_object_version"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
obj := "my-obj"
|
|
_, err := createObjVersions(s3client, bucket, obj, 3)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
rDate := time.Now().Add(time.Hour * 48)
|
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
_, err = s3client.PutObjectRetention(ctx, &s3.PutObjectRetentionInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
VersionId: getPtr("01G65Z755AFWAKHE12NY0CQ9FH"),
|
|
Retention: &types.ObjectLockRetention{
|
|
Mode: types.ObjectLockRetentionModeGovernance,
|
|
RetainUntilDate: &rDate,
|
|
},
|
|
})
|
|
cancel()
|
|
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchVersion)); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}, withLock(), withVersioning(types.BucketVersioningStatusEnabled))
|
|
}
|
|
|
|
func Versioning_GetObjectRetention_invalid_versionId(s *S3Conf) error {
|
|
testName := "Versioning_GetObjectRetention_invalid_versionId"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
obj := "my-obj"
|
|
_, err := createObjVersions(s3client, bucket, obj, 1)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
_, err = s3client.GetObjectRetention(ctx, &s3.GetObjectRetentionInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
VersionId: getPtr("invalid_versionId"),
|
|
})
|
|
cancel()
|
|
return checkApiErr(err, s3err.GetInvalidArgumentErr(s3err.InvalidArgVersionId, "invalid_versionId"))
|
|
}, withLock())
|
|
}
|
|
|
|
func Versioning_GetObjectRetention_non_existing_object_version(s *S3Conf) error {
|
|
testName := "Versioning_GetObjectRetention_non_existing_object_version"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
obj := "my-obj"
|
|
_, err := createObjVersions(s3client, bucket, obj, 3)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
_, err = s3client.GetObjectRetention(ctx, &s3.GetObjectRetentionInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
VersionId: getPtr("01G65Z755AFWAKHE12NY0CQ9FH"),
|
|
})
|
|
cancel()
|
|
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchVersion)); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}, withLock(), withVersioning(types.BucketVersioningStatusEnabled))
|
|
}
|
|
|
|
func Versioning_Put_GetObjectRetention_delete_marker(s *S3Conf) error {
|
|
testName := "Versioning_Put_GetObjectRetention_delete_marker"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
obj := "my-object"
|
|
_, err := putObjectWithData(10, &s3.PutObjectInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
}, s3client)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
out, err := s3client.DeleteObject(ctx, &s3.DeleteObjectInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
})
|
|
cancel()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// PutObjectRetention
|
|
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
|
|
_, err = s3client.PutObjectRetention(ctx, &s3.PutObjectRetentionInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
VersionId: out.VersionId,
|
|
Retention: &types.ObjectLockRetention{
|
|
Mode: types.ObjectLockRetentionModeCompliance,
|
|
RetainUntilDate: getPtr(time.Now().AddDate(1, 0, 0)),
|
|
},
|
|
})
|
|
cancel()
|
|
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrMethodNotAllowed)); err != nil {
|
|
return err
|
|
}
|
|
|
|
// GetObjectRetention
|
|
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
|
|
_, err = s3client.GetObjectRetention(ctx, &s3.GetObjectRetentionInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
VersionId: out.VersionId,
|
|
})
|
|
cancel()
|
|
|
|
return checkApiErr(err, s3err.GetAPIError(s3err.ErrMethodNotAllowed))
|
|
}, withLock())
|
|
}
|
|
|
|
func Versioning_Put_GetObjectRetention_success(s *S3Conf) error {
|
|
testName := "Versioning_Put_GetObjectRetention_success"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
obj := "my-obj"
|
|
objVersions, err := createObjVersions(s3client, bucket, obj, 3)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
objVersion := objVersions[1]
|
|
|
|
rDate := time.Now().Add(time.Hour * 48)
|
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
_, err = s3client.PutObjectRetention(ctx, &s3.PutObjectRetentionInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
VersionId: objVersion.VersionId,
|
|
Retention: &types.ObjectLockRetention{
|
|
Mode: types.ObjectLockRetentionModeGovernance,
|
|
RetainUntilDate: &rDate,
|
|
},
|
|
})
|
|
cancel()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
|
|
res, err := s3client.GetObjectRetention(ctx, &s3.GetObjectRetentionInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
VersionId: objVersion.VersionId,
|
|
})
|
|
cancel()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if res.Retention.Mode != types.ObjectLockRetentionModeGovernance {
|
|
return fmt.Errorf("expected the object retention mode to be %v, instead got %v",
|
|
types.ObjectLockRetentionModeGovernance, res.Retention.Mode)
|
|
}
|
|
|
|
return cleanupLockedObjects(s3client, bucket, []objToDelete{{key: getString(objVersion.Key), versionId: getString(objVersion.VersionId)}})
|
|
}, withLock(), withVersioning(types.BucketVersioningStatusEnabled))
|
|
}
|
|
|
|
func Versioning_PutObjectLegalHold_invalid_versionId(s *S3Conf) error {
|
|
testName := "Versioning_PutObjectLegalHold_invalid_versionId"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
obj := "my-obj"
|
|
_, err := createObjVersions(s3client, bucket, obj, 1)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
_, err = s3client.PutObjectLegalHold(ctx, &s3.PutObjectLegalHoldInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
VersionId: getPtr("invalid_version_id"),
|
|
LegalHold: &types.ObjectLockLegalHold{
|
|
Status: types.ObjectLockLegalHoldStatusOn,
|
|
},
|
|
})
|
|
cancel()
|
|
return checkApiErr(err, s3err.GetInvalidArgumentErr(s3err.InvalidArgVersionId, "invalid_version_id"))
|
|
}, withLock())
|
|
}
|
|
|
|
func Versioning_PutObjectLegalHold_non_existing_object_version(s *S3Conf) error {
|
|
testName := "Versioning_PutObjectLegalHold_non_existing_object_version"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
obj := "my-obj"
|
|
_, err := createObjVersions(s3client, bucket, obj, 3)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
_, err = s3client.PutObjectLegalHold(ctx, &s3.PutObjectLegalHoldInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
VersionId: getPtr("01G65Z755AFWAKHE12NY0CQ9FH"),
|
|
LegalHold: &types.ObjectLockLegalHold{
|
|
Status: types.ObjectLockLegalHoldStatusOn,
|
|
},
|
|
})
|
|
cancel()
|
|
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchVersion)); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}, withLock(), withVersioning(types.BucketVersioningStatusEnabled))
|
|
}
|
|
|
|
func Versioning_GetObjectLegalHold_invalid_versionId(s *S3Conf) error {
|
|
testName := "Versioning_GetObjectLegalHold_invalid_versionId"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
obj := "my-obj"
|
|
_, err := createObjVersions(s3client, bucket, obj, 3)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
_, err = s3client.GetObjectLegalHold(ctx, &s3.GetObjectLegalHoldInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
VersionId: getPtr("invalid_version_id"),
|
|
})
|
|
cancel()
|
|
return checkApiErr(err, s3err.GetInvalidArgumentErr(s3err.InvalidArgVersionId, "invalid_version_id"))
|
|
}, withLock())
|
|
}
|
|
|
|
func Versioning_GetObjectLegalHold_non_existing_object_version(s *S3Conf) error {
|
|
testName := "Versioning_GetObjectLegalHold_non_existing_object_version"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
obj := "my-obj"
|
|
_, err := createObjVersions(s3client, bucket, obj, 3)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
_, err = s3client.GetObjectLegalHold(ctx, &s3.GetObjectLegalHoldInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
VersionId: getPtr("01G65Z755AFWAKHE12NY0CQ9FH"),
|
|
})
|
|
cancel()
|
|
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchVersion)); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}, withLock(), withVersioning(types.BucketVersioningStatusEnabled))
|
|
}
|
|
|
|
func Versioning_PutGetObjectLegalHold_delete_marker(s *S3Conf) error {
|
|
testName := "Versioning_PutGetObjectLegalHold_delete_marker"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
obj := "my-object"
|
|
_, err := putObjectWithData(10, &s3.PutObjectInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
}, s3client)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
out, err := s3client.DeleteObject(ctx, &s3.DeleteObjectInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
})
|
|
cancel()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// PutObjectLegalHold
|
|
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
|
|
_, err = s3client.PutObjectLegalHold(ctx, &s3.PutObjectLegalHoldInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
VersionId: out.VersionId,
|
|
LegalHold: &types.ObjectLockLegalHold{
|
|
Status: types.ObjectLockLegalHoldStatusOn,
|
|
},
|
|
})
|
|
cancel()
|
|
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrMethodNotAllowed)); err != nil {
|
|
return err
|
|
}
|
|
|
|
// GetObjectLegalHold
|
|
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
|
|
_, err = s3client.GetObjectLegalHold(ctx, &s3.GetObjectLegalHoldInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
VersionId: out.VersionId,
|
|
})
|
|
cancel()
|
|
|
|
return checkApiErr(err, s3err.GetAPIError(s3err.ErrMethodNotAllowed))
|
|
}, withLock())
|
|
}
|
|
|
|
func Versioning_Put_GetObjectLegalHold_success(s *S3Conf) error {
|
|
testName := "Versioning_Put_GetObjectLegalHold_success"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
obj := "my-obj"
|
|
objVersions, err := createObjVersions(s3client, bucket, obj, 3)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
objVersion := objVersions[1]
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
_, err = s3client.PutObjectLegalHold(ctx, &s3.PutObjectLegalHoldInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
VersionId: objVersion.VersionId,
|
|
LegalHold: &types.ObjectLockLegalHold{
|
|
Status: types.ObjectLockLegalHoldStatusOn,
|
|
},
|
|
})
|
|
cancel()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
|
|
res, err := s3client.GetObjectLegalHold(ctx, &s3.GetObjectLegalHoldInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
VersionId: objVersion.VersionId,
|
|
})
|
|
cancel()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if res.LegalHold.Status != types.ObjectLockLegalHoldStatusOn {
|
|
return fmt.Errorf("expected the object version legal hold status to be %v, instead got %v",
|
|
types.ObjectLockLegalHoldStatusOn, res.LegalHold.Status)
|
|
}
|
|
|
|
return cleanupLockedObjects(s3client, bucket, []objToDelete{
|
|
{
|
|
key: getString(objVersion.Key),
|
|
versionId: getString(objVersion.VersionId),
|
|
removeOnlyLeglHold: true,
|
|
},
|
|
})
|
|
}, withLock(), withVersioning(types.BucketVersioningStatusEnabled))
|
|
}
|
|
|
|
func Versioning_WORM_obj_version_locked_with_legal_hold(s *S3Conf) error {
|
|
testName := "Versioning_WORM_obj_version_locked_with_legal_hold"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
obj := "my-obj"
|
|
objVersions, err := createObjVersions(s3client, bucket, obj, 2)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
version := objVersions[1]
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
_, err = s3client.PutObjectLegalHold(ctx, &s3.PutObjectLegalHoldInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
VersionId: version.VersionId,
|
|
LegalHold: &types.ObjectLockLegalHold{
|
|
Status: types.ObjectLockLegalHoldStatusOn,
|
|
},
|
|
})
|
|
cancel()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
|
|
_, err = s3client.DeleteObject(ctx, &s3.DeleteObjectInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
VersionId: version.VersionId,
|
|
})
|
|
cancel()
|
|
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrObjectLocked)); err != nil {
|
|
return err
|
|
}
|
|
|
|
return cleanupLockedObjects(s3client, bucket, []objToDelete{
|
|
{
|
|
key: obj,
|
|
versionId: getString(version.VersionId),
|
|
removeOnlyLeglHold: true,
|
|
},
|
|
})
|
|
}, withLock(), withVersioning(types.BucketVersioningStatusEnabled))
|
|
}
|
|
|
|
func Versioning_WORM_obj_version_locked_with_governance_retention(s *S3Conf) error {
|
|
testName := "Versioning_WORM_obj_version_locked_with_governance_retention"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
obj := "my-obj"
|
|
objVersions, err := createObjVersions(s3client, bucket, obj, 2)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
version := objVersions[0]
|
|
|
|
rDate := time.Now().Add(time.Hour * 48)
|
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
_, err = s3client.PutObjectRetention(ctx, &s3.PutObjectRetentionInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
VersionId: version.VersionId,
|
|
Retention: &types.ObjectLockRetention{
|
|
Mode: types.ObjectLockRetentionModeGovernance,
|
|
RetainUntilDate: &rDate,
|
|
},
|
|
})
|
|
cancel()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
|
|
_, err = s3client.DeleteObject(ctx, &s3.DeleteObjectInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
VersionId: version.VersionId,
|
|
})
|
|
cancel()
|
|
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrObjectLocked)); err != nil {
|
|
return err
|
|
}
|
|
|
|
return cleanupLockedObjects(s3client, bucket, []objToDelete{
|
|
{
|
|
key: obj,
|
|
versionId: getString(version.VersionId),
|
|
},
|
|
})
|
|
}, withLock(), withVersioning(types.BucketVersioningStatusEnabled))
|
|
}
|
|
|
|
func Versioning_WORM_obj_version_locked_with_compliance_retention(s *S3Conf) error {
|
|
testName := "Versioning_WORM_obj_version_locked_with_compliance_retention"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
obj := "my-obj"
|
|
objVersions, err := createObjVersions(s3client, bucket, obj, 2)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
version := objVersions[0]
|
|
|
|
rDate := time.Now().Add(time.Hour * 48)
|
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
_, err = s3client.PutObjectRetention(ctx, &s3.PutObjectRetentionInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
VersionId: version.VersionId,
|
|
Retention: &types.ObjectLockRetention{
|
|
Mode: types.ObjectLockRetentionModeCompliance,
|
|
RetainUntilDate: &rDate,
|
|
},
|
|
})
|
|
cancel()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
|
|
_, err = s3client.DeleteObject(ctx, &s3.DeleteObjectInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
VersionId: version.VersionId,
|
|
})
|
|
cancel()
|
|
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrObjectLocked)); err != nil {
|
|
return err
|
|
}
|
|
|
|
return cleanupLockedObjects(s3client, bucket, []objToDelete{
|
|
{
|
|
key: obj,
|
|
versionId: getString(version.VersionId),
|
|
isCompliance: true,
|
|
},
|
|
})
|
|
}, withLock(), withVersioning(types.BucketVersioningStatusEnabled))
|
|
}
|
|
|
|
func Versioning_WORM_delete_marker_locked_object_legal_hold(s *S3Conf) error {
|
|
testName := "Versioning_WORM_delete_marker_locked_object_legal_hold"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
obj := "my-obj"
|
|
objVersions, err := createObjVersions(s3client, bucket, obj, 1)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
version := objVersions[0]
|
|
objVersions[0].IsLatest = getPtr(false)
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
_, err = s3client.PutObjectLegalHold(ctx, &s3.PutObjectLegalHoldInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
LegalHold: &types.ObjectLockLegalHold{
|
|
Status: types.ObjectLockLegalHoldStatusOn,
|
|
},
|
|
})
|
|
cancel()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
|
|
out, err := s3client.DeleteObject(ctx, &s3.DeleteObjectInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
})
|
|
cancel()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
delMarkers := []types.DeleteMarkerEntry{
|
|
{
|
|
IsLatest: getPtr(true),
|
|
Key: &obj,
|
|
VersionId: out.VersionId,
|
|
},
|
|
}
|
|
|
|
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
|
|
resp, err := s3client.ListObjectVersions(ctx, &s3.ListObjectVersionsInput{
|
|
Bucket: &bucket,
|
|
})
|
|
cancel()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if !compareVersions(objVersions, resp.Versions) {
|
|
return fmt.Errorf("expected the object versions to be %v, instead got %v", objVersions, resp.Versions)
|
|
}
|
|
if !compareDelMarkers(delMarkers, resp.DeleteMarkers) {
|
|
return fmt.Errorf("expected the object delete markers to be %v, instead got %v", delMarkers, resp.DeleteMarkers)
|
|
}
|
|
|
|
return cleanupLockedObjects(s3client, bucket, []objToDelete{
|
|
{
|
|
key: obj,
|
|
versionId: getString(version.VersionId),
|
|
removeOnlyLeglHold: true,
|
|
},
|
|
})
|
|
}, withLock())
|
|
}
|
|
|
|
func Versioning_WORM_delete_marker_locked_object_governance_retention(s *S3Conf) error {
|
|
testName := "Versioning_WORM_delete_marker_locked_object_governance_retention"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
obj := "my-obj"
|
|
objVersions, err := createObjVersions(s3client, bucket, obj, 1)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
version := objVersions[0]
|
|
objVersions[0].IsLatest = getPtr(false)
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
_, err = s3client.PutObjectRetention(ctx, &s3.PutObjectRetentionInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
Retention: &types.ObjectLockRetention{
|
|
Mode: types.ObjectLockRetentionModeGovernance,
|
|
RetainUntilDate: getPtr(time.Now().AddDate(1, 0, 0)),
|
|
},
|
|
})
|
|
cancel()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
|
|
out, err := s3client.DeleteObject(ctx, &s3.DeleteObjectInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
})
|
|
cancel()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
delMarkers := []types.DeleteMarkerEntry{
|
|
{
|
|
IsLatest: getPtr(true),
|
|
Key: &obj,
|
|
VersionId: out.VersionId,
|
|
},
|
|
}
|
|
|
|
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
|
|
resp, err := s3client.ListObjectVersions(ctx, &s3.ListObjectVersionsInput{
|
|
Bucket: &bucket,
|
|
})
|
|
cancel()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if !compareVersions(objVersions, resp.Versions) {
|
|
return fmt.Errorf("expected the object versions to be %v, instead got %v", objVersions, resp.Versions)
|
|
}
|
|
if !compareDelMarkers(delMarkers, resp.DeleteMarkers) {
|
|
return fmt.Errorf("expected the object delete markers to be %v, instead got %v", delMarkers, resp.DeleteMarkers)
|
|
}
|
|
|
|
return cleanupLockedObjects(s3client, bucket, []objToDelete{
|
|
{
|
|
key: obj,
|
|
versionId: getString(version.VersionId),
|
|
isCompliance: false,
|
|
},
|
|
})
|
|
}, withLock())
|
|
}
|
|
|
|
func Versioning_WORM_delete_marker_locked_object_compliance_retention(s *S3Conf) error {
|
|
testName := "Versioning_WORM_delete_marker_locked_object_compliance_retention"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
obj := "my-obj"
|
|
objVersions, err := createObjVersions(s3client, bucket, obj, 1)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
version := objVersions[0]
|
|
objVersions[0].IsLatest = getPtr(false)
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
_, err = s3client.PutObjectRetention(ctx, &s3.PutObjectRetentionInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
Retention: &types.ObjectLockRetention{
|
|
Mode: types.ObjectLockRetentionModeCompliance,
|
|
RetainUntilDate: getPtr(time.Now().AddDate(1, 0, 0)),
|
|
},
|
|
})
|
|
cancel()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
|
|
out, err := s3client.DeleteObject(ctx, &s3.DeleteObjectInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
})
|
|
cancel()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
delMarkers := []types.DeleteMarkerEntry{
|
|
{
|
|
IsLatest: getPtr(true),
|
|
Key: &obj,
|
|
VersionId: out.VersionId,
|
|
},
|
|
}
|
|
|
|
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
|
|
resp, err := s3client.ListObjectVersions(ctx, &s3.ListObjectVersionsInput{
|
|
Bucket: &bucket,
|
|
})
|
|
cancel()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if !compareVersions(objVersions, resp.Versions) {
|
|
return fmt.Errorf("expected the object versions to be %v, instead got %v", objVersions, resp.Versions)
|
|
}
|
|
if !compareDelMarkers(delMarkers, resp.DeleteMarkers) {
|
|
return fmt.Errorf("expected the object delete markers to be %v, instead got %v", delMarkers, resp.DeleteMarkers)
|
|
}
|
|
|
|
return cleanupLockedObjects(s3client, bucket, []objToDelete{
|
|
{
|
|
key: obj,
|
|
versionId: getString(version.VersionId),
|
|
isCompliance: true,
|
|
},
|
|
})
|
|
}, withLock())
|
|
}
|
|
|
|
func Versioning_WORM_PutObject_overwrite_locked_object(s *S3Conf) error {
|
|
testName := "Versioning_WORM_PutObject_overwrite_locked_object"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
obj := "my-obj"
|
|
versions, err := createObjVersions(s3client, bucket, obj, 1)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
v := versions[0]
|
|
v.IsLatest = getPtr(false)
|
|
|
|
// lock the object with legal hold
|
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
_, err = s3client.PutObjectLegalHold(ctx, &s3.PutObjectLegalHoldInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
LegalHold: &types.ObjectLockLegalHold{
|
|
Status: types.ObjectLockLegalHoldStatusOn,
|
|
},
|
|
})
|
|
cancel()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
dataLen := int64(10)
|
|
|
|
// overwrite the locked object with a new version
|
|
r, err := putObjectWithData(dataLen, &s3.PutObjectInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
}, s3client)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
version := types.ObjectVersion{
|
|
ETag: r.res.ETag,
|
|
IsLatest: getPtr(true),
|
|
Key: &obj,
|
|
Size: &dataLen,
|
|
VersionId: r.res.VersionId,
|
|
StorageClass: types.ObjectVersionStorageClassStandard,
|
|
ChecksumType: r.res.ChecksumType,
|
|
}
|
|
|
|
result := []types.ObjectVersion{version, v}
|
|
|
|
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
|
|
out, err := s3client.ListObjectVersions(ctx, &s3.ListObjectVersionsInput{
|
|
Bucket: &bucket,
|
|
})
|
|
cancel()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if !compareVersions(result, out.Versions) {
|
|
return fmt.Errorf("expected the object versions to be %v, instead got %v", result, out.Versions)
|
|
}
|
|
|
|
return cleanupLockedObjects(s3client, bucket, []objToDelete{
|
|
{
|
|
key: obj,
|
|
versionId: getString(v.VersionId),
|
|
removeOnlyLeglHold: true,
|
|
},
|
|
})
|
|
}, withLock())
|
|
}
|
|
|
|
func Versioning_WORM_CopyObject_overwrite_locked_object(s *S3Conf) error {
|
|
testName := "Versioning_WORM_CopyObject_overwrite_locked_object"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
obj := "my-obj"
|
|
versions, err := createObjVersions(s3client, bucket, obj, 1)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
v := versions[0]
|
|
v.IsLatest = getPtr(false)
|
|
|
|
// lock the object with legal hold
|
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
_, err = s3client.PutObjectLegalHold(ctx, &s3.PutObjectLegalHoldInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
LegalHold: &types.ObjectLockLegalHold{
|
|
Status: types.ObjectLockLegalHoldStatusOn,
|
|
},
|
|
})
|
|
cancel()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// create a source object version
|
|
srcObj := "source-object"
|
|
srcVersions, err := createObjVersions(s3client, bucket, srcObj, 1)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
srcVersion := srcVersions[0]
|
|
|
|
// overwrite the locked object with a new version with CopyObject
|
|
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
|
|
copyResult, err := s3client.CopyObject(ctx, &s3.CopyObjectInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
CopySource: getPtr(fmt.Sprintf("%s/%s", bucket, srcObj)),
|
|
})
|
|
cancel()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
version := types.ObjectVersion{
|
|
ETag: copyResult.CopyObjectResult.ETag,
|
|
IsLatest: getPtr(true),
|
|
Key: &obj,
|
|
Size: srcVersion.Size,
|
|
VersionId: copyResult.VersionId,
|
|
StorageClass: types.ObjectVersionStorageClassStandard,
|
|
ChecksumType: copyResult.CopyObjectResult.ChecksumType,
|
|
}
|
|
|
|
result := []types.ObjectVersion{version, v, srcVersion}
|
|
|
|
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
|
|
out, err := s3client.ListObjectVersions(ctx, &s3.ListObjectVersionsInput{
|
|
Bucket: &bucket,
|
|
})
|
|
cancel()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if !compareVersions(result, out.Versions) {
|
|
return fmt.Errorf("expected the object versions to be %v, instead got %v", result, out.Versions)
|
|
}
|
|
|
|
return cleanupLockedObjects(s3client, bucket, []objToDelete{
|
|
{
|
|
key: obj,
|
|
versionId: getString(v.VersionId),
|
|
removeOnlyLeglHold: true,
|
|
},
|
|
})
|
|
}, withLock())
|
|
}
|
|
|
|
func Versioning_WORM_CompleteMultipartUpload_overwrite_locked_object(s *S3Conf) error {
|
|
testName := "Versioning_WORM_CompleteMultipartUpload_overwrite_locked_object"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
obj := "my-obj"
|
|
versions, err := createObjVersions(s3client, bucket, obj, 1)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
v := versions[0]
|
|
v.IsLatest = getPtr(false)
|
|
|
|
// lock the object with legal hold
|
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
_, err = s3client.PutObjectLegalHold(ctx, &s3.PutObjectLegalHoldInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
LegalHold: &types.ObjectLockLegalHold{
|
|
Status: types.ObjectLockLegalHoldStatusOn,
|
|
},
|
|
})
|
|
cancel()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
dataLen := int64(5 * 1024 * 1024)
|
|
|
|
// overwrite the locked object with a new version
|
|
mp, err := createMp(s3client, bucket, obj)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
parts, _, err := uploadParts(s3client, dataLen, 1, bucket, obj, *mp.UploadId)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
part := parts[0]
|
|
|
|
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
|
|
res, err := s3client.CompleteMultipartUpload(ctx, &s3.CompleteMultipartUploadInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
MultipartUpload: &types.CompletedMultipartUpload{
|
|
Parts: []types.CompletedPart{
|
|
{
|
|
ETag: part.ETag,
|
|
PartNumber: part.PartNumber,
|
|
},
|
|
},
|
|
},
|
|
UploadId: mp.UploadId,
|
|
})
|
|
cancel()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
version := types.ObjectVersion{
|
|
ETag: res.ETag,
|
|
IsLatest: getPtr(true),
|
|
Key: &obj,
|
|
Size: &dataLen,
|
|
VersionId: res.VersionId,
|
|
StorageClass: types.ObjectVersionStorageClassStandard,
|
|
}
|
|
|
|
result := []types.ObjectVersion{version, v}
|
|
|
|
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
|
|
out, err := s3client.ListObjectVersions(ctx, &s3.ListObjectVersionsInput{
|
|
Bucket: &bucket,
|
|
})
|
|
cancel()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if !compareVersions(result, out.Versions) {
|
|
return fmt.Errorf("expected the object versions to be %v, instead got %v", result, out.Versions)
|
|
}
|
|
|
|
return cleanupLockedObjects(s3client, bucket, []objToDelete{
|
|
{
|
|
key: obj,
|
|
versionId: getString(v.VersionId),
|
|
removeOnlyLeglHold: true,
|
|
},
|
|
})
|
|
}, withLock())
|
|
}
|
|
|
|
func Versioning_WORM_remove_delete_marker_under_bucket_default_retention(s *S3Conf) error {
|
|
testName := "Versioning_WORM_remove_delete_marker_under_bucket_default_retention"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
_, err := s3client.PutObjectLockConfiguration(ctx, &s3.PutObjectLockConfigurationInput{
|
|
Bucket: &bucket,
|
|
ObjectLockConfiguration: &types.ObjectLockConfiguration{
|
|
ObjectLockEnabled: types.ObjectLockEnabledEnabled,
|
|
Rule: &types.ObjectLockRule{
|
|
DefaultRetention: &types.DefaultRetention{
|
|
Mode: types.ObjectLockRetentionModeGovernance,
|
|
Days: getPtr(int32(5)),
|
|
},
|
|
},
|
|
},
|
|
})
|
|
cancel()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
obj := "my-object"
|
|
versions, err := createObjVersions(s3client, bucket, obj, 3)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Create a delete marker
|
|
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
|
|
out, err := s3client.DeleteObject(ctx, &s3.DeleteObjectInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
})
|
|
cancel()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Delete the delete marker
|
|
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
|
|
_, err = s3client.DeleteObject(ctx, &s3.DeleteObjectInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
VersionId: out.VersionId,
|
|
})
|
|
cancel()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
|
|
resp, err := s3client.ListObjectVersions(ctx, &s3.ListObjectVersionsInput{
|
|
Bucket: &bucket,
|
|
})
|
|
cancel()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if !compareVersions(versions, resp.Versions) {
|
|
return fmt.Errorf("expected the object vresions to be %v, instead got %v", versions, resp.Versions)
|
|
}
|
|
if len(resp.DeleteMarkers) != 0 {
|
|
return fmt.Errorf("expected empty delete markers list, instead got %v", resp.DeleteMarkers)
|
|
}
|
|
|
|
//
|
|
lockedVersions := make([]objToDelete, 0, len(versions))
|
|
for _, v := range versions {
|
|
lockedVersions = append(lockedVersions, objToDelete{
|
|
key: obj,
|
|
versionId: getString(v.VersionId),
|
|
isCompliance: false,
|
|
})
|
|
}
|
|
return cleanupLockedObjects(s3client, bucket, lockedVersions)
|
|
}, withLock())
|
|
}
|
|
|
|
func Versioning_AccessControl_GetObjectVersion(s *S3Conf) error {
|
|
testName := "Versioning_AccessControl_GetObjectVersion"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
obj := "my-obj"
|
|
objData, err := putObjectWithData(10, &s3.PutObjectInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
}, s3client)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
testuser := getUser("user")
|
|
err = createUsers(s, []user{testuser})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
doc := genPolicyDoc("Allow", fmt.Sprintf(`"%s"`, testuser.access), `"s3:GetObject"`, fmt.Sprintf(`"arn:aws:s3:::%s/*"`, bucket))
|
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
_, err = s3client.PutBucketPolicy(ctx, &s3.PutBucketPolicyInput{
|
|
Bucket: &bucket,
|
|
Policy: &doc,
|
|
})
|
|
cancel()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
userClient := s.getUserClient(testuser)
|
|
|
|
// querying with versionId should return access denied
|
|
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
|
|
_, err = userClient.GetObject(ctx, &s3.GetObjectInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
VersionId: objData.res.VersionId,
|
|
})
|
|
defer cancel()
|
|
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrAccessDenied)); err != nil {
|
|
return err
|
|
}
|
|
|
|
// grant the user s3:GetObjectVersion
|
|
doc = genPolicyDoc("Allow", fmt.Sprintf(`"%s"`, testuser.access), `"s3:GetObjectVersion"`, fmt.Sprintf(`"arn:aws:s3:::%s/*"`, bucket))
|
|
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
|
|
_, err = s3client.PutBucketPolicy(ctx, &s3.PutBucketPolicyInput{
|
|
Bucket: &bucket,
|
|
Policy: &doc,
|
|
})
|
|
cancel()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
|
|
_, err = userClient.GetObject(ctx, &s3.GetObjectInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
VersionId: objData.res.VersionId,
|
|
})
|
|
defer cancel()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}, withVersioning(types.BucketVersioningStatusEnabled))
|
|
}
|
|
|
|
func Versioning_AccessControl_HeadObjectVersion(s *S3Conf) error {
|
|
testName := "Versioning_AccessControl_HeadObjectVersion"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
obj := "my-obj"
|
|
objData, err := putObjectWithData(10, &s3.PutObjectInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
}, s3client)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
testuser := getUser("user")
|
|
err = createUsers(s, []user{testuser})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
doc := genPolicyDoc("Allow", fmt.Sprintf(`"%s"`, testuser.access), `"s3:GetObject"`, fmt.Sprintf(`"arn:aws:s3:::%s/*"`, bucket))
|
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
_, err = s3client.PutBucketPolicy(ctx, &s3.PutBucketPolicyInput{
|
|
Bucket: &bucket,
|
|
Policy: &doc,
|
|
})
|
|
cancel()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
userClient := s.getUserClient(testuser)
|
|
|
|
// querying with versionId should return access denied
|
|
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
|
|
_, err = userClient.HeadObject(ctx, &s3.HeadObjectInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
VersionId: objData.res.VersionId,
|
|
})
|
|
cancel()
|
|
if err := checkSdkApiErr(err, http.StatusText(http.StatusForbidden)); err != nil {
|
|
return err
|
|
}
|
|
|
|
// grant the user s3:GetObjectVersion
|
|
doc = genPolicyDoc("Allow", fmt.Sprintf(`"%s"`, testuser.access), `"s3:GetObjectVersion"`, fmt.Sprintf(`"arn:aws:s3:::%s/*"`, bucket))
|
|
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
|
|
_, err = s3client.PutBucketPolicy(ctx, &s3.PutBucketPolicyInput{
|
|
Bucket: &bucket,
|
|
Policy: &doc,
|
|
})
|
|
cancel()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
|
|
_, err = userClient.HeadObject(ctx, &s3.HeadObjectInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
VersionId: objData.res.VersionId,
|
|
})
|
|
cancel()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}, withVersioning(types.BucketVersioningStatusEnabled))
|
|
}
|
|
|
|
func Versioning_AccessControl_object_tagging_policy(s *S3Conf) error {
|
|
testName := "Versioning_AccessControl_PutObjectTagging_policy"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
object := "my-object"
|
|
res, err := putObjectWithData(10, &s3.PutObjectInput{
|
|
Bucket: &bucket,
|
|
Key: &object,
|
|
}, s3client)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
testuser := getUser("user")
|
|
err = createUsers(s, []user{testuser})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
userClient := s.getUserClient(testuser)
|
|
|
|
putGetDeleteObjectTagging := func(versionId *string, denyAccess bool) error {
|
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
_, err := userClient.PutObjectTagging(ctx, &s3.PutObjectTaggingInput{
|
|
Bucket: &bucket,
|
|
Key: &object,
|
|
VersionId: versionId,
|
|
Tagging: &types.Tagging{
|
|
TagSet: []types.Tag{
|
|
{Key: getPtr("key"), Value: getPtr("value")},
|
|
},
|
|
},
|
|
})
|
|
cancel()
|
|
if denyAccess {
|
|
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrAccessDenied)); err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
|
|
_, err = userClient.GetObjectTagging(ctx, &s3.GetObjectTaggingInput{
|
|
Bucket: &bucket,
|
|
Key: &object,
|
|
VersionId: versionId,
|
|
})
|
|
cancel()
|
|
if denyAccess {
|
|
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrAccessDenied)); err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
|
|
_, err = userClient.DeleteObjectTagging(ctx, &s3.DeleteObjectTaggingInput{
|
|
Bucket: &bucket,
|
|
Key: &object,
|
|
VersionId: versionId,
|
|
})
|
|
cancel()
|
|
if denyAccess {
|
|
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrAccessDenied)); err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
policy := genPolicyDoc("Allow", fmt.Sprintf(`"%s"`, testuser.access), `["s3:PutObjectVersionTagging", "s3:GetObjectVersionTagging", "s3:DeleteObjectVersionTagging"]`, fmt.Sprintf(`"arn:aws:s3:::%s/*"`, bucket))
|
|
err = putBucketPolicy(s3client, bucket, policy)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// deny without versionId
|
|
err = putGetDeleteObjectTagging(nil, true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// allow with versionId
|
|
err = putGetDeleteObjectTagging(res.res.VersionId, false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
policy = genPolicyDoc("Allow", fmt.Sprintf(`"%s"`, testuser.access), `["s3:PutObjectTagging", "s3:GetObjectTagging", "s3:DeleteObjectTagging"]`, fmt.Sprintf(`"arn:aws:s3:::%s/*"`, bucket))
|
|
err = putBucketPolicy(s3client, bucket, policy)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// allow without versionId
|
|
err = putGetDeleteObjectTagging(nil, false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// deny with versionId
|
|
err = putGetDeleteObjectTagging(res.res.VersionId, true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}, withVersioning(types.BucketVersioningStatusEnabled))
|
|
}
|
|
|
|
func Versioning_AccessControl_DeleteObject_policy(s *S3Conf) error {
|
|
testName := "Versioning_AccessControl_DeleteObject_policy"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
obj := "my-object"
|
|
testuser := getUser("user")
|
|
err := createUsers(s, []user{testuser})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
userClient := s.getUserClient(testuser)
|
|
|
|
delObject := func(versionId *string, denyAccess bool) error {
|
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
_, err := userClient.DeleteObject(ctx, &s3.DeleteObjectInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
VersionId: versionId,
|
|
})
|
|
cancel()
|
|
if denyAccess {
|
|
return checkApiErr(err, s3err.GetAPIError(s3err.ErrAccessDenied))
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
res, err := putObjectWithData(10, &s3.PutObjectInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
}, s3client)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
policy := genPolicyDoc("Allow", fmt.Sprintf(`"%s"`, testuser.access), `"s3:DeleteObject"`, fmt.Sprintf(`"arn:aws:s3:::%s/*"`, bucket))
|
|
err = putBucketPolicy(s3client, bucket, policy)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// deny with versionId
|
|
err = delObject(res.res.VersionId, true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// allow without versionId
|
|
err = delObject(nil, false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
policy = genPolicyDoc("Allow", fmt.Sprintf(`"%s"`, testuser.access), `"s3:DeleteObjectVersion"`, fmt.Sprintf(`"arn:aws:s3:::%s/*"`, bucket))
|
|
err = putBucketPolicy(s3client, bucket, policy)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// recreate the object
|
|
res, err = putObjectWithData(10, &s3.PutObjectInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
}, s3client)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// deny without versionId
|
|
err = delObject(nil, true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// allow with versionId
|
|
err = delObject(res.res.VersionId, false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}, withVersioning(types.BucketVersioningStatusEnabled))
|
|
}
|
|
|
|
func Versioning_AccessControl_GetObjectAttributes_policy(s *S3Conf) error {
|
|
testName := "Versioning_AccessControl_GetObjectAttributes_policy"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
obj := "my-object"
|
|
res, err := putObjectWithData(10, &s3.PutObjectInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
}, s3client)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
testuser := getUser("user")
|
|
err = createUsers(s, []user{testuser})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
userClient := s.getUserClient(testuser)
|
|
|
|
getObjectAttr := func(versionId *string, denyAccess bool) error {
|
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
_, err := userClient.GetObjectAttributes(ctx, &s3.GetObjectAttributesInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
VersionId: versionId,
|
|
ObjectAttributes: types.ObjectAttributesChecksum.Values(),
|
|
})
|
|
cancel()
|
|
if denyAccess {
|
|
return checkApiErr(err, s3err.GetAPIError(s3err.ErrAccessDenied))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
policy := genPolicyDoc("Allow", fmt.Sprintf(`"%s"`, testuser.access), `"s3:GetObjectAttributes"`, fmt.Sprintf(`"arn:aws:s3:::%s/*"`, bucket))
|
|
err = putBucketPolicy(s3client, bucket, policy)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// deny with versionId
|
|
err = getObjectAttr(res.res.VersionId, true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// allow without versionId
|
|
err = getObjectAttr(nil, false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
policy = genPolicyDoc("Allow", fmt.Sprintf(`"%s"`, testuser.access), `"s3:GetObjectVersionAttributes"`, fmt.Sprintf(`"arn:aws:s3:::%s/*"`, bucket))
|
|
err = putBucketPolicy(s3client, bucket, policy)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// deny without versionId
|
|
err = getObjectAttr(nil, true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// allow with versionId
|
|
err = getObjectAttr(res.res.VersionId, false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}, withVersioning(types.BucketVersioningStatusEnabled))
|
|
}
|
|
|
|
func VersioningDisabled_GetBucketVersioning_not_configured(s *S3Conf) error {
|
|
testName := "VersioningDisabled_GetBucketVersioning_not_configured"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
err := putBucketVersioningStatus(s3client, bucket, types.BucketVersioningStatusEnabled)
|
|
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrVersioningNotConfigured)); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func VersioningDisabled_PutBucketVersioning_not_configured(s *S3Conf) error {
|
|
testName := "VersioningDisabled_PutBucketVersioning_not_configured"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
_, err := s3client.GetBucketVersioning(ctx, &s3.GetBucketVersioningInput{
|
|
Bucket: &bucket,
|
|
})
|
|
cancel()
|
|
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrVersioningNotConfigured)); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func Versioning_concurrent_upload_object(s *S3Conf) error {
|
|
testName := "Versioninig_concurrent_upload_object"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
obj := "my-obj"
|
|
versionCount := 5
|
|
// Channel to collect errors
|
|
errCh := make(chan error, versionCount)
|
|
|
|
uploadVersion := func(wg *sync.WaitGroup) {
|
|
defer wg.Done()
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
res, err := s3client.PutObject(ctx, &s3.PutObjectInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
})
|
|
cancel()
|
|
if err != nil {
|
|
// Send error to the channel
|
|
errCh <- err
|
|
return
|
|
}
|
|
|
|
fmt.Printf("uploaded object successfully: versionId: %v\n", *res.VersionId)
|
|
}
|
|
|
|
wg := &sync.WaitGroup{}
|
|
wg.Add(versionCount)
|
|
|
|
for range versionCount {
|
|
go uploadVersion(wg)
|
|
}
|
|
|
|
wg.Wait()
|
|
close(errCh)
|
|
|
|
// Check if there were any errors
|
|
for err := range errCh {
|
|
if err != nil {
|
|
fmt.Printf("error uploading an object: %v\n", err.Error())
|
|
return err
|
|
}
|
|
}
|
|
|
|
// List object versions after all uploads
|
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
res, err := s3client.ListObjectVersions(ctx, &s3.ListObjectVersionsInput{
|
|
Bucket: &bucket,
|
|
})
|
|
cancel()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if len(res.Versions) != versionCount {
|
|
return fmt.Errorf("expected %v object versions, instead got %v",
|
|
versionCount, len(res.Versions))
|
|
}
|
|
|
|
return nil
|
|
}, withVersioning(types.BucketVersioningStatusEnabled))
|
|
}
|
|
|
|
func Versioning_GetObjectTagging_invalid_versionId(s *S3Conf) error {
|
|
testName := "Versioning_GetObjectTagging_invalid_versionId"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
obj := "my-object"
|
|
_, err := putObjectWithData(4, &s3.PutObjectInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
}, s3client)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
_, err = s3client.GetObjectTagging(ctx, &s3.GetObjectTaggingInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
VersionId: getPtr("invalid_versionId"),
|
|
})
|
|
cancel()
|
|
return checkApiErr(err, s3err.GetInvalidArgumentErr(s3err.InvalidArgVersionId, "invalid_versionId"))
|
|
})
|
|
}
|
|
|
|
func Versioning_PutObjectTagging_non_existing_object_version(s *S3Conf) error {
|
|
testName := "Versioning_PutObjectTagging_non_existing_object_version"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
obj := "my-object"
|
|
_, err := putObjectWithData(4, &s3.PutObjectInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
}, s3client)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
_, err = s3client.PutObjectTagging(ctx, &s3.PutObjectTaggingInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
Tagging: &types.Tagging{
|
|
TagSet: []types.Tag{{Key: getPtr("key"), Value: getPtr("value")}},
|
|
},
|
|
VersionId: getPtr("01K97XE6PJQ1A4X5TJFDHK4EMC"),
|
|
})
|
|
cancel()
|
|
return checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchVersion))
|
|
}, withVersioning(types.BucketVersioningStatusEnabled))
|
|
}
|
|
|
|
func Versioning_PutGetDeleteObjectTagging_delete_marker(s *S3Conf) error {
|
|
testName := "Versioning_PutGetDeleteObjectTagging_delete_marker"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
obj := "my-object"
|
|
_, err := putObjectWithData(10, &s3.PutObjectInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
}, s3client)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
out, err := s3client.DeleteObject(ctx, &s3.DeleteObjectInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
})
|
|
cancel()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// PutObjectTagging
|
|
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
|
|
_, err = s3client.PutObjectTagging(ctx, &s3.PutObjectTaggingInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
VersionId: out.VersionId,
|
|
Tagging: &types.Tagging{
|
|
TagSet: []types.Tag{{Key: getPtr("key"), Value: getPtr("value")}},
|
|
},
|
|
})
|
|
cancel()
|
|
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrMethodNotAllowed)); err != nil {
|
|
return err
|
|
}
|
|
|
|
// GetObjectTagging
|
|
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
|
|
_, err = s3client.GetObjectTagging(ctx, &s3.GetObjectTaggingInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
VersionId: out.VersionId,
|
|
})
|
|
cancel()
|
|
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrMethodNotAllowed)); err != nil {
|
|
return err
|
|
}
|
|
|
|
// DeleteObjectTagging
|
|
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
|
|
_, err = s3client.DeleteObjectTagging(ctx, &s3.DeleteObjectTaggingInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
VersionId: out.VersionId,
|
|
})
|
|
cancel()
|
|
|
|
return checkApiErr(err, s3err.GetAPIError(s3err.ErrMethodNotAllowed))
|
|
}, withLock())
|
|
}
|
|
|
|
func Versioning_PutObjectTagging_invalid_versionId(s *S3Conf) error {
|
|
testName := "Versioning_PutObjectTagging_invalid_versionId"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
obj := "my-object"
|
|
_, err := putObjectWithData(4, &s3.PutObjectInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
}, s3client)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
_, err = s3client.PutObjectTagging(ctx, &s3.PutObjectTaggingInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
Tagging: &types.Tagging{
|
|
TagSet: []types.Tag{{Key: getPtr("key"), Value: getPtr("value")}},
|
|
},
|
|
VersionId: getPtr("invalid_versionId"),
|
|
})
|
|
cancel()
|
|
return checkApiErr(err, s3err.GetInvalidArgumentErr(s3err.InvalidArgVersionId, "invalid_versionId"))
|
|
}, withVersioning(types.BucketVersioningStatusEnabled))
|
|
}
|
|
|
|
func Versioning_GetObjectTagging_non_existing_object_version(s *S3Conf) error {
|
|
testName := "Versioning_GetObjectTagging_non_existing_object_version"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
obj := "my-object"
|
|
_, err := putObjectWithData(4, &s3.PutObjectInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
}, s3client)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
_, err = s3client.GetObjectTagging(ctx, &s3.GetObjectTaggingInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
VersionId: getPtr("01K97XE6PJQ1A4X5TJFDHK4EMC"),
|
|
})
|
|
cancel()
|
|
return checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchVersion))
|
|
}, withVersioning(types.BucketVersioningStatusEnabled))
|
|
}
|
|
|
|
func Versioning_DeleteObjectTagging_invalid_versionId(s *S3Conf) error {
|
|
testName := "Versioning_DeleteObjectTagging_invalid_versionId"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
obj := "my-object"
|
|
_, err := putObjectWithData(4, &s3.PutObjectInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
}, s3client)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
_, err = s3client.DeleteObjectTagging(ctx, &s3.DeleteObjectTaggingInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
VersionId: getPtr("invalid_versionId"),
|
|
})
|
|
cancel()
|
|
return checkApiErr(err, s3err.GetInvalidArgumentErr(s3err.InvalidArgVersionId, "invalid_versionId"))
|
|
})
|
|
}
|
|
|
|
func Versioning_DeleteObjectTagging_non_existing_object_version(s *S3Conf) error {
|
|
testName := "Versioning_DeleteObjectTagging_non_existing_object_version"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
obj := "my-object"
|
|
_, err := putObjectWithData(4, &s3.PutObjectInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
}, s3client)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
_, err = s3client.DeleteObjectTagging(ctx, &s3.DeleteObjectTaggingInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
VersionId: getPtr("01K97XE6PJQ1A4X5TJFDHK4EMC"),
|
|
})
|
|
cancel()
|
|
return checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchVersion))
|
|
}, withVersioning(types.BucketVersioningStatusEnabled))
|
|
}
|
|
|
|
func Versioning_PutGetDeleteObjectTagging_success(s *S3Conf) error {
|
|
testName := "Versioning_PutGetDeleteObjectTagging_success"
|
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
obj := "my-object"
|
|
versions, err := createObjVersions(s3client, bucket, obj, 5)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
versionId := versions[2].VersionId
|
|
|
|
tagging := types.Tagging{
|
|
TagSet: []types.Tag{
|
|
{Key: getPtr("key"), Value: getPtr("value")},
|
|
},
|
|
}
|
|
|
|
compareVersionId := func(expected, input *string) error {
|
|
if getString(expected) != getString(input) {
|
|
return fmt.Errorf("expected the response versionId to be %s, instead got %s", getString(expected), getString(input))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
res, err := s3client.PutObjectTagging(ctx, &s3.PutObjectTaggingInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
Tagging: &tagging,
|
|
VersionId: versionId,
|
|
})
|
|
cancel()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := compareVersionId(versionId, res.VersionId); err != nil {
|
|
return err
|
|
}
|
|
|
|
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
|
|
out, err := s3client.GetObjectTagging(ctx, &s3.GetObjectTaggingInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
VersionId: versionId,
|
|
})
|
|
cancel()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !areTagsSame(tagging.TagSet, out.TagSet) {
|
|
return fmt.Errorf("expected the object version tags to be %v, instead got %v", tagging.TagSet, out.TagSet)
|
|
}
|
|
if err := compareVersionId(versionId, out.VersionId); err != nil {
|
|
return err
|
|
}
|
|
|
|
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
|
|
resp, err := s3client.DeleteObjectTagging(ctx, &s3.DeleteObjectTaggingInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
VersionId: versionId,
|
|
})
|
|
cancel()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := compareVersionId(versionId, resp.VersionId); err != nil {
|
|
return err
|
|
}
|
|
|
|
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
|
|
r, err := s3client.GetObjectTagging(ctx, &s3.GetObjectTaggingInput{
|
|
Bucket: &bucket,
|
|
Key: &obj,
|
|
VersionId: versionId,
|
|
})
|
|
cancel()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if len(r.TagSet) != 0 {
|
|
return fmt.Errorf("expected empty tag set, instead got %v", r.TagSet)
|
|
}
|
|
|
|
return nil
|
|
}, withVersioning(types.BucketVersioningStatusEnabled))
|
|
}
|