feat: adds tagging support for object versions in posix

Closes #1343

Object version tagging support was previously missing in the gateway. The support is added with this PR. If versioning is not enabled at the gateway level and a user attempts to put, get, or delete object version tags, the gateway returns an `InvalidArgument`(Invalid versionId)
This commit is contained in:
niksis02
2025-11-04 23:47:48 +04:00
parent c06463424a
commit 8d2eeebce3
13 changed files with 265 additions and 49 deletions

View File

@@ -1085,7 +1085,7 @@ func (az *Azure) CopyObject(ctx context.Context, input s3response.CopyObjectInpu
}, nil
}
func (az *Azure) PutObjectTagging(ctx context.Context, bucket, object string, tags map[string]string) error {
func (az *Azure) PutObjectTagging(ctx context.Context, bucket, object, _ string, tags map[string]string) error {
client, err := az.getBlobClient(bucket, object)
if err != nil {
return err
@@ -1099,7 +1099,7 @@ func (az *Azure) PutObjectTagging(ctx context.Context, bucket, object string, ta
return nil
}
func (az *Azure) GetObjectTagging(ctx context.Context, bucket, object string) (map[string]string, error) {
func (az *Azure) GetObjectTagging(ctx context.Context, bucket, object, _ string) (map[string]string, error) {
client, err := az.getBlobClient(bucket, object)
if err != nil {
return nil, err
@@ -1113,7 +1113,7 @@ func (az *Azure) GetObjectTagging(ctx context.Context, bucket, object string) (m
return parseAzTags(tags.BlobTagSet), nil
}
func (az *Azure) DeleteObjectTagging(ctx context.Context, bucket, object string) error {
func (az *Azure) DeleteObjectTagging(ctx context.Context, bucket, object, _ string) error {
client, err := az.getBlobClient(bucket, object)
if err != nil {
return err

View File

@@ -83,9 +83,9 @@ type Backend interface {
DeleteBucketTagging(_ context.Context, bucket string) error
// object tagging operations
GetObjectTagging(_ context.Context, bucket, object string) (map[string]string, error)
PutObjectTagging(_ context.Context, bucket, object string, tags map[string]string) error
DeleteObjectTagging(_ context.Context, bucket, object string) error
GetObjectTagging(_ context.Context, bucket, object, versionId string) (map[string]string, error)
PutObjectTagging(_ context.Context, bucket, object, versionId string, tags map[string]string) error
DeleteObjectTagging(_ context.Context, bucket, object, versionId string) error
// object lock operations
PutObjectLockConfiguration(_ context.Context, bucket string, config []byte) error
@@ -251,13 +251,13 @@ func (BackendUnsupported) DeleteBucketTagging(_ context.Context, bucket string)
return s3err.GetAPIError(s3err.ErrNotImplemented)
}
func (BackendUnsupported) GetObjectTagging(_ context.Context, bucket, object string) (map[string]string, error) {
func (BackendUnsupported) GetObjectTagging(_ context.Context, bucket, object, versionId string) (map[string]string, error) {
return nil, s3err.GetAPIError(s3err.ErrNotImplemented)
}
func (BackendUnsupported) PutObjectTagging(_ context.Context, bucket, object string, tags map[string]string) error {
func (BackendUnsupported) PutObjectTagging(_ context.Context, bucket, object, versionId string, tags map[string]string) error {
return s3err.GetAPIError(s3err.ErrNotImplemented)
}
func (BackendUnsupported) DeleteObjectTagging(_ context.Context, bucket, object string) error {
func (BackendUnsupported) DeleteObjectTagging(_ context.Context, bucket, object, versionId string) error {
return s3err.GetAPIError(s3err.ErrNotImplemented)
}

View File

@@ -1313,7 +1313,7 @@ func (p *Posix) CreateMultipartUpload(ctx context.Context, mpu s3response.Create
// set object tagging
if tags != nil {
err := p.PutObjectTagging(ctx, bucket, filepath.Join(objdir, uploadID), tags)
err := p.PutObjectTagging(ctx, bucket, filepath.Join(objdir, uploadID), "", tags)
if err != nil {
// cleanup object if returning error
os.RemoveAll(filepath.Join(tmppath, uploadID))
@@ -3149,7 +3149,7 @@ func (p *Posix) PutObject(ctx context.Context, po s3response.PutObjectInput) (s3
// Set object tagging
if tags != nil {
err := p.PutObjectTagging(ctx, *po.Bucket, *po.Key, tags)
err := p.PutObjectTagging(ctx, *po.Bucket, *po.Key, "", tags)
if errors.Is(err, fs.ErrNotExist) {
return s3response.PutObjectOutput{
ETag: etag,
@@ -3722,7 +3722,7 @@ func (p *Posix) GetObject(_ context.Context, input *s3.GetObjectInput) (*s3.GetO
objMeta := p.loadObjectMetaData(nil, bucket, object, &fid, userMetaData)
var tagCount *int32
tags, err := p.getAttrTags(bucket, object)
tags, err := p.getAttrTags(bucket, object, versionId)
if err != nil && !errors.Is(err, s3err.GetAPIError(s3err.ErrBucketTaggingNotFound)) {
return nil, err
}
@@ -3802,7 +3802,7 @@ func (p *Posix) GetObject(_ context.Context, input *s3.GetObjectInput) (*s3.GetO
objMeta := p.loadObjectMetaData(f, bucket, object, &fi, userMetaData)
var tagCount *int32
tags, err := p.getAttrTags(bucket, object)
tags, err := p.getAttrTags(bucket, object, versionId)
if err != nil && !errors.Is(err, s3err.GetAPIError(s3err.ErrBucketTaggingNotFound)) {
return nil, err
}
@@ -4319,7 +4319,7 @@ func (p *Posix) CopyObject(ctx context.Context, input s3response.CopyObjectInput
return s3response.CopyObjectOutput{}, err
}
err = p.PutObjectTagging(ctx, dstBucket, dstObject, tags)
err = p.PutObjectTagging(ctx, dstBucket, dstObject, "", tags)
if err != nil {
return s3response.CopyObjectOutput{}, err
}
@@ -4742,7 +4742,7 @@ func (p *Posix) GetBucketTagging(_ context.Context, bucket string) (map[string]s
return nil, fmt.Errorf("stat bucket: %w", err)
}
tags, err := p.getAttrTags(bucket, "")
tags, err := p.getAttrTags(bucket, "", "")
if err != nil {
return nil, err
}
@@ -4757,7 +4757,7 @@ func (p *Posix) DeleteBucketTagging(ctx context.Context, bucket string) error {
return p.PutBucketTagging(ctx, bucket, nil)
}
func (p *Posix) GetObjectTagging(_ context.Context, bucket, object string) (map[string]string, error) {
func (p *Posix) GetObjectTagging(_ context.Context, bucket, object, versionId string) (map[string]string, error) {
if !p.isBucketValid(bucket) {
return nil, s3err.GetAPIError(s3err.ErrInvalidBucketName)
}
@@ -4769,13 +4769,35 @@ func (p *Posix) GetObjectTagging(_ context.Context, bucket, object string) (map[
return nil, fmt.Errorf("stat bucket: %w", err)
}
return p.getAttrTags(bucket, object)
if versionId != "" {
if !p.versioningEnabled() {
//TODO: Maybe we need to return our custom error here?
return nil, s3err.GetAPIError(s3err.ErrInvalidVersionId)
}
vId, err := p.meta.RetrieveAttribute(nil, bucket, object, versionIdKey)
if errors.Is(err, fs.ErrNotExist) || errors.Is(err, syscall.ENOTDIR) {
return nil, s3err.GetAPIError(s3err.ErrNoSuchKey)
}
if err != nil && !errors.Is(err, meta.ErrNoSuchKey) {
return nil, fmt.Errorf("get obj versionId: %w", err)
}
if string(vId) != versionId {
bucket = filepath.Join(p.versioningDir, bucket)
object = filepath.Join(genObjVersionKey(object), versionId)
}
}
return p.getAttrTags(bucket, object, versionId)
}
func (p *Posix) getAttrTags(bucket, object string) (map[string]string, error) {
func (p *Posix) getAttrTags(bucket, object, versionId string) (map[string]string, error) {
tags := make(map[string]string)
b, err := p.meta.RetrieveAttribute(nil, bucket, object, tagHdr)
if errors.Is(err, fs.ErrNotExist) || errors.Is(err, syscall.ENOTDIR) {
if versionId != "" {
return nil, s3err.GetAPIError(s3err.ErrNoSuchVersion)
}
return nil, s3err.GetAPIError(s3err.ErrNoSuchKey)
}
if errors.Is(err, meta.ErrNoSuchKey) {
@@ -4793,7 +4815,7 @@ func (p *Posix) getAttrTags(bucket, object string) (map[string]string, error) {
return tags, nil
}
func (p *Posix) PutObjectTagging(_ context.Context, bucket, object string, tags map[string]string) error {
func (p *Posix) PutObjectTagging(_ context.Context, bucket, object, versionId string, tags map[string]string) error {
if !p.isBucketValid(bucket) {
return s3err.GetAPIError(s3err.ErrInvalidBucketName)
}
@@ -4805,9 +4827,31 @@ func (p *Posix) PutObjectTagging(_ context.Context, bucket, object string, tags
return fmt.Errorf("stat bucket: %w", err)
}
if versionId != "" {
if !p.versioningEnabled() {
//TODO: Maybe we need to return our custom error here?
return s3err.GetAPIError(s3err.ErrInvalidVersionId)
}
vId, err := p.meta.RetrieveAttribute(nil, bucket, object, versionIdKey)
if errors.Is(err, fs.ErrNotExist) || errors.Is(err, syscall.ENOTDIR) {
return s3err.GetAPIError(s3err.ErrNoSuchKey)
}
if err != nil && !errors.Is(err, meta.ErrNoSuchKey) {
return fmt.Errorf("get obj versionId: %w", err)
}
if string(vId) != versionId {
bucket = filepath.Join(p.versioningDir, bucket)
object = filepath.Join(genObjVersionKey(object), versionId)
}
}
if tags == nil {
err = p.meta.DeleteAttribute(bucket, object, tagHdr)
if errors.Is(err, fs.ErrNotExist) || errors.Is(err, syscall.ENOTDIR) {
if versionId != "" {
return s3err.GetAPIError(s3err.ErrNoSuchVersion)
}
return s3err.GetAPIError(s3err.ErrNoSuchKey)
}
if errors.Is(err, meta.ErrNoSuchKey) {
@@ -4826,6 +4870,9 @@ func (p *Posix) PutObjectTagging(_ context.Context, bucket, object string, tags
err = p.meta.StoreAttribute(nil, bucket, object, tagHdr, b)
if errors.Is(err, fs.ErrNotExist) || errors.Is(err, syscall.ENOTDIR) {
if versionId != "" {
return s3err.GetAPIError(s3err.ErrNoSuchVersion)
}
return s3err.GetAPIError(s3err.ErrNoSuchKey)
}
if err != nil {
@@ -4835,11 +4882,11 @@ func (p *Posix) PutObjectTagging(_ context.Context, bucket, object string, tags
return nil
}
func (p *Posix) DeleteObjectTagging(ctx context.Context, bucket, object string) error {
func (p *Posix) DeleteObjectTagging(ctx context.Context, bucket, object, versionId string) error {
if !p.isBucketValid(bucket) {
return s3err.GetAPIError(s3err.ErrInvalidBucketName)
}
return p.PutObjectTagging(ctx, bucket, object, nil)
return p.PutObjectTagging(ctx, bucket, object, versionId, nil)
}
func (p *Posix) PutBucketPolicy(ctx context.Context, bucket string, policy []byte) error {

View File

@@ -1445,7 +1445,7 @@ func (s *S3Proxy) PutBucketAcl(ctx context.Context, bucket string, data []byte)
return handleError(s.putMetaBucketObj(ctx, bucket, data, metaPrefixAcl))
}
func (s *S3Proxy) PutObjectTagging(ctx context.Context, bucket, object string, tags map[string]string) error {
func (s *S3Proxy) PutObjectTagging(ctx context.Context, bucket, object, versionId string, tags map[string]string) error {
if bucket == s.metaBucket {
return s3err.GetAPIError(s3err.ErrAccessDenied)
}
@@ -1460,20 +1460,22 @@ func (s *S3Proxy) PutObjectTagging(ctx context.Context, bucket, object string, t
}
_, err := s.client.PutObjectTagging(ctx, &s3.PutObjectTaggingInput{
Bucket: &bucket,
Key: &object,
Tagging: tagging,
Bucket: &bucket,
Key: &object,
VersionId: &versionId,
Tagging: tagging,
})
return handleError(err)
}
func (s *S3Proxy) GetObjectTagging(ctx context.Context, bucket, object string) (map[string]string, error) {
func (s *S3Proxy) GetObjectTagging(ctx context.Context, bucket, object, versionId string) (map[string]string, error) {
if bucket == s.metaBucket {
return nil, s3err.GetAPIError(s3err.ErrAccessDenied)
}
output, err := s.client.GetObjectTagging(ctx, &s3.GetObjectTaggingInput{
Bucket: &bucket,
Key: &object,
Bucket: &bucket,
Key: &object,
VersionId: &versionId,
})
if err != nil {
return nil, handleError(err)
@@ -1487,13 +1489,14 @@ func (s *S3Proxy) GetObjectTagging(ctx context.Context, bucket, object string) (
return tags, nil
}
func (s *S3Proxy) DeleteObjectTagging(ctx context.Context, bucket, object string) error {
func (s *S3Proxy) DeleteObjectTagging(ctx context.Context, bucket, object, versionId string) error {
if bucket == s.metaBucket {
return s3err.GetAPIError(s3err.ErrAccessDenied)
}
_, err := s.client.DeleteObjectTagging(ctx, &s3.DeleteObjectTaggingInput{
Bucket: &bucket,
Key: &object,
Bucket: &bucket,
Key: &object,
VersionId: &versionId,
})
return handleError(err)
}

View File

@@ -59,7 +59,7 @@ var _ backend.Backend = &BackendMock{}
// DeleteObjectFunc: func(contextMoqParam context.Context, deleteObjectInput *s3.DeleteObjectInput) (*s3.DeleteObjectOutput, error) {
// panic("mock out the DeleteObject method")
// },
// DeleteObjectTaggingFunc: func(contextMoqParam context.Context, bucket string, object string) error {
// DeleteObjectTaggingFunc: func(contextMoqParam context.Context, bucket string, object string, versionId string) error {
// panic("mock out the DeleteObjectTagging method")
// },
// DeleteObjectsFunc: func(contextMoqParam context.Context, deleteObjectsInput *s3.DeleteObjectsInput) (s3response.DeleteResult, error) {
@@ -101,7 +101,7 @@ var _ backend.Backend = &BackendMock{}
// GetObjectRetentionFunc: func(contextMoqParam context.Context, bucket string, object string, versionId string) ([]byte, error) {
// panic("mock out the GetObjectRetention method")
// },
// GetObjectTaggingFunc: func(contextMoqParam context.Context, bucket string, object string) (map[string]string, error) {
// GetObjectTaggingFunc: func(contextMoqParam context.Context, bucket string, object string, versionId string) (map[string]string, error) {
// panic("mock out the GetObjectTagging method")
// },
// HeadBucketFunc: func(contextMoqParam context.Context, headBucketInput *s3.HeadBucketInput) (*s3.HeadBucketOutput, error) {
@@ -164,7 +164,7 @@ var _ backend.Backend = &BackendMock{}
// PutObjectRetentionFunc: func(contextMoqParam context.Context, bucket string, object string, versionId string, retention []byte) error {
// panic("mock out the PutObjectRetention method")
// },
// PutObjectTaggingFunc: func(contextMoqParam context.Context, bucket string, object string, tags map[string]string) error {
// PutObjectTaggingFunc: func(contextMoqParam context.Context, bucket string, object string, versionId string, tags map[string]string) error {
// panic("mock out the PutObjectTagging method")
// },
// RestoreObjectFunc: func(contextMoqParam context.Context, restoreObjectInput *s3.RestoreObjectInput) error {
@@ -229,7 +229,7 @@ type BackendMock struct {
DeleteObjectFunc func(contextMoqParam context.Context, deleteObjectInput *s3.DeleteObjectInput) (*s3.DeleteObjectOutput, error)
// DeleteObjectTaggingFunc mocks the DeleteObjectTagging method.
DeleteObjectTaggingFunc func(contextMoqParam context.Context, bucket string, object string) error
DeleteObjectTaggingFunc func(contextMoqParam context.Context, bucket string, object string, versionId string) error
// DeleteObjectsFunc mocks the DeleteObjects method.
DeleteObjectsFunc func(contextMoqParam context.Context, deleteObjectsInput *s3.DeleteObjectsInput) (s3response.DeleteResult, error)
@@ -271,7 +271,7 @@ type BackendMock struct {
GetObjectRetentionFunc func(contextMoqParam context.Context, bucket string, object string, versionId string) ([]byte, error)
// GetObjectTaggingFunc mocks the GetObjectTagging method.
GetObjectTaggingFunc func(contextMoqParam context.Context, bucket string, object string) (map[string]string, error)
GetObjectTaggingFunc func(contextMoqParam context.Context, bucket string, object string, versionId string) (map[string]string, error)
// HeadBucketFunc mocks the HeadBucket method.
HeadBucketFunc func(contextMoqParam context.Context, headBucketInput *s3.HeadBucketInput) (*s3.HeadBucketOutput, error)
@@ -334,7 +334,7 @@ type BackendMock struct {
PutObjectRetentionFunc func(contextMoqParam context.Context, bucket string, object string, versionId string, retention []byte) error
// PutObjectTaggingFunc mocks the PutObjectTagging method.
PutObjectTaggingFunc func(contextMoqParam context.Context, bucket string, object string, tags map[string]string) error
PutObjectTaggingFunc func(contextMoqParam context.Context, bucket string, object string, versionId string, tags map[string]string) error
// RestoreObjectFunc mocks the RestoreObject method.
RestoreObjectFunc func(contextMoqParam context.Context, restoreObjectInput *s3.RestoreObjectInput) error
@@ -452,6 +452,8 @@ type BackendMock struct {
Bucket string
// Object is the object argument value.
Object string
// VersionId is the versionId argument value.
VersionId string
}
// DeleteObjects holds details about calls to the DeleteObjects method.
DeleteObjects []struct {
@@ -560,6 +562,8 @@ type BackendMock struct {
Bucket string
// Object is the object argument value.
Object string
// VersionId is the versionId argument value.
VersionId string
}
// HeadBucket holds details about calls to the HeadBucket method.
HeadBucket []struct {
@@ -733,6 +737,8 @@ type BackendMock struct {
Bucket string
// Object is the object argument value.
Object string
// VersionId is the versionId argument value.
VersionId string
// Tags is the tags argument value.
Tags map[string]string
}
@@ -1268,7 +1274,7 @@ func (mock *BackendMock) DeleteObjectCalls() []struct {
}
// DeleteObjectTagging calls DeleteObjectTaggingFunc.
func (mock *BackendMock) DeleteObjectTagging(contextMoqParam context.Context, bucket string, object string) error {
func (mock *BackendMock) DeleteObjectTagging(contextMoqParam context.Context, bucket string, object string, versionId string) error {
if mock.DeleteObjectTaggingFunc == nil {
panic("BackendMock.DeleteObjectTaggingFunc: method is nil but Backend.DeleteObjectTagging was just called")
}
@@ -1276,15 +1282,17 @@ func (mock *BackendMock) DeleteObjectTagging(contextMoqParam context.Context, bu
ContextMoqParam context.Context
Bucket string
Object string
VersionId string
}{
ContextMoqParam: contextMoqParam,
Bucket: bucket,
Object: object,
VersionId: versionId,
}
mock.lockDeleteObjectTagging.Lock()
mock.calls.DeleteObjectTagging = append(mock.calls.DeleteObjectTagging, callInfo)
mock.lockDeleteObjectTagging.Unlock()
return mock.DeleteObjectTaggingFunc(contextMoqParam, bucket, object)
return mock.DeleteObjectTaggingFunc(contextMoqParam, bucket, object, versionId)
}
// DeleteObjectTaggingCalls gets all the calls that were made to DeleteObjectTagging.
@@ -1295,11 +1303,13 @@ func (mock *BackendMock) DeleteObjectTaggingCalls() []struct {
ContextMoqParam context.Context
Bucket string
Object string
VersionId string
} {
var calls []struct {
ContextMoqParam context.Context
Bucket string
Object string
VersionId string
}
mock.lockDeleteObjectTagging.RLock()
calls = mock.calls.DeleteObjectTagging
@@ -1792,7 +1802,7 @@ func (mock *BackendMock) GetObjectRetentionCalls() []struct {
}
// GetObjectTagging calls GetObjectTaggingFunc.
func (mock *BackendMock) GetObjectTagging(contextMoqParam context.Context, bucket string, object string) (map[string]string, error) {
func (mock *BackendMock) GetObjectTagging(contextMoqParam context.Context, bucket string, object string, versionId string) (map[string]string, error) {
if mock.GetObjectTaggingFunc == nil {
panic("BackendMock.GetObjectTaggingFunc: method is nil but Backend.GetObjectTagging was just called")
}
@@ -1800,15 +1810,17 @@ func (mock *BackendMock) GetObjectTagging(contextMoqParam context.Context, bucke
ContextMoqParam context.Context
Bucket string
Object string
VersionId string
}{
ContextMoqParam: contextMoqParam,
Bucket: bucket,
Object: object,
VersionId: versionId,
}
mock.lockGetObjectTagging.Lock()
mock.calls.GetObjectTagging = append(mock.calls.GetObjectTagging, callInfo)
mock.lockGetObjectTagging.Unlock()
return mock.GetObjectTaggingFunc(contextMoqParam, bucket, object)
return mock.GetObjectTaggingFunc(contextMoqParam, bucket, object, versionId)
}
// GetObjectTaggingCalls gets all the calls that were made to GetObjectTagging.
@@ -1819,11 +1831,13 @@ func (mock *BackendMock) GetObjectTaggingCalls() []struct {
ContextMoqParam context.Context
Bucket string
Object string
VersionId string
} {
var calls []struct {
ContextMoqParam context.Context
Bucket string
Object string
VersionId string
}
mock.lockGetObjectTagging.RLock()
calls = mock.calls.GetObjectTagging
@@ -2600,7 +2614,7 @@ func (mock *BackendMock) PutObjectRetentionCalls() []struct {
}
// PutObjectTagging calls PutObjectTaggingFunc.
func (mock *BackendMock) PutObjectTagging(contextMoqParam context.Context, bucket string, object string, tags map[string]string) error {
func (mock *BackendMock) PutObjectTagging(contextMoqParam context.Context, bucket string, object string, versionId string, tags map[string]string) error {
if mock.PutObjectTaggingFunc == nil {
panic("BackendMock.PutObjectTaggingFunc: method is nil but Backend.PutObjectTagging was just called")
}
@@ -2608,17 +2622,19 @@ func (mock *BackendMock) PutObjectTagging(contextMoqParam context.Context, bucke
ContextMoqParam context.Context
Bucket string
Object string
VersionId string
Tags map[string]string
}{
ContextMoqParam: contextMoqParam,
Bucket: bucket,
Object: object,
VersionId: versionId,
Tags: tags,
}
mock.lockPutObjectTagging.Lock()
mock.calls.PutObjectTagging = append(mock.calls.PutObjectTagging, callInfo)
mock.lockPutObjectTagging.Unlock()
return mock.PutObjectTaggingFunc(contextMoqParam, bucket, object, tags)
return mock.PutObjectTaggingFunc(contextMoqParam, bucket, object, versionId, tags)
}
// PutObjectTaggingCalls gets all the calls that were made to PutObjectTagging.
@@ -2629,12 +2645,14 @@ func (mock *BackendMock) PutObjectTaggingCalls() []struct {
ContextMoqParam context.Context
Bucket string
Object string
VersionId string
Tags map[string]string
} {
var calls []struct {
ContextMoqParam context.Context
Bucket string
Object string
VersionId string
Tags map[string]string
}
mock.lockPutObjectTagging.RLock()

View File

@@ -30,6 +30,7 @@ import (
func (c S3ApiController) DeleteObjectTagging(ctx *fiber.Ctx) (*Response, error) {
bucket := ctx.Params("bucket")
key := strings.TrimPrefix(ctx.Path(), fmt.Sprintf("/%s/", bucket))
versionId := ctx.Query("versionId")
acct := utils.ContextKeyAccount.Get(ctx).(auth.Account)
isRoot := utils.ContextKeyIsRoot.Get(ctx).(bool)
isBucketPublic := utils.ContextKeyPublicBucket.IsSet(ctx)
@@ -55,7 +56,7 @@ func (c S3ApiController) DeleteObjectTagging(ctx *fiber.Ctx) (*Response, error)
}, err
}
err = c.be.DeleteObjectTagging(ctx.Context(), bucket, key)
err = c.be.DeleteObjectTagging(ctx.Context(), bucket, key, versionId)
return &Response{
MetaOpts: &MetaOptions{
Status: http.StatusNoContent,

View File

@@ -81,7 +81,7 @@ func TestS3ApiController_DeleteObjectTagging(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
be := &BackendMock{
DeleteObjectTaggingFunc: func(contextMoqParam context.Context, bucket, object string) error {
DeleteObjectTaggingFunc: func(contextMoqParam context.Context, bucket, object, versionId string) error {
return tt.input.beErr
},
GetBucketPolicyFunc: func(contextMoqParam context.Context, bucket string) ([]byte, error) {

View File

@@ -35,6 +35,7 @@ import (
func (c S3ApiController) GetObjectTagging(ctx *fiber.Ctx) (*Response, error) {
bucket := ctx.Params("bucket")
key := strings.TrimPrefix(ctx.Path(), fmt.Sprintf("/%s/", bucket))
versionId := ctx.Query("versionId")
acct := utils.ContextKeyAccount.Get(ctx).(auth.Account)
isRoot := utils.ContextKeyIsRoot.Get(ctx).(bool)
parsedAcl := utils.ContextKeyParsedAcl.Get(ctx).(auth.ACL)
@@ -59,7 +60,7 @@ func (c S3ApiController) GetObjectTagging(ctx *fiber.Ctx) (*Response, error) {
}, err
}
data, err := c.be.GetObjectTagging(ctx.Context(), bucket, key)
data, err := c.be.GetObjectTagging(ctx.Context(), bucket, key, versionId)
if err != nil {
return &Response{
MetaOpts: &MetaOptions{

View File

@@ -95,7 +95,7 @@ func TestS3ApiController_GetObjectTagging(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
be := &BackendMock{
GetObjectTaggingFunc: func(contextMoqParam context.Context, bucket, object string) (map[string]string, error) {
GetObjectTaggingFunc: func(contextMoqParam context.Context, bucket, object, versionId string) (map[string]string, error) {
return tt.input.beRes.(map[string]string), tt.input.beErr
},
GetBucketPolicyFunc: func(contextMoqParam context.Context, bucket string) ([]byte, error) {

View File

@@ -36,6 +36,7 @@ import (
func (c S3ApiController) PutObjectTagging(ctx *fiber.Ctx) (*Response, error) {
bucket := ctx.Params("bucket")
key := strings.TrimPrefix(ctx.Path(), fmt.Sprintf("/%s/", bucket))
versionId := ctx.Query("versionId")
acct := utils.ContextKeyAccount.Get(ctx).(auth.Account)
isRoot := utils.ContextKeyIsRoot.Get(ctx).(bool)
IsBucketPublic := utils.ContextKeyPublicBucket.IsSet(ctx)
@@ -69,7 +70,7 @@ func (c S3ApiController) PutObjectTagging(ctx *fiber.Ctx) (*Response, error) {
}, err
}
err = c.be.PutObjectTagging(ctx.Context(), bucket, key, tagging)
err = c.be.PutObjectTagging(ctx.Context(), bucket, key, versionId, tagging)
return &Response{
MetaOpts: &MetaOptions{
BucketOwner: parsedAcl.Owner,

View File

@@ -115,7 +115,7 @@ func TestS3ApiController_PutObjectTagging(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
be := &BackendMock{
PutObjectTaggingFunc: func(contextMoqParam context.Context, bucket, object string, tags map[string]string) error {
PutObjectTaggingFunc: func(contextMoqParam context.Context, bucket, object, versionId string, tags map[string]string) error {
return tt.input.beErr
},
GetBucketPolicyFunc: func(contextMoqParam context.Context, bucket string) ([]byte, error) {

View File

@@ -1004,6 +1004,11 @@ func TestVersioning(ts *TestState) {
ts.Run(Versioning_GetObject_delete_marker_without_versionId)
ts.Run(Versioning_GetObject_delete_marker)
ts.Run(Versioning_GetObject_null_versionId_obj)
// object tagging actions
ts.Run(Versioning_PutObjectTagging_non_existing_object_version)
ts.Run(Versioning_GetObjectTagging_non_existing_object_version)
ts.Run(Versioning_DeleteObjectTagging_non_existing_object_version)
ts.Run(Versioning_PutGetDeleteObjectTagging_success)
// GetObjectAttributes action
ts.Run(Versioning_GetObjectAttributes_object_version)
ts.Run(Versioning_GetObjectAttributes_delete_marker)
@@ -1636,6 +1641,10 @@ func GetIntTests() IntTests {
"Versioning_GetObject_delete_marker_without_versionId": Versioning_GetObject_delete_marker_without_versionId,
"Versioning_GetObject_delete_marker": Versioning_GetObject_delete_marker,
"Versioning_GetObject_null_versionId_obj": Versioning_GetObject_null_versionId_obj,
"Versioning_PutObjectTagging_non_existing_object_version": Versioning_PutObjectTagging_non_existing_object_version,
"Versioning_GetObjectTagging_non_existing_object_version": Versioning_GetObjectTagging_non_existing_object_version,
"Versioning_DeleteObjectTagging_non_existing_object_version": Versioning_DeleteObjectTagging_non_existing_object_version,
"Versioning_PutGetDeleteObjectTagging_success": Versioning_PutGetDeleteObjectTagging_success,
"Versioning_GetObjectAttributes_object_version": Versioning_GetObjectAttributes_object_version,
"Versioning_GetObjectAttributes_delete_marker": Versioning_GetObjectAttributes_delete_marker,
"Versioning_DeleteObject_delete_object_version": Versioning_DeleteObject_delete_object_version,

View File

@@ -2682,3 +2682,139 @@ func Versioning_concurrent_upload_object(s *S3Conf) error {
return nil
}, withVersioning(types.BucketVersioningStatusEnabled))
}
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_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_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")},
},
}
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
_, err = s3client.PutObjectTagging(ctx, &s3.PutObjectTaggingInput{
Bucket: &bucket,
Key: &obj,
Tagging: &tagging,
VersionId: versionId,
})
cancel()
if 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)
}
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
_, err = s3client.DeleteObjectTagging(ctx, &s3.DeleteObjectTaggingInput{
Bucket: &bucket,
Key: &obj,
VersionId: versionId,
})
cancel()
if err != nil {
return err
}
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
_, err = s3client.GetObjectTagging(ctx, &s3.GetObjectTaggingInput{
Bucket: &bucket,
Key: &obj,
VersionId: versionId,
})
cancel()
return checkApiErr(err, s3err.GetAPIError(s3err.ErrBucketTaggingNotFound))
}, withVersioning(types.BucketVersioningStatusEnabled))
}