From 79e4cf7bca643fef4b689b2451ec3cff11ac385a Mon Sep 17 00:00:00 2001 From: upstream Date: Fri, 2 Oct 2026 16:40:10 +0200 Subject: [PATCH] Upstream versity/versitygw state of the touched files before 070b030237 --- s3api/controllers/bucket-get.go | 723 ++++++++++++++++++++++ tests/integration/ListMultipartUploads.go | 51 -- 2 files changed, 723 insertions(+), 51 deletions(-) create mode 100644 s3api/controllers/bucket-get.go diff --git a/s3api/controllers/bucket-get.go b/s3api/controllers/bucket-get.go new file mode 100644 index 0000000..0399c9a --- /dev/null +++ b/s3api/controllers/bucket-get.go @@ -0,0 +1,723 @@ +// 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 controllers + +import ( + "strings" + + "github.com/aws/aws-sdk-go-v2/service/s3" + "github.com/aws/aws-sdk-go-v2/service/s3/types" + "github.com/gofiber/fiber/v3" + "github.com/versity/versitygw/auth" + "github.com/versity/versitygw/s3api/utils" + "github.com/versity/versitygw/s3response" +) + +func (c S3ApiController) GetBucketTagging(ctx fiber.Ctx) (*Response, error) { + bucket := ctx.Params("bucket") + acct := utils.ContextKeyAccount.Get(ctx).(auth.Account) + isRoot := utils.ContextKeyIsRoot.Get(ctx).(bool) + isPublicBucket := utils.ContextKeyPublicBucket.IsSet(ctx) + parsedAcl := utils.ContextKeyParsedAcl.Get(ctx).(auth.ACL) + + err := c.verifyAccess(ctx, auth.AccessOptions{ + Acl: parsedAcl, + AclPermission: auth.PermissionRead, + IsRoot: isRoot, + Acc: acct, + Bucket: bucket, + Actions: []auth.Action{auth.GetBucketTaggingAction}, + IsPublicRequest: isPublicBucket, + }) + if err != nil { + return &Response{ + MetaOpts: &MetaOptions{ + BucketOwner: parsedAcl.Owner, + }, + }, err + } + + tags, err := c.be.GetBucketTagging(ctx.RequestCtx(), bucket) + if err != nil { + return &Response{ + MetaOpts: &MetaOptions{ + BucketOwner: parsedAcl.Owner, + }, + }, err + } + resp := s3response.Tagging{ + TagSet: s3response.TagSet{ + Tags: make([]s3response.Tag, 0, len(tags)), + }, + } + + for key, val := range tags { + resp.TagSet.Tags = append(resp.TagSet.Tags, + s3response.Tag{Key: key, Value: val}) + } + + return &Response{ + Data: resp, + MetaOpts: &MetaOptions{ + BucketOwner: parsedAcl.Owner, + }, + }, err +} + +func (c S3ApiController) GetBucketOwnershipControls(ctx fiber.Ctx) (*Response, error) { + bucket := ctx.Params("bucket") + acct := utils.ContextKeyAccount.Get(ctx).(auth.Account) + isRoot := utils.ContextKeyIsRoot.Get(ctx).(bool) + isPublicBucket := utils.ContextKeyPublicBucket.IsSet(ctx) + parsedAcl := utils.ContextKeyParsedAcl.Get(ctx).(auth.ACL) + + err := c.verifyAccess(ctx, auth.AccessOptions{ + Acl: parsedAcl, + AclPermission: auth.PermissionRead, + IsRoot: isRoot, + Acc: acct, + Bucket: bucket, + Actions: []auth.Action{auth.GetBucketOwnershipControlsAction}, + IsPublicRequest: isPublicBucket, + }) + if err != nil { + return &Response{ + MetaOpts: &MetaOptions{ + BucketOwner: parsedAcl.Owner, + }, + }, err + } + + data, err := c.be.GetBucketOwnershipControls(ctx.RequestCtx(), bucket) + return &Response{ + Data: s3response.OwnershipControls{ + Rules: []types.OwnershipControlsRule{ + { + ObjectOwnership: data, + }, + }, + }, + MetaOpts: &MetaOptions{ + BucketOwner: parsedAcl.Owner, + }, + }, err +} + +func (c S3ApiController) GetBucketVersioning(ctx fiber.Ctx) (*Response, error) { + bucket := ctx.Params("bucket") + acct := utils.ContextKeyAccount.Get(ctx).(auth.Account) + isRoot := utils.ContextKeyIsRoot.Get(ctx).(bool) + isPublicBucket := utils.ContextKeyPublicBucket.IsSet(ctx) + parsedAcl := utils.ContextKeyParsedAcl.Get(ctx).(auth.ACL) + + err := c.verifyAccess(ctx, auth.AccessOptions{ + Acl: parsedAcl, + AclPermission: auth.PermissionRead, + IsRoot: isRoot, + Acc: acct, + Bucket: bucket, + Actions: []auth.Action{auth.GetBucketVersioningAction}, + IsPublicRequest: isPublicBucket, + }) + if err != nil { + return &Response{ + MetaOpts: &MetaOptions{ + BucketOwner: parsedAcl.Owner, + }, + }, err + } + + data, err := c.be.GetBucketVersioning(ctx.RequestCtx(), bucket) + return &Response{ + Data: data, + MetaOpts: &MetaOptions{ + BucketOwner: parsedAcl.Owner, + }, + }, err +} + +func (c S3ApiController) GetBucketCors(ctx fiber.Ctx) (*Response, error) { + bucket := ctx.Params("bucket") + acct := utils.ContextKeyAccount.Get(ctx).(auth.Account) + isRoot := utils.ContextKeyIsRoot.Get(ctx).(bool) + isPublicBucket := utils.ContextKeyPublicBucket.IsSet(ctx) + parsedAcl := utils.ContextKeyParsedAcl.Get(ctx).(auth.ACL) + + err := c.verifyAccess(ctx, auth.AccessOptions{ + Acl: parsedAcl, + AclPermission: auth.PermissionRead, + IsRoot: isRoot, + Acc: acct, + Bucket: bucket, + Actions: []auth.Action{auth.GetBucketCorsAction}, + IsPublicRequest: isPublicBucket, + }) + if err != nil { + return &Response{ + MetaOpts: &MetaOptions{ + BucketOwner: parsedAcl.Owner, + }, + }, err + } + + data, err := c.be.GetBucketCors(ctx.RequestCtx(), bucket) + if err != nil { + return &Response{ + MetaOpts: &MetaOptions{ + BucketOwner: parsedAcl.Owner, + }, + }, err + } + + output, err := auth.ParseCORSOutput(data) + return &Response{ + Data: output, + MetaOpts: &MetaOptions{ + BucketOwner: parsedAcl.Owner, + }, + }, err +} + +func (c S3ApiController) GetBucketWebsite(ctx fiber.Ctx) (*Response, error) { + bucket := ctx.Params("bucket") + acct := utils.ContextKeyAccount.Get(ctx).(auth.Account) + isRoot := utils.ContextKeyIsRoot.Get(ctx).(bool) + isPublicBucket := utils.ContextKeyPublicBucket.IsSet(ctx) + parsedAcl := utils.ContextKeyParsedAcl.Get(ctx).(auth.ACL) + + err := c.verifyAccess(ctx, auth.AccessOptions{ + Acl: parsedAcl, + AclPermission: auth.PermissionRead, + IsRoot: isRoot, + Acc: acct, + Bucket: bucket, + Actions: []auth.Action{auth.GetBucketWebsiteAction}, + IsPublicRequest: isPublicBucket, + }) + if err != nil { + return &Response{ + MetaOpts: &MetaOptions{ + BucketOwner: parsedAcl.Owner, + }, + }, err + } + + data, err := c.be.GetBucketWebsite(ctx.RequestCtx(), bucket) + if err != nil { + return &Response{ + MetaOpts: &MetaOptions{ + BucketOwner: parsedAcl.Owner, + }, + }, err + } + + output, err := s3response.ParseWebsiteConfigOutput(data) + return &Response{ + Data: output, + MetaOpts: &MetaOptions{ + BucketOwner: parsedAcl.Owner, + }, + }, err +} + +func (c S3ApiController) GetBucketPolicy(ctx fiber.Ctx) (*Response, error) { + bucket := ctx.Params("bucket") + acct := utils.ContextKeyAccount.Get(ctx).(auth.Account) + isRoot := utils.ContextKeyIsRoot.Get(ctx).(bool) + isPublicBucket := utils.ContextKeyPublicBucket.IsSet(ctx) + parsedAcl := utils.ContextKeyParsedAcl.Get(ctx).(auth.ACL) + + err := c.verifyAccess(ctx, auth.AccessOptions{ + Acl: parsedAcl, + AclPermission: auth.PermissionRead, + IsRoot: isRoot, + Acc: acct, + Bucket: bucket, + Actions: []auth.Action{auth.GetBucketPolicyAction}, + IsPublicRequest: isPublicBucket, + }) + if err != nil { + return &Response{ + MetaOpts: &MetaOptions{ + BucketOwner: parsedAcl.Owner, + }, + }, err + } + + data, err := c.be.GetBucketPolicy(ctx.RequestCtx(), bucket) + return &Response{ + Data: data, + MetaOpts: &MetaOptions{ + BucketOwner: parsedAcl.Owner, + }, + }, err +} + +func (c S3ApiController) GetBucketPolicyStatus(ctx fiber.Ctx) (*Response, error) { + bucket := ctx.Params("bucket") + acct := utils.ContextKeyAccount.Get(ctx).(auth.Account) + isRoot := utils.ContextKeyIsRoot.Get(ctx).(bool) + isPublicBucket := utils.ContextKeyPublicBucket.IsSet(ctx) + parsedAcl := utils.ContextKeyParsedAcl.Get(ctx).(auth.ACL) + + err := c.verifyAccess(ctx, auth.AccessOptions{ + Acl: parsedAcl, + AclPermission: auth.PermissionRead, + IsRoot: isRoot, + Acc: acct, + Bucket: bucket, + Actions: []auth.Action{auth.GetBucketPolicyStatusAction}, + IsPublicRequest: isPublicBucket, + }) + if err != nil { + return &Response{ + MetaOpts: &MetaOptions{ + BucketOwner: parsedAcl.Owner, + }, + }, err + } + + policyRaw, err := c.be.GetBucketPolicy(ctx.RequestCtx(), bucket) + if err != nil { + return &Response{ + MetaOpts: &MetaOptions{ + BucketOwner: parsedAcl.Owner, + }, + }, err + } + + policy, err := auth.ParsePolicyDocument(policyRaw) + if err != nil { + return &Response{ + MetaOpts: &MetaOptions{ + BucketOwner: parsedAcl.Owner, + }, + }, err + } + isPublic := policy.IsPublic() + + return &Response{ + Data: types.PolicyStatus{ + IsPublic: &isPublic, + }, + MetaOpts: &MetaOptions{ + BucketOwner: parsedAcl.Owner, + }, + }, nil +} + +func (c S3ApiController) ListObjectVersions(ctx fiber.Ctx) (*Response, error) { + // url values + bucket := ctx.Params("bucket") + prefix := ctx.Query("prefix") + delimiter := ctx.Query("delimiter") + maxkeysStr := ctx.Query("max-keys") + keyMarker := ctx.Query("key-marker") + versionIdMarker := ctx.Query("version-id-marker") + // context keys + acct := utils.ContextKeyAccount.Get(ctx).(auth.Account) + isRoot := utils.ContextKeyIsRoot.Get(ctx).(bool) + isPublicBucket := utils.ContextKeyPublicBucket.IsSet(ctx) + parsedAcl := utils.ContextKeyParsedAcl.Get(ctx).(auth.ACL) + + err := c.verifyAccess(ctx, auth.AccessOptions{ + Acl: parsedAcl, + AclPermission: auth.PermissionRead, + IsRoot: isRoot, + Acc: acct, + Bucket: bucket, + Actions: []auth.Action{auth.ListBucketVersionsAction}, + IsPublicRequest: isPublicBucket, + }) + if err != nil { + return &Response{ + MetaOpts: &MetaOptions{ + BucketOwner: parsedAcl.Owner, + }, + }, err + } + + maxkeys, err := utils.ParseMaxLimiter(maxkeysStr, utils.LimiterTypeVersionsMaxKeys) + if err != nil { + return &Response{ + MetaOpts: &MetaOptions{ + BucketOwner: parsedAcl.Owner, + }, + }, err + } + + data, err := c.be.ListObjectVersions(ctx.RequestCtx(), + &s3.ListObjectVersionsInput{ + Bucket: &bucket, + Delimiter: &delimiter, + KeyMarker: &keyMarker, + MaxKeys: &maxkeys, + Prefix: &prefix, + VersionIdMarker: &versionIdMarker, + }) + return &Response{ + Data: data, + MetaOpts: &MetaOptions{ + BucketOwner: parsedAcl.Owner, + }, + }, err +} + +func (c S3ApiController) GetObjectLockConfiguration(ctx fiber.Ctx) (*Response, error) { + // url values + bucket := ctx.Params("bucket") + // context keys + acct := utils.ContextKeyAccount.Get(ctx).(auth.Account) + isRoot := utils.ContextKeyIsRoot.Get(ctx).(bool) + isPublicBucket := utils.ContextKeyPublicBucket.IsSet(ctx) + parsedAcl := utils.ContextKeyParsedAcl.Get(ctx).(auth.ACL) + + err := c.verifyAccess(ctx, auth.AccessOptions{ + Acl: parsedAcl, + AclPermission: auth.PermissionRead, + IsRoot: isRoot, + Acc: acct, + Bucket: bucket, + Actions: []auth.Action{auth.GetBucketObjectLockConfigurationAction}, + IsPublicRequest: isPublicBucket, + }) + if err != nil { + return &Response{ + MetaOpts: &MetaOptions{ + BucketOwner: parsedAcl.Owner, + }, + }, err + } + + data, err := c.be.GetObjectLockConfiguration(ctx.RequestCtx(), bucket) + if err != nil { + return &Response{ + MetaOpts: &MetaOptions{ + BucketOwner: parsedAcl.Owner, + }, + }, err + } + + resp, err := auth.ParseBucketLockConfigurationOutput(data) + return &Response{ + Data: resp, + MetaOpts: &MetaOptions{ + BucketOwner: parsedAcl.Owner, + }, + }, err +} + +func (c S3ApiController) GetBucketAcl(ctx fiber.Ctx) (*Response, error) { + // url values + bucket := ctx.Params("bucket") + // context keys + acct := utils.ContextKeyAccount.Get(ctx).(auth.Account) + isRoot := utils.ContextKeyIsRoot.Get(ctx).(bool) + isPublicBucket := utils.ContextKeyPublicBucket.IsSet(ctx) + parsedAcl := utils.ContextKeyParsedAcl.Get(ctx).(auth.ACL) + + err := c.verifyAccess(ctx, auth.AccessOptions{ + Acl: parsedAcl, + AclPermission: auth.PermissionReadAcp, + IsRoot: isRoot, + Acc: acct, + Bucket: bucket, + Actions: []auth.Action{auth.GetBucketAclAction}, + IsPublicRequest: isPublicBucket, + }) + if err != nil { + return &Response{ + MetaOpts: &MetaOptions{ + BucketOwner: parsedAcl.Owner, + }, + }, err + } + + data, err := c.be.GetBucketAcl(ctx.RequestCtx(), + &s3.GetBucketAclInput{Bucket: &bucket}) + if err != nil { + return &Response{ + MetaOpts: &MetaOptions{ + BucketOwner: parsedAcl.Owner, + }, + }, err + } + + res, err := auth.ParseACLOutput(data, parsedAcl.Owner) + return &Response{ + Data: res, + MetaOpts: &MetaOptions{ + BucketOwner: parsedAcl.Owner, + }, + }, err +} + +func (c S3ApiController) ListMultipartUploads(ctx fiber.Ctx) (*Response, error) { + // url values + bucket := ctx.Params("bucket") + prefix := ctx.Query("prefix") + delimiter := ctx.Query("delimiter") + keyMarker := ctx.Query("key-marker") + maxUploadsStr := ctx.Query("max-uploads") + uploadIdMarker := ctx.Query("upload-id-marker") + // context keys + acct := utils.ContextKeyAccount.Get(ctx).(auth.Account) + isRoot := utils.ContextKeyIsRoot.Get(ctx).(bool) + isPublicBucket := utils.ContextKeyPublicBucket.IsSet(ctx) + parsedAcl := utils.ContextKeyParsedAcl.Get(ctx).(auth.ACL) + + err := c.verifyAccess(ctx, auth.AccessOptions{ + Acl: parsedAcl, + AclPermission: auth.PermissionRead, + IsRoot: isRoot, + Acc: acct, + Bucket: bucket, + Actions: []auth.Action{auth.ListBucketMultipartUploadsAction}, + IsPublicRequest: isPublicBucket, + }) + if err != nil { + return &Response{ + MetaOpts: &MetaOptions{ + BucketOwner: parsedAcl.Owner, + }, + }, err + } + maxUploads, err := utils.ParseMaxLimiter(maxUploadsStr, utils.LimiterTypeMaxUploads) + if err != nil { + return &Response{ + MetaOpts: &MetaOptions{ + BucketOwner: parsedAcl.Owner, + }, + }, err + } + res, err := c.be.ListMultipartUploads(ctx.RequestCtx(), + &s3.ListMultipartUploadsInput{ + Bucket: &bucket, + Delimiter: &delimiter, + Prefix: &prefix, + UploadIdMarker: &uploadIdMarker, + MaxUploads: &maxUploads, + KeyMarker: &keyMarker, + }) + return &Response{ + Data: res, + MetaOpts: &MetaOptions{ + BucketOwner: parsedAcl.Owner, + }, + }, err +} + +func (c S3ApiController) ListObjectsV2(ctx fiber.Ctx) (*Response, error) { + // url values + bucket := ctx.Params("bucket") + prefix := ctx.Query("prefix") + cToken := ctx.Query("continuation-token") + sAfter := ctx.Query("start-after") + delimiter := ctx.Query("delimiter") + maxkeysStr := ctx.Query("max-keys") + fetchOwner := strings.EqualFold(ctx.Query("fetch-owner"), "true") + // context locals + acct := utils.ContextKeyAccount.Get(ctx).(auth.Account) + isRoot := utils.ContextKeyIsRoot.Get(ctx).(bool) + isPublicBucket := utils.ContextKeyPublicBucket.IsSet(ctx) + parsedAcl := utils.ContextKeyParsedAcl.Get(ctx).(auth.ACL) + region, ok := utils.ContextKeyRegion.Get(ctx).(string) + if !ok { + region = defaultRegion + } + + err := c.verifyAccess(ctx, auth.AccessOptions{ + Acl: parsedAcl, + AclPermission: auth.PermissionRead, + IsRoot: isRoot, + Acc: acct, + Bucket: bucket, + Actions: []auth.Action{auth.ListBucketAction}, + IsPublicRequest: isPublicBucket, + }) + if err != nil { + return &Response{ + MetaOpts: &MetaOptions{ + BucketOwner: parsedAcl.Owner, + }, + }, err + } + maxkeys, err := utils.ParseMaxLimiter(maxkeysStr, utils.LimiterTypeMaxKeys) + if err != nil { + return &Response{ + MetaOpts: &MetaOptions{ + BucketOwner: parsedAcl.Owner, + }, + }, err + } + + res, err := c.be.ListObjectsV2(ctx.RequestCtx(), + &s3.ListObjectsV2Input{ + Bucket: &bucket, + Prefix: &prefix, + ContinuationToken: &cToken, + Delimiter: &delimiter, + MaxKeys: &maxkeys, + StartAfter: &sAfter, + FetchOwner: &fetchOwner, + }) + if err != nil { + return &Response{ + MetaOpts: &MetaOptions{ + BucketOwner: parsedAcl.Owner, + }, + }, err + } + + return &Response{ + Headers: map[string]*string{ + "x-amz-bucket-region": ®ion, + }, + Data: res, + MetaOpts: &MetaOptions{ + BucketOwner: parsedAcl.Owner, + }, + }, nil +} + +func (c S3ApiController) ListObjects(ctx fiber.Ctx) (*Response, error) { + // url values + bucket := ctx.Params("bucket") + prefix := ctx.Query("prefix") + marker := ctx.Query("marker") + delimiter := ctx.Query("delimiter") + maxkeysStr := ctx.Query("max-keys") + // context locals + acct := utils.ContextKeyAccount.Get(ctx).(auth.Account) + isRoot := utils.ContextKeyIsRoot.Get(ctx).(bool) + isPublicBucket := utils.ContextKeyPublicBucket.IsSet(ctx) + parsedAcl := utils.ContextKeyParsedAcl.Get(ctx).(auth.ACL) + region, ok := utils.ContextKeyRegion.Get(ctx).(string) + if !ok { + region = defaultRegion + } + + err := c.verifyAccess(ctx, auth.AccessOptions{ + Acl: parsedAcl, + AclPermission: auth.PermissionRead, + IsRoot: isRoot, + Acc: acct, + Bucket: bucket, + Actions: []auth.Action{auth.ListBucketAction}, + IsPublicRequest: isPublicBucket, + }) + if err != nil { + return &Response{ + MetaOpts: &MetaOptions{ + BucketOwner: parsedAcl.Owner, + }, + }, err + } + + maxkeys, err := utils.ParseMaxLimiter(maxkeysStr, utils.LimiterTypeMaxKeys) + if err != nil { + return &Response{ + MetaOpts: &MetaOptions{ + BucketOwner: parsedAcl.Owner, + }, + }, err + } + + res, err := c.be.ListObjects(ctx.RequestCtx(), + &s3.ListObjectsInput{ + Bucket: &bucket, + Prefix: &prefix, + Marker: &marker, + Delimiter: &delimiter, + MaxKeys: &maxkeys, + }) + if err != nil { + return &Response{ + MetaOpts: &MetaOptions{ + BucketOwner: parsedAcl.Owner, + }, + }, err + } + + // S3 echoes the request marker back in the response, and includes the + // element even when the request carried no marker. Backends return a nil + // pointer for an empty marker, which encoding/xml drops entirely, so + // restore it here for every backend. + if res.Marker == nil { + res.Marker = &marker + } + + return &Response{ + Headers: map[string]*string{ + "x-amz-bucket-region": ®ion, + }, + Data: res, + MetaOpts: &MetaOptions{ + BucketOwner: parsedAcl.Owner, + }, + }, nil +} + +// GetBucketLocation handles GET /:bucket?location +func (c S3ApiController) GetBucketLocation(ctx fiber.Ctx) (*Response, error) { + bucket := ctx.Params("bucket") + acct := utils.ContextKeyAccount.Get(ctx).(auth.Account) + isRoot := utils.ContextKeyIsRoot.Get(ctx).(bool) + isPublicBucket := utils.ContextKeyPublicBucket.IsSet(ctx) + parsedAcl := utils.ContextKeyParsedAcl.Get(ctx).(auth.ACL) + + err := c.verifyAccess(ctx, auth.AccessOptions{ + Acl: parsedAcl, + AclPermission: auth.PermissionRead, + IsRoot: isRoot, + Acc: acct, + Bucket: bucket, + Actions: []auth.Action{auth.GetBucketLocationAction}, + IsPublicRequest: isPublicBucket, + }) + if err != nil { + return &Response{ + MetaOpts: &MetaOptions{ + BucketOwner: parsedAcl.Owner, + }, + }, err + } + + // verify bucket existence/access via backend HeadBucket + _, err = c.be.HeadBucket(ctx.RequestCtx(), &s3.HeadBucketInput{Bucket: &bucket}) + if err != nil { + return &Response{ + MetaOpts: &MetaOptions{ + BucketOwner: parsedAcl.Owner, + }, + }, err + } + + // pick up configured region from locals (set by router middleware) + region, _ := ctx.Locals("region").(string) + value := ®ion + if region == "us-east-1" { + value = nil + } + + return &Response{ + Data: s3response.LocationConstraint{ + Value: value, + }, + MetaOpts: &MetaOptions{ + BucketOwner: parsedAcl.Owner, + }, + }, nil +} diff --git a/tests/integration/ListMultipartUploads.go b/tests/integration/ListMultipartUploads.go index 7ec8863..f163875 100644 --- a/tests/integration/ListMultipartUploads.go +++ b/tests/integration/ListMultipartUploads.go @@ -152,57 +152,6 @@ func ListMultipartUploads_max_uploads(s *S3Conf) error { }) } -func ListMultipartUploads_next_markers_not_truncated(s *S3Conf) error { - testName := "ListMultipartUploads_next_markers_not_truncated" - return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { - uploads := []types.MultipartUpload{} - for _, key := range []string{"bar", "baz", "foo"} { - out, err := createMp(s3client, bucket, key) - if err != nil { - return err - } - uploads = append(uploads, types.MultipartUpload{ - UploadId: out.UploadId, - Key: out.Key, - StorageClass: types.StorageClassStandard, - }) - } - last := uploads[len(uploads)-1] - - // a single page still reports the last upload in the next markers, - // with the default max-uploads and with one equal to the upload count - for _, maxUploads := range []*int32{nil, getPtr(int32(len(uploads)))} { - ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) - out, err := s3client.ListMultipartUploads(ctx, &s3.ListMultipartUploadsInput{ - Bucket: &bucket, - MaxUploads: maxUploads, - }) - cancel() - if err != nil { - return err - } - - if out.IsTruncated == nil || *out.IsTruncated { - return fmt.Errorf("expected a non-truncated response") - } - if ok := compareMultipartUploads(out.Uploads, uploads); !ok { - return fmt.Errorf("expected multipart uploads to be %v, instead got %v", - uploads, out.Uploads) - } - if getString(out.NextKeyMarker) != getString(last.Key) { - return fmt.Errorf("expected next-key-marker to be %v, instead got %v", - getString(last.Key), getString(out.NextKeyMarker)) - } - if getString(out.NextUploadIdMarker) != getString(last.UploadId) { - return fmt.Errorf("expected next-upload-id-marker to be %v, instead got %v", - getString(last.UploadId), getString(out.NextUploadIdMarker)) - } - } - - return nil - }) -} - func ListMultipartUploads_exceeding_max_uploads(s *S3Conf) error { testName := "ListMultipartUploads_exceeding_max_uploads" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {