Upstream versity/versitygw state of the touched files before 070b030237

This commit is contained in:
upstream committed 2026-10-02 16:40:10 +02:00
1 parent 08d5acf963
commit 79e4cf7bca
2 files changed
+723 -51

No files matched your search

+723
View File
@@ -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": &region,
},
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": &region,
},
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 := &region
if region == "us-east-1" {
value = nil
}
return &Response{
Data: s3response.LocationConstraint{
Value: value,
},
MetaOpts: &MetaOptions{
BucketOwner: parsedAcl.Owner,
},
}, nil
}
-51
View File
@@ -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 {