mirror of
https://github.com/versity/versitygw.git
synced 2026-04-20 20:50:28 +00:00
Merge pull request #585 from versity/fix/567-create-mp-missing-props
CreateMultipartUpload missing properties support
This commit is contained in:
@@ -303,7 +303,7 @@ func (p *Posix) DeleteBucket(_ context.Context, input *s3.DeleteBucketInput) err
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Posix) CreateMultipartUpload(_ context.Context, mpu *s3.CreateMultipartUploadInput) (*s3.CreateMultipartUploadOutput, error) {
|
||||
func (p *Posix) CreateMultipartUpload(ctx context.Context, mpu *s3.CreateMultipartUploadInput) (*s3.CreateMultipartUploadOutput, error) {
|
||||
if mpu.Bucket == nil {
|
||||
return nil, s3err.GetAPIError(s3err.ErrInvalidBucketName)
|
||||
}
|
||||
@@ -328,6 +328,23 @@ func (p *Posix) CreateMultipartUpload(_ context.Context, mpu *s3.CreateMultipart
|
||||
return nil, s3err.GetAPIError(s3err.ErrDirectoryObjectContainsData)
|
||||
}
|
||||
|
||||
// parse object tags
|
||||
tagsStr := getString(mpu.Tagging)
|
||||
tags := make(map[string]string)
|
||||
if tagsStr != "" {
|
||||
tagParts := strings.Split(tagsStr, "&")
|
||||
for _, prt := range tagParts {
|
||||
p := strings.Split(prt, "=")
|
||||
if len(p) != 2 {
|
||||
return nil, s3err.GetAPIError(s3err.ErrInvalidTag)
|
||||
}
|
||||
if len(p[0]) > 128 || len(p[1]) > 256 {
|
||||
return nil, s3err.GetAPIError(s3err.ErrInvalidTag)
|
||||
}
|
||||
tags[p[0]] = p[1]
|
||||
}
|
||||
}
|
||||
|
||||
// generate random uuid for upload id
|
||||
uploadID := uuid.New().String()
|
||||
// hash object name for multipart container
|
||||
@@ -355,10 +372,10 @@ func (p *Posix) CreateMultipartUpload(_ context.Context, mpu *s3.CreateMultipart
|
||||
return nil, fmt.Errorf("set name attr for upload: %w", err)
|
||||
}
|
||||
|
||||
// set user attrs
|
||||
// set user metadata
|
||||
for k, v := range mpu.Metadata {
|
||||
err := p.meta.StoreAttribute(bucket, filepath.Join(objdir, uploadID),
|
||||
k, []byte(v))
|
||||
fmt.Sprintf("%v.%v", metaHdr, k), []byte(v))
|
||||
if err != nil {
|
||||
// cleanup object if returning error
|
||||
os.RemoveAll(filepath.Join(tmppath, uploadID))
|
||||
@@ -367,6 +384,60 @@ func (p *Posix) CreateMultipartUpload(_ context.Context, mpu *s3.CreateMultipart
|
||||
}
|
||||
}
|
||||
|
||||
// set object tagging
|
||||
if tagsStr != "" {
|
||||
err := p.PutObjectTagging(ctx, bucket, filepath.Join(objdir, uploadID), tags)
|
||||
if err != nil {
|
||||
// cleanup object if returning error
|
||||
os.RemoveAll(filepath.Join(tmppath, uploadID))
|
||||
os.Remove(tmppath)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// set content-type
|
||||
if *mpu.ContentType != "" {
|
||||
err := p.meta.StoreAttribute(bucket, filepath.Join(objdir, uploadID),
|
||||
contentTypeHdr, []byte(*mpu.ContentType))
|
||||
if err != nil {
|
||||
// cleanup object if returning error
|
||||
os.RemoveAll(filepath.Join(tmppath, uploadID))
|
||||
os.Remove(tmppath)
|
||||
return nil, fmt.Errorf("set content-type: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// set object legal hold
|
||||
if mpu.ObjectLockLegalHoldStatus == types.ObjectLockLegalHoldStatusOn {
|
||||
if err := p.PutObjectLegalHold(ctx, bucket, filepath.Join(objdir, uploadID), "", true); err != nil {
|
||||
// cleanup object if returning error
|
||||
os.RemoveAll(filepath.Join(tmppath, uploadID))
|
||||
os.Remove(tmppath)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Set object retention
|
||||
if mpu.ObjectLockMode != "" {
|
||||
retention := types.ObjectLockRetention{
|
||||
Mode: types.ObjectLockRetentionMode(mpu.ObjectLockMode),
|
||||
RetainUntilDate: mpu.ObjectLockRetainUntilDate,
|
||||
}
|
||||
retParsed, err := json.Marshal(retention)
|
||||
if err != nil {
|
||||
// cleanup object if returning error
|
||||
os.RemoveAll(filepath.Join(tmppath, uploadID))
|
||||
os.Remove(tmppath)
|
||||
return nil, fmt.Errorf("parse object lock retention: %w", err)
|
||||
}
|
||||
if err := p.PutObjectRetention(ctx, bucket, filepath.Join(objdir, uploadID), "", retParsed); err != nil {
|
||||
// cleanup object if returning error
|
||||
os.RemoveAll(filepath.Join(tmppath, uploadID))
|
||||
os.Remove(tmppath)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &s3.CreateMultipartUploadOutput{
|
||||
Bucket: &bucket,
|
||||
Key: &object,
|
||||
@@ -492,7 +563,7 @@ func (p *Posix) CompleteMultipartUpload(ctx context.Context, input *s3.CompleteM
|
||||
|
||||
userMetaData := make(map[string]string)
|
||||
upiddir := filepath.Join(objdir, uploadID)
|
||||
p.loadUserMetaData(bucket, objdir, userMetaData)
|
||||
cType, _ := p.loadUserMetaData(bucket, upiddir, userMetaData)
|
||||
|
||||
objname := filepath.Join(bucket, object)
|
||||
dir := filepath.Dir(objname)
|
||||
@@ -509,7 +580,7 @@ func (p *Posix) CompleteMultipartUpload(ctx context.Context, input *s3.CompleteM
|
||||
}
|
||||
|
||||
for k, v := range userMetaData {
|
||||
err = p.meta.StoreAttribute(bucket, object, k, []byte(v))
|
||||
err = p.meta.StoreAttribute(bucket, object, fmt.Sprintf("%v.%v", metaHdr, k), []byte(v))
|
||||
if err != nil {
|
||||
// cleanup object if returning error
|
||||
os.Remove(objname)
|
||||
@@ -517,6 +588,54 @@ func (p *Posix) CompleteMultipartUpload(ctx context.Context, input *s3.CompleteM
|
||||
}
|
||||
}
|
||||
|
||||
// load and set tagging
|
||||
tagging, err := p.meta.RetrieveAttribute(bucket, upiddir, tagHdr)
|
||||
if err == nil {
|
||||
if err := p.meta.StoreAttribute(bucket, object, tagHdr, tagging); err != nil {
|
||||
// cleanup object
|
||||
os.Remove(objname)
|
||||
return nil, fmt.Errorf("set object tagging: %w", err)
|
||||
}
|
||||
}
|
||||
if err != nil && !errors.Is(err, meta.ErrNoSuchKey) {
|
||||
return nil, fmt.Errorf("get object tagging: %w", err)
|
||||
}
|
||||
|
||||
// set content-type
|
||||
if cType != "" {
|
||||
if err := p.meta.StoreAttribute(bucket, object, contentTypeHdr, []byte(cType)); err != nil {
|
||||
// cleanup object
|
||||
os.Remove(objname)
|
||||
return nil, fmt.Errorf("set object content type: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// load and set legal hold
|
||||
lHold, err := p.meta.RetrieveAttribute(bucket, upiddir, objectLegalHoldKey)
|
||||
if err == nil {
|
||||
if err := p.meta.StoreAttribute(bucket, object, objectLegalHoldKey, lHold); err != nil {
|
||||
// cleanup object
|
||||
os.Remove(objname)
|
||||
return nil, fmt.Errorf("set object legal hold: %w", err)
|
||||
}
|
||||
}
|
||||
if err != nil && !errors.Is(err, meta.ErrNoSuchKey) {
|
||||
return nil, fmt.Errorf("get object legal hold: %w", err)
|
||||
}
|
||||
|
||||
// load and set retention
|
||||
ret, err := p.meta.RetrieveAttribute(bucket, upiddir, objectRetentionKey)
|
||||
if err == nil {
|
||||
if err := p.meta.StoreAttribute(bucket, object, objectRetentionKey, ret); err != nil {
|
||||
// cleanup object
|
||||
os.Remove(objname)
|
||||
return nil, fmt.Errorf("set object retention: %w", err)
|
||||
}
|
||||
}
|
||||
if err != nil && !errors.Is(err, meta.ErrNoSuchKey) {
|
||||
return nil, fmt.Errorf("get object retention: %w", err)
|
||||
}
|
||||
|
||||
// Calculate s3 compatible md5sum for complete multipart.
|
||||
s3MD5 := backend.GetMultipartMD5(parts)
|
||||
|
||||
|
||||
@@ -1821,45 +1821,9 @@ func (c S3ApiController) PutActions(ctx *fiber.Ctx) error {
|
||||
})
|
||||
}
|
||||
|
||||
legalHoldHdr := ctx.Get("X-Amz-Object-Lock-Legal-Hold")
|
||||
objLockModeHdr := ctx.Get("X-Amz-Object-Lock-Mode")
|
||||
objLockDate := ctx.Get("X-Amz-Object-Lock-Retain-Until-Date")
|
||||
|
||||
if (objLockDate != "" && objLockModeHdr == "") || (objLockDate == "" && objLockModeHdr != "") {
|
||||
return SendResponse(ctx, s3err.GetAPIError(s3err.ErrObjectLockInvalidHeaders),
|
||||
&MetaOpts{
|
||||
Logger: c.logger,
|
||||
Action: "PutObject",
|
||||
BucketOwner: parsedAcl.Owner,
|
||||
})
|
||||
}
|
||||
|
||||
var retainUntilDate *time.Time
|
||||
if objLockDate != "" {
|
||||
rDate, err := time.Parse(time.RFC3339, objLockDate)
|
||||
if err != nil {
|
||||
return SendResponse(ctx, s3err.GetAPIError(s3err.ErrInvalidRequest),
|
||||
&MetaOpts{
|
||||
Logger: c.logger,
|
||||
Action: "PutObject",
|
||||
BucketOwner: parsedAcl.Owner,
|
||||
})
|
||||
}
|
||||
if rDate.Before(time.Now()) {
|
||||
return SendResponse(ctx, s3err.GetAPIError(s3err.ErrPastObjectLockRetainDate),
|
||||
&MetaOpts{
|
||||
Logger: c.logger,
|
||||
Action: "PutObject",
|
||||
BucketOwner: parsedAcl.Owner,
|
||||
})
|
||||
}
|
||||
retainUntilDate = &rDate
|
||||
}
|
||||
|
||||
if objLockModeHdr != "" &&
|
||||
objLockModeHdr != string(types.ObjectLockModeCompliance) &&
|
||||
objLockModeHdr != string(types.ObjectLockModeGovernance) {
|
||||
return SendResponse(ctx, s3err.GetAPIError(s3err.ErrInvalidRequest),
|
||||
objLock, err := utils.ParsObjectLockHdrs(ctx)
|
||||
if err != nil {
|
||||
return SendResponse(ctx, err,
|
||||
&MetaOpts{
|
||||
Logger: c.logger,
|
||||
Action: "PutObject",
|
||||
@@ -1884,9 +1848,9 @@ func (c S3ApiController) PutActions(ctx *fiber.Ctx) error {
|
||||
Metadata: metadata,
|
||||
Body: body,
|
||||
Tagging: &tagging,
|
||||
ObjectLockRetainUntilDate: retainUntilDate,
|
||||
ObjectLockMode: types.ObjectLockMode(objLockModeHdr),
|
||||
ObjectLockLegalHoldStatus: types.ObjectLockLegalHoldStatus(legalHoldHdr),
|
||||
ObjectLockRetainUntilDate: &objLock.RetainUntilDate,
|
||||
ObjectLockMode: objLock.ObjectLockMode,
|
||||
ObjectLockLegalHoldStatus: objLock.LegalHoldStatus,
|
||||
})
|
||||
ctx.Response().Header.Set("ETag", etag)
|
||||
return SendResponse(ctx, err,
|
||||
@@ -2406,6 +2370,8 @@ func (c S3ApiController) CreateActions(ctx *fiber.Ctx) error {
|
||||
acct := ctx.Locals("account").(auth.Account)
|
||||
isRoot := ctx.Locals("isRoot").(bool)
|
||||
parsedAcl := ctx.Locals("parsedAcl").(auth.ACL)
|
||||
contentType := ctx.Get("Content-Type")
|
||||
tagging := ctx.Get("X-Amz-Tagging")
|
||||
|
||||
if keyEnd != "" {
|
||||
key = strings.Join([]string{key, keyEnd}, "/")
|
||||
@@ -2607,10 +2573,28 @@ func (c S3ApiController) CreateActions(ctx *fiber.Ctx) error {
|
||||
})
|
||||
}
|
||||
|
||||
objLockState, err := utils.ParsObjectLockHdrs(ctx)
|
||||
if err != nil {
|
||||
return SendXMLResponse(ctx, nil, err,
|
||||
&MetaOpts{
|
||||
Logger: c.logger,
|
||||
Action: "CreateMultipartUpload",
|
||||
BucketOwner: parsedAcl.Owner,
|
||||
})
|
||||
}
|
||||
|
||||
metadata := utils.GetUserMetaData(&ctx.Request().Header)
|
||||
|
||||
res, err := c.be.CreateMultipartUpload(ctx.Context(),
|
||||
&s3.CreateMultipartUploadInput{
|
||||
Bucket: &bucket,
|
||||
Key: &key,
|
||||
Bucket: &bucket,
|
||||
Key: &key,
|
||||
Tagging: &tagging,
|
||||
ContentType: &contentType,
|
||||
ObjectLockRetainUntilDate: &objLockState.RetainUntilDate,
|
||||
ObjectLockMode: objLockState.ObjectLockMode,
|
||||
ObjectLockLegalHoldStatus: objLockState.LegalHoldStatus,
|
||||
Metadata: metadata,
|
||||
})
|
||||
return SendXMLResponse(ctx, res, err,
|
||||
&MetaOpts{
|
||||
|
||||
@@ -278,3 +278,51 @@ func ParseObjectAttributes(ctx *fiber.Ctx) map[types.ObjectAttributes]struct{} {
|
||||
|
||||
return attrs
|
||||
}
|
||||
|
||||
type objLockCfg struct {
|
||||
RetainUntilDate time.Time
|
||||
ObjectLockMode types.ObjectLockMode
|
||||
LegalHoldStatus types.ObjectLockLegalHoldStatus
|
||||
}
|
||||
|
||||
func ParsObjectLockHdrs(ctx *fiber.Ctx) (*objLockCfg, error) {
|
||||
legalHoldHdr := ctx.Get("X-Amz-Object-Lock-Legal-Hold")
|
||||
objLockModeHdr := ctx.Get("X-Amz-Object-Lock-Mode")
|
||||
objLockDate := ctx.Get("X-Amz-Object-Lock-Retain-Until-Date")
|
||||
|
||||
if (objLockDate != "" && objLockModeHdr == "") || (objLockDate == "" && objLockModeHdr != "") {
|
||||
return nil, s3err.GetAPIError(s3err.ErrObjectLockInvalidHeaders)
|
||||
}
|
||||
|
||||
var retainUntilDate time.Time
|
||||
if objLockDate != "" {
|
||||
rDate, err := time.Parse(time.RFC3339, objLockDate)
|
||||
if err != nil {
|
||||
return nil, s3err.GetAPIError(s3err.ErrInvalidRequest)
|
||||
}
|
||||
if rDate.Before(time.Now()) {
|
||||
return nil, s3err.GetAPIError(s3err.ErrPastObjectLockRetainDate)
|
||||
}
|
||||
retainUntilDate = rDate
|
||||
}
|
||||
|
||||
objLockMode := types.ObjectLockMode(objLockModeHdr)
|
||||
|
||||
if objLockMode != "" &&
|
||||
objLockMode != types.ObjectLockModeCompliance &&
|
||||
objLockMode != types.ObjectLockModeGovernance {
|
||||
return nil, s3err.GetAPIError(s3err.ErrInvalidRequest)
|
||||
}
|
||||
|
||||
legalHold := types.ObjectLockLegalHoldStatus(legalHoldHdr)
|
||||
|
||||
if legalHold != "" && legalHold != types.ObjectLockLegalHoldStatusOff && legalHold != types.ObjectLockLegalHoldStatusOn {
|
||||
return nil, s3err.GetAPIError(s3err.ErrInvalidRequest)
|
||||
}
|
||||
|
||||
return &objLockCfg{
|
||||
RetainUntilDate: retainUntilDate,
|
||||
ObjectLockMode: objLockMode,
|
||||
LegalHoldStatus: legalHold,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -200,6 +200,14 @@ func TestDeleteObjectTagging(s *S3Conf) {
|
||||
|
||||
func TestCreateMultipartUpload(s *S3Conf) {
|
||||
CreateMultipartUpload_non_existing_bucket(s)
|
||||
CreateMultipartUpload_with_metadata(s)
|
||||
CreateMultipartUpload_with_invalid_tagging(s)
|
||||
CreateMultipartUpload_with_tagging(s)
|
||||
CreateMultipartUpload_with_content_type(s)
|
||||
CreateMultipartUpload_with_object_lock(s)
|
||||
CreateMultipartUpload_with_object_lock_not_enabled(s)
|
||||
CreateMultipartUpload_with_object_lock_invalid_retention(s)
|
||||
CreateMultipartUpload_past_retain_until_date(s)
|
||||
CreateMultipartUpload_success(s)
|
||||
}
|
||||
|
||||
@@ -564,6 +572,14 @@ func GetIntTests() IntTests {
|
||||
"DeleteObjectTagging_success_status": DeleteObjectTagging_success_status,
|
||||
"DeleteObjectTagging_success": DeleteObjectTagging_success,
|
||||
"CreateMultipartUpload_non_existing_bucket": CreateMultipartUpload_non_existing_bucket,
|
||||
"CreateMultipartUpload_with_metadata": CreateMultipartUpload_with_metadata,
|
||||
"CreateMultipartUpload_with_invalid_tagging": CreateMultipartUpload_with_invalid_tagging,
|
||||
"CreateMultipartUpload_with_tagging": CreateMultipartUpload_with_tagging,
|
||||
"CreateMultipartUpload_with_content_type": CreateMultipartUpload_with_content_type,
|
||||
"CreateMultipartUpload_with_object_lock": CreateMultipartUpload_with_object_lock,
|
||||
"CreateMultipartUpload_with_object_lock_not_enabled": CreateMultipartUpload_with_object_lock_not_enabled,
|
||||
"CreateMultipartUpload_with_object_lock_invalid_retention": CreateMultipartUpload_with_object_lock_invalid_retention,
|
||||
"CreateMultipartUpload_past_retain_until_date": CreateMultipartUpload_past_retain_until_date,
|
||||
"CreateMultipartUpload_success": CreateMultipartUpload_success,
|
||||
"UploadPart_non_existing_bucket": UploadPart_non_existing_bucket,
|
||||
"UploadPart_invalid_part_number": UploadPart_invalid_part_number,
|
||||
|
||||
@@ -4278,6 +4278,359 @@ func CreateMultipartUpload_non_existing_bucket(s *S3Conf) error {
|
||||
})
|
||||
}
|
||||
|
||||
func CreateMultipartUpload_with_metadata(s *S3Conf) error {
|
||||
testName := "CreateMultipartUpload_with_metadata"
|
||||
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
||||
obj := "my-obj"
|
||||
meta := map[string]string{
|
||||
"prop1": "val1",
|
||||
"prop2": "val2",
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
||||
out, err := s3client.CreateMultipartUpload(ctx, &s3.CreateMultipartUploadInput{
|
||||
Bucket: &bucket,
|
||||
Key: &obj,
|
||||
Metadata: meta,
|
||||
})
|
||||
cancel()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
parts, err := uploadParts(s3client, 100, 1, bucket, obj, *out.UploadId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
compParts := []types.CompletedPart{}
|
||||
for _, el := range parts {
|
||||
compParts = append(compParts, types.CompletedPart{
|
||||
ETag: el.ETag,
|
||||
PartNumber: el.PartNumber,
|
||||
})
|
||||
}
|
||||
|
||||
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
|
||||
_, err = s3client.CompleteMultipartUpload(ctx, &s3.CompleteMultipartUploadInput{
|
||||
Bucket: &bucket,
|
||||
Key: &obj,
|
||||
UploadId: out.UploadId,
|
||||
MultipartUpload: &types.CompletedMultipartUpload{
|
||||
Parts: compParts,
|
||||
},
|
||||
})
|
||||
cancel()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
|
||||
resp, err := s3client.HeadObject(ctx, &s3.HeadObjectInput{
|
||||
Bucket: &bucket,
|
||||
Key: &obj,
|
||||
})
|
||||
cancel()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !areMapsSame(resp.Metadata, meta) {
|
||||
return fmt.Errorf("expected uploaded object metadata to be %v, instead got %v", meta, resp.Metadata)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func CreateMultipartUpload_with_content_type(s *S3Conf) error {
|
||||
testName := "CreateMultipartUpload_with_content_type"
|
||||
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
||||
obj := "my-obj"
|
||||
cType := "application/octet-stream"
|
||||
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
||||
out, err := s3client.CreateMultipartUpload(ctx, &s3.CreateMultipartUploadInput{
|
||||
Bucket: &bucket,
|
||||
Key: &obj,
|
||||
ContentType: &cType,
|
||||
})
|
||||
cancel()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
parts, err := uploadParts(s3client, 100, 1, bucket, obj, *out.UploadId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
compParts := []types.CompletedPart{}
|
||||
for _, el := range parts {
|
||||
compParts = append(compParts, types.CompletedPart{
|
||||
ETag: el.ETag,
|
||||
PartNumber: el.PartNumber,
|
||||
})
|
||||
}
|
||||
|
||||
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
|
||||
_, err = s3client.CompleteMultipartUpload(ctx, &s3.CompleteMultipartUploadInput{
|
||||
Bucket: &bucket,
|
||||
Key: &obj,
|
||||
UploadId: out.UploadId,
|
||||
MultipartUpload: &types.CompletedMultipartUpload{
|
||||
Parts: compParts,
|
||||
},
|
||||
})
|
||||
cancel()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
|
||||
resp, err := s3client.HeadObject(ctx, &s3.HeadObjectInput{
|
||||
Bucket: &bucket,
|
||||
Key: &obj,
|
||||
})
|
||||
cancel()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if *resp.ContentType != cType {
|
||||
return fmt.Errorf("expected uploaded object content-type to be %v, instead got %v", cType, *resp.ContentType)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func CreateMultipartUpload_with_object_lock(s *S3Conf) error {
|
||||
testName := "CreateMultipartUpload_with_object_lock"
|
||||
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
||||
obj := "my-obj"
|
||||
retainUntilDate := time.Now().Add(24 * time.Hour)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
||||
out, err := s3client.CreateMultipartUpload(ctx, &s3.CreateMultipartUploadInput{
|
||||
Bucket: &bucket,
|
||||
Key: &obj,
|
||||
ObjectLockLegalHoldStatus: types.ObjectLockLegalHoldStatusOn,
|
||||
ObjectLockMode: types.ObjectLockModeGovernance,
|
||||
ObjectLockRetainUntilDate: &retainUntilDate,
|
||||
})
|
||||
cancel()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
parts, err := uploadParts(s3client, 100, 1, bucket, obj, *out.UploadId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
compParts := []types.CompletedPart{}
|
||||
for _, el := range parts {
|
||||
compParts = append(compParts, types.CompletedPart{
|
||||
ETag: el.ETag,
|
||||
PartNumber: el.PartNumber,
|
||||
})
|
||||
}
|
||||
|
||||
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
|
||||
_, err = s3client.CompleteMultipartUpload(ctx, &s3.CompleteMultipartUploadInput{
|
||||
Bucket: &bucket,
|
||||
Key: &obj,
|
||||
UploadId: out.UploadId,
|
||||
MultipartUpload: &types.CompletedMultipartUpload{
|
||||
Parts: compParts,
|
||||
},
|
||||
})
|
||||
cancel()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
|
||||
resp, err := s3client.HeadObject(ctx, &s3.HeadObjectInput{
|
||||
Bucket: &bucket,
|
||||
Key: &obj,
|
||||
})
|
||||
cancel()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.ObjectLockLegalHoldStatus != types.ObjectLockLegalHoldStatusOn {
|
||||
return fmt.Errorf("expected uploaded object legal hold status to be %v, instead got %v", types.ObjectLockLegalHoldStatusOn, resp.ObjectLockLegalHoldStatus)
|
||||
}
|
||||
if resp.ObjectLockMode != types.ObjectLockModeGovernance {
|
||||
return fmt.Errorf("expected uploaded object lock mode to be %v, instead got %v", types.ObjectLockModeGovernance, resp.ObjectLockMode)
|
||||
}
|
||||
|
||||
return nil
|
||||
}, withLock())
|
||||
}
|
||||
|
||||
func CreateMultipartUpload_with_object_lock_not_enabled(s *S3Conf) error {
|
||||
testName := "CreateMultipartUpload_with_object_lock_not_enabled"
|
||||
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
||||
obj := "my-obj"
|
||||
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
||||
_, err := s3client.CreateMultipartUpload(ctx, &s3.CreateMultipartUploadInput{
|
||||
Bucket: &bucket,
|
||||
Key: &obj,
|
||||
ObjectLockLegalHoldStatus: types.ObjectLockLegalHoldStatusOn,
|
||||
})
|
||||
cancel()
|
||||
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidBucketObjectLockConfiguration)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func CreateMultipartUpload_with_object_lock_invalid_retention(s *S3Conf) error {
|
||||
testName := "CreateMultipartUpload_with_object_lock_invalid_retention"
|
||||
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
||||
obj := "my-obj"
|
||||
retentionDate := time.Now().Add(24 * time.Hour)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
||||
_, err := s3client.CreateMultipartUpload(ctx, &s3.CreateMultipartUploadInput{
|
||||
Bucket: &bucket,
|
||||
Key: &obj,
|
||||
ObjectLockMode: types.ObjectLockModeGovernance,
|
||||
})
|
||||
cancel()
|
||||
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrObjectLockInvalidHeaders)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
|
||||
_, err = s3client.CreateMultipartUpload(ctx, &s3.CreateMultipartUploadInput{
|
||||
Bucket: &bucket,
|
||||
Key: &obj,
|
||||
ObjectLockRetainUntilDate: &retentionDate,
|
||||
})
|
||||
cancel()
|
||||
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrObjectLockInvalidHeaders)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func CreateMultipartUpload_past_retain_until_date(s *S3Conf) error {
|
||||
testName := "CreateMultipartUpload_past_retain_until_date"
|
||||
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
||||
obj := "my-obj"
|
||||
rDate := time.Now().Add(-5 * time.Hour)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
||||
_, err := s3client.CreateMultipartUpload(ctx, &s3.CreateMultipartUploadInput{
|
||||
Bucket: &bucket,
|
||||
Key: &obj,
|
||||
ObjectLockMode: types.ObjectLockModeGovernance,
|
||||
ObjectLockRetainUntilDate: &rDate,
|
||||
})
|
||||
cancel()
|
||||
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrPastObjectLockRetainDate)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func CreateMultipartUpload_with_invalid_tagging(s *S3Conf) error {
|
||||
testName := "CreateMultipartUpload_with_invalid_tagging"
|
||||
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
||||
obj := "my-obj"
|
||||
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
||||
_, err := s3client.CreateMultipartUpload(ctx, &s3.CreateMultipartUploadInput{
|
||||
Bucket: &bucket,
|
||||
Key: &obj,
|
||||
Tagging: getPtr("invalid_tag"),
|
||||
})
|
||||
cancel()
|
||||
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidTag)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func CreateMultipartUpload_with_tagging(s *S3Conf) error {
|
||||
testName := "CreateMultipartUpload_with_tagging"
|
||||
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
||||
obj := "my-obj"
|
||||
tagging := "key1=val1&key2=val2"
|
||||
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
||||
out, err := s3client.CreateMultipartUpload(ctx, &s3.CreateMultipartUploadInput{
|
||||
Bucket: &bucket,
|
||||
Key: &obj,
|
||||
Tagging: &tagging,
|
||||
})
|
||||
cancel()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
parts, err := uploadParts(s3client, 100, 1, bucket, obj, *out.UploadId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
compParts := []types.CompletedPart{}
|
||||
for _, el := range parts {
|
||||
compParts = append(compParts, types.CompletedPart{
|
||||
ETag: el.ETag,
|
||||
PartNumber: el.PartNumber,
|
||||
})
|
||||
}
|
||||
|
||||
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
|
||||
_, err = s3client.CompleteMultipartUpload(ctx, &s3.CompleteMultipartUploadInput{
|
||||
Bucket: &bucket,
|
||||
Key: &obj,
|
||||
UploadId: out.UploadId,
|
||||
MultipartUpload: &types.CompletedMultipartUpload{
|
||||
Parts: compParts,
|
||||
},
|
||||
})
|
||||
cancel()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
|
||||
resp, err := s3client.GetObjectTagging(ctx, &s3.GetObjectTaggingInput{
|
||||
Bucket: &bucket,
|
||||
Key: &obj,
|
||||
})
|
||||
cancel()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
expectedOutput := []types.Tag{
|
||||
{
|
||||
Key: getPtr("key1"),
|
||||
Value: getPtr("val1"),
|
||||
},
|
||||
{
|
||||
Key: getPtr("key2"),
|
||||
Value: getPtr("val2"),
|
||||
},
|
||||
}
|
||||
|
||||
if !areTagsSame(resp.TagSet, expectedOutput) {
|
||||
return fmt.Errorf("expected object tagging to be %v, instead got %v", expectedOutput, resp.TagSet)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func CreateMultipartUpload_success(s *S3Conf) error {
|
||||
testName := "CreateMultipartUpload_success"
|
||||
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
||||
|
||||
Reference in New Issue
Block a user