Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CopyObject metadata #265

Merged
merged 5 commits into from
Sep 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions backend/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,9 @@ type Backend interface {
SelectObjectContent(context.Context, *s3.SelectObjectContentInput) (s3response.SelectObjectContentResult, error)

// object tags operations
GetTags(_ context.Context, bucket, object string) (map[string]string, error)
SetTags(_ context.Context, bucket, object string, tags map[string]string) error
RemoveTags(_ context.Context, bucket, object string) error
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

// non AWS actions
ChangeBucketOwner(_ context.Context, bucket, newOwner string) error
Expand Down Expand Up @@ -166,13 +166,13 @@ func (BackendUnsupported) SelectObjectContent(context.Context, *s3.SelectObjectC
return s3response.SelectObjectContentResult{}, s3err.GetAPIError(s3err.ErrNotImplemented)
}

func (BackendUnsupported) GetTags(_ context.Context, bucket, object string) (map[string]string, error) {
func (BackendUnsupported) GetObjectTagging(_ context.Context, bucket, object string) (map[string]string, error) {
return nil, s3err.GetAPIError(s3err.ErrNotImplemented)
}
func (BackendUnsupported) SetTags(_ context.Context, bucket, object string, tags map[string]string) error {
func (BackendUnsupported) PutObjectTagging(_ context.Context, bucket, object string, tags map[string]string) error {
return s3err.GetAPIError(s3err.ErrNotImplemented)
}
func (BackendUnsupported) RemoveTags(_ context.Context, bucket, object string) error {
func (BackendUnsupported) DeleteObjectTagging(_ context.Context, bucket, object string) error {
return s3err.GetAPIError(s3err.ErrNotImplemented)
}

Expand Down
49 changes: 38 additions & 11 deletions backend/posix/posix.go
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,20 @@ func loadUserMetaData(path string, m map[string]string) (contentType, contentEnc
return
}

func compareUserMetadata(meta1, meta2 map[string]string) bool {
if len(meta1) != len(meta2) {
return false
}

for key, val := range meta1 {
if meta2[key] != val {
return false
}
}

return true
}

func isValidMeta(val string) bool {
if strings.HasPrefix(val, "user."+metaHdr) {
return true
Expand Down Expand Up @@ -1006,7 +1020,7 @@ func (p *Posix) PutObject(ctx context.Context, po *s3.PutObjectInput) (string, e
}

if tagsStr != "" {
err := p.SetTags(ctx, *po.Bucket, *po.Key, tags)
err := p.PutObjectTagging(ctx, *po.Bucket, *po.Key, tags)
if err != nil {
return "", err
}
Expand Down Expand Up @@ -1243,10 +1257,6 @@ func (p *Posix) CopyObject(ctx context.Context, input *s3.CopyObjectInput) (*s3.
dstObject := *input.Key
owner := *input.ExpectedBucketOwner

if fmt.Sprintf("%v/%v", srcBucket, srcObject) == fmt.Sprintf("%v/%v", dstBucket, dstObject) {
return &s3.CopyObjectOutput{}, s3err.GetAPIError(s3err.ErrInvalidCopyDest)
}

_, err := os.Stat(srcBucket)
if errors.Is(err, fs.ErrNotExist) {
return nil, s3err.GetAPIError(s3err.ErrNoSuchBucket)
Expand Down Expand Up @@ -1294,12 +1304,29 @@ func (p *Posix) CopyObject(ctx context.Context, input *s3.CopyObjectInput) (*s3.
return nil, fmt.Errorf("stat object: %w", err)
}

etag, err := p.PutObject(ctx, &s3.PutObjectInput{Bucket: &dstBucket, Key: &dstObject, Body: f, ContentLength: fInfo.Size()})
meta := make(map[string]string)
loadUserMetaData(objPath, meta)

dstObjdPath := filepath.Join(dstBucket, dstObject)
if dstObjdPath == objPath {
if compareUserMetadata(meta, input.Metadata) {
return &s3.CopyObjectOutput{}, s3err.GetAPIError(s3err.ErrInvalidCopyDest)
} else {
for key := range meta {
xattr.Remove(dstObjdPath, key)
}
for k, v := range input.Metadata {
xattr.Set(dstObjdPath, fmt.Sprintf("user.%v.%v", metaHdr, k), []byte(v))
}
}
}

benmcclelland marked this conversation as resolved.
Show resolved Hide resolved
etag, err := p.PutObject(ctx, &s3.PutObjectInput{Bucket: &dstBucket, Key: &dstObject, Body: f, ContentLength: fInfo.Size(), Metadata: meta})
if err != nil {
return nil, err
}

fi, err := os.Stat(filepath.Join(dstBucket, dstObject))
fi, err := os.Stat(dstObjdPath)
if err != nil {
return nil, fmt.Errorf("stat dst object: %w", err)
}
Expand Down Expand Up @@ -1479,7 +1506,7 @@ func (p *Posix) GetBucketAcl(_ context.Context, input *s3.GetBucketAclInput) ([]
return b, nil
}

func (p *Posix) GetTags(_ context.Context, bucket, object string) (map[string]string, error) {
func (p *Posix) GetObjectTagging(_ context.Context, bucket, object string) (map[string]string, error) {
_, err := os.Stat(bucket)
if errors.Is(err, fs.ErrNotExist) {
return nil, s3err.GetAPIError(s3err.ErrNoSuchBucket)
Expand Down Expand Up @@ -1512,7 +1539,7 @@ func (p *Posix) getXattrTags(bucket, object string) (map[string]string, error) {
return tags, nil
}

func (p *Posix) SetTags(_ context.Context, bucket, object string, tags map[string]string) error {
func (p *Posix) PutObjectTagging(_ context.Context, bucket, object string, tags map[string]string) error {
_, err := os.Stat(bucket)
if errors.Is(err, fs.ErrNotExist) {
return s3err.GetAPIError(s3err.ErrNoSuchBucket)
Expand Down Expand Up @@ -1548,8 +1575,8 @@ func (p *Posix) SetTags(_ context.Context, bucket, object string, tags map[strin
return nil
}

func (p *Posix) RemoveTags(ctx context.Context, bucket, object string) error {
return p.SetTags(ctx, bucket, object, nil)
func (p *Posix) DeleteObjectTagging(ctx context.Context, bucket, object string) error {
return p.PutObjectTagging(ctx, bucket, object, nil)
}

func (p *Posix) ChangeBucketOwner(ctx context.Context, bucket, newOwner string) error {
Expand Down
5 changes: 4 additions & 1 deletion backend/walk.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,11 @@ func Walk(fileSystem fs.FS, prefix, delimiter, marker string, max int32, getObj
if !pastMarker {
if path == marker {
pastMarker = true
return nil
}
if path < marker {
return nil
}
return nil
}

// If object doesn't have prefix, don't include in results.
Expand Down
3 changes: 3 additions & 0 deletions integration/action-tests.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ func TestListObjects(s *S3Conf) {
ListObjects_invalid_max_keys(s)
ListObjects_max_keys_0(s)
ListObjects_delimiter(s)
ListObjects_max_keys_none(s)
ListObjects_marker_not_from_obj_list(s)
}

func TestDeleteObject(s *S3Conf) {
Expand All @@ -91,6 +93,7 @@ func TestCopyObject(s *S3Conf) {
CopyObject_non_existing_dst_bucket(s)
CopyObject_not_owned_source_bucket(s)
CopyObject_copy_to_itself(s)
CopyObject_to_itself_with_new_metadata(s)
CopyObject_success(s)
}

Expand Down
81 changes: 81 additions & 0 deletions integration/tests.go
Original file line number Diff line number Diff line change
Expand Up @@ -1509,6 +1509,61 @@ func ListObjects_delimiter(s *S3Conf) {
})
}

func ListObjects_max_keys_none(s *S3Conf) {
testName := "ListObjects_max_keys_none"
actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
err := putObjects(s3client, []string{"foo", "bar", "baz"}, bucket)
if err != nil {
return err
}

ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
out, err := s3client.ListObjects(ctx, &s3.ListObjectsInput{
Bucket: &bucket,
})
cancel()
if err != nil {
return err
}

if out.MaxKeys != 1000 {
return fmt.Errorf("expected max-keys to be 1000, instead got %v", out.MaxKeys)
}

return nil
})
}

func ListObjects_marker_not_from_obj_list(s *S3Conf) {
testName := "ListObjects_marker_not_from_obj_list"
actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
err := putObjects(s3client, []string{"foo", "bar", "baz", "qux", "hello", "xyz"}, bucket)
if err != nil {
return err
}

ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
out, err := s3client.ListObjects(ctx, &s3.ListObjectsInput{
Bucket: &bucket,
Marker: getPtr("ceil"),
})
cancel()
if err != nil {
return err
}

for _, el := range out.Contents {
fmt.Println(*el.Key)
}

if !compareObjects([]string{"foo", "qux", "hello", "xyz"}, out.Contents) {
return fmt.Errorf("expected output to be %v, instead got %v", []string{"foo", "qux", "hello", "xyz"}, out.Contents)
}

return nil
})
}

func DeleteObject_non_existing_object(s *S3Conf) {
testName := "DeleteObject_non_existing_object"
actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
Expand Down Expand Up @@ -1783,6 +1838,32 @@ func CopyObject_copy_to_itself(s *S3Conf) {
})
}

func CopyObject_to_itself_with_new_metadata(s *S3Conf) {
testName := "CopyObject_to_itself_with_new_metadata"
actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
obj := "my-obj"
err := putObjects(s3client, []string{obj}, bucket)
if err != nil {
return err
}
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
_, err = s3client.CopyObject(ctx, &s3.CopyObjectInput{
Bucket: &bucket,
Key: &obj,
CopySource: getPtr(fmt.Sprintf("%v/%v", bucket, obj)),
Metadata: map[string]string{
"Hello": "World",
},
})
cancel()
if err != nil {
return err
}

return nil
})
}

func CopyObject_success(s *S3Conf) {
testName := "CopyObject_success"
actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
Expand Down
Loading