From 8550dba36f7dd4577af59583940440975dc47f2f Mon Sep 17 00:00:00 2001 From: niksis02 Date: Mon, 2 Mar 2026 18:56:50 +0400 Subject: [PATCH] feat: add tagging support for directory objects in posix Closes #1857 Adds object Tagging support for directory objects in `PutObject` posix. Updates the integration tests to test object metadata and tagging both for file and directory objects. --- backend/posix/posix.go | 9 ++++ tests/integration/PutObject.go | 76 +++++++++++++++++++++------------- 2 files changed, 56 insertions(+), 29 deletions(-) diff --git a/backend/posix/posix.go b/backend/posix/posix.go index 862c8712..30c044ae 100644 --- a/backend/posix/posix.go +++ b/backend/posix/posix.go @@ -3227,6 +3227,7 @@ func (p *Posix) PutObjectWithPostFunc(ctx context.Context, po s3response.PutObje return s3response.PutObjectOutput{}, err } + // set object metadata for k, v := range po.Metadata { err := p.meta.StoreAttribute(nil, *po.Bucket, *po.Key, fmt.Sprintf("%v.%v", metaHdr, k), []byte(v)) @@ -3235,6 +3236,14 @@ func (p *Posix) PutObjectWithPostFunc(ctx context.Context, po s3response.PutObje } } + // Set object tagging + if tags != nil { + err := p.PutObjectTagging(withCtxNoSlot(ctx), *po.Bucket, *po.Key, "", tags) + if err != nil { + return s3response.PutObjectOutput{}, err + } + } + // set etag attribute to signify this dir was specifically put err = p.meta.StoreAttribute(nil, *po.Bucket, *po.Key, etagkey, []byte(emptyMD5)) diff --git a/tests/integration/PutObject.go b/tests/integration/PutObject.go index 6b848f0a..86e5d14c 100644 --- a/tests/integration/PutObject.go +++ b/tests/integration/PutObject.go @@ -80,13 +80,12 @@ func PutObject_special_chars(s *S3Conf) error { func PutObject_tagging(s *S3Conf) error { testName := "PutObject_tagging" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { - obj := "my-obj" - testTagging := func(taggging string, result map[string]string, expectedErr error) error { + testTagging := func(object, taggging string, result map[string]string, expectedErr error) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.PutObject(ctx, &s3.PutObjectInput{ Bucket: &bucket, - Key: &obj, + Key: &object, Tagging: &taggging, }) cancel() @@ -108,7 +107,7 @@ func PutObject_tagging(s *S3Conf) error { ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) res, err := s3client.GetObjectTagging(ctx, &s3.GetObjectTaggingInput{ Bucket: &bucket, - Key: &obj, + Key: &object, }) cancel() if err != nil { @@ -133,6 +132,8 @@ func PutObject_tagging(s *S3Conf) error { return nil } + fileObj, dirObj := "file-object", "dir-object/" + for i, el := range []struct { tagging string result map[string]string @@ -182,9 +183,16 @@ func PutObject_tagging(s *S3Conf) error { continue } } - err := testTagging(el.tagging, el.result, el.expectedErr) + // once test for file object + err := testTagging(fileObj, el.tagging, el.result, el.expectedErr) if err != nil { - return fmt.Errorf("test case %v failed: %w", i+1, err) + return fmt.Errorf("test case %v failed for file object: %w", i+1, err) + } + + // the test for directory object + err = testTagging(dirObj, el.tagging, el.result, el.expectedErr) + if err != nil { + return fmt.Errorf("test case %v failed for directory object: %w", i+1, err) } } return nil @@ -461,7 +469,8 @@ func PutObject_conditional_writes(s *S3Conf) error { func PutObject_with_metadata(s *S3Conf) error { testName := "PutObject_with_metadata" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { - meta := map[string]string{ + + inputMeta := map[string]string{ "Key": "Val", "X-Test": "Example", "UPPERCASE": "should-remain", @@ -477,26 +486,6 @@ func PutObject_with_metadata(s *S3Conf) error { "WhitespaceKey ": " trailing-key", } - obj := "my-object" - _, err := putObjectWithData(3, &s3.PutObjectInput{ - Bucket: &bucket, - Key: &obj, - Metadata: meta, - }, s3client) - if err != nil { - return err - } - - ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) - res, err := s3client.HeadObject(ctx, &s3.HeadObjectInput{ - Bucket: &bucket, - Key: &obj, - }) - cancel() - if err != nil { - return err - } - expectedMeta := map[string]string{ "key": "Val", "x-test": "Example", @@ -513,8 +502,37 @@ func PutObject_with_metadata(s *S3Conf) error { "whitespacekey": "trailing-key", } - if !areMapsSame(expectedMeta, res.Metadata) { - return fmt.Errorf("expected the object metadata to be %v, instead got %v", expectedMeta, res.Metadata) + for i, test := range []struct { + obj string + dataLength int64 + }{ + // test for file object + {"file-object", 100}, + // test for directory object + {"dir-object/", 0}, + } { + _, err := putObjectWithData(test.dataLength, &s3.PutObjectInput{ + Bucket: &bucket, + Key: &test.obj, + Metadata: inputMeta, + }, s3client) + if err != nil { + return fmt.Errorf("test %v failed: %w", i+1, err) + } + + ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) + res, err := s3client.HeadObject(ctx, &s3.HeadObjectInput{ + Bucket: &bucket, + Key: &test.obj, + }) + cancel() + if err != nil { + return fmt.Errorf("test %v failed: %w", i+1, err) + } + + if !areMapsSame(expectedMeta, res.Metadata) { + return fmt.Errorf("test %v failed: expected the object metadata to be %v, instead got %v", i+1, expectedMeta, res.Metadata) + } } return nil