Files
versitygw/integration/tests.go
Ben McClelland ba501e482d feat: steaming requests for put object and put part
This builds on the previous work that sets up the body streaming
for the put object and put part requests. This adds the auth and
checksum readers to postpone the v4auth checks and the content
checksum until the end of the body stream.

This means that the backend with start reading the data from the
body stream before the request is fully validated and signatures
checked. So the backend must check the error returned from the
body reader for the final auth and content checks. The backend
is expected to discard the data upon error.

This should increase performance and reduce memory utilization
to no longer require caching the entire request body in memory
for put object and put part.
2023-12-14 19:19:46 -08:00

3873 lines
101 KiB
Go

package integration
import (
"context"
"crypto/sha256"
"encoding/xml"
"errors"
"fmt"
"io"
"net/http"
"regexp"
"strings"
"time"
"github.com/aws/aws-sdk-go-v2/service/s3"
"github.com/aws/aws-sdk-go-v2/service/s3/types"
"github.com/google/uuid"
"github.com/versity/versitygw/s3err"
"github.com/versity/versitygw/s3response"
)
var (
shortTimeout = 10 * time.Second
)
func Authentication_empty_auth_header(s *S3Conf) error {
testName := "Authentication_empty_auth_header"
return authHandler(s, &authConfig{
testName: testName,
path: "my-bucket",
method: http.MethodGet,
body: nil,
service: "s3",
date: time.Now(),
}, func(req *http.Request) error {
req.Header.Set("Authorization", "")
client := http.Client{
Timeout: shortTimeout,
}
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if err := checkAuthErr(resp, s3err.GetAPIError(s3err.ErrAuthHeaderEmpty)); err != nil {
return err
}
return nil
})
}
func Authentication_invalid_auth_header(s *S3Conf) error {
testName := "Authentication_invalid_auth_header"
return authHandler(s, &authConfig{
testName: testName,
path: "my-bucket",
method: http.MethodGet,
body: nil,
service: "s3",
date: time.Now(),
}, func(req *http.Request) error {
req.Header.Set("Authorization", "invalid header")
client := http.Client{
Timeout: shortTimeout,
}
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if err := checkAuthErr(resp, s3err.GetAPIError(s3err.ErrMissingFields)); err != nil {
return err
}
return nil
})
}
func Authentication_unsupported_signature_version(s *S3Conf) error {
testName := "Authentication_unsupported_signature_version"
return authHandler(s, &authConfig{
testName: testName,
path: "my-bucket",
method: http.MethodGet,
body: nil,
service: "s3",
date: time.Now(),
}, func(req *http.Request) error {
authHdr := req.Header.Get("Authorization")
authHdr = strings.Replace(authHdr, "AWS4-HMAC-SHA256", "AWS2-HMAC-SHA1", 1)
req.Header.Set("Authorization", authHdr)
client := http.Client{
Timeout: shortTimeout,
}
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if err := checkAuthErr(resp, s3err.GetAPIError(s3err.ErrSignatureVersionNotSupported)); err != nil {
return err
}
return nil
})
}
func Authentication_malformed_credentials(s *S3Conf) error {
testName := "Authentication_malformed_credentials"
return authHandler(s, &authConfig{
testName: testName,
path: "my-bucket",
method: http.MethodGet,
body: nil,
service: "s3",
date: time.Now(),
}, func(req *http.Request) error {
authHdr := req.Header.Get("Authorization")
regExp := regexp.MustCompile("Credential=[^,]+,")
hdr := regExp.ReplaceAllString(authHdr, "Credential-access/32234/us-east-1/s3/aws4_request,")
req.Header.Set("Authorization", hdr)
client := http.Client{
Timeout: shortTimeout,
}
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if err := checkAuthErr(resp, s3err.GetAPIError(s3err.ErrCredMalformed)); err != nil {
return err
}
return nil
})
}
func Authentication_malformed_credentials_invalid_parts(s *S3Conf) error {
testName := "Authentication_malformed_credentials_invalid_parts"
return authHandler(s, &authConfig{
testName: testName,
path: "my-bucket",
method: http.MethodGet,
body: nil,
service: "s3",
date: time.Now(),
}, func(req *http.Request) error {
authHdr := req.Header.Get("Authorization")
regExp := regexp.MustCompile("Credential=[^,]+,")
hdr := regExp.ReplaceAllString(authHdr, "Credential=access/32234/us-east-1/s3,")
req.Header.Set("Authorization", hdr)
client := http.Client{
Timeout: shortTimeout,
}
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if err := checkAuthErr(resp, s3err.GetAPIError(s3err.ErrCredMalformed)); err != nil {
return err
}
return nil
})
}
func Authentication_credentials_terminated_string(s *S3Conf) error {
testName := "Authentication_credentials_terminated_string"
return authHandler(s, &authConfig{
testName: testName,
path: "my-bucket",
method: http.MethodGet,
body: nil,
service: "s3",
date: time.Now(),
}, func(req *http.Request) error {
authHdr := req.Header.Get("Authorization")
regExp := regexp.MustCompile("Credential=[^,]+,")
hdr := regExp.ReplaceAllString(authHdr, "Credential=access/32234/us-east-1/s3/aws_request,")
req.Header.Set("Authorization", hdr)
client := http.Client{
Timeout: shortTimeout,
}
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if err := checkAuthErr(resp, s3err.GetAPIError(s3err.ErrSignatureTerminationStr)); err != nil {
return err
}
return nil
})
}
func Authentication_credentials_incorrect_service(s *S3Conf) error {
testName := "Authentication_credentials_incorrect_service"
return authHandler(s, &authConfig{
testName: testName,
path: "my-bucket",
method: http.MethodGet,
body: nil,
service: "ec2",
date: time.Now(),
}, func(req *http.Request) error {
client := http.Client{
Timeout: shortTimeout,
}
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if err := checkAuthErr(resp, s3err.GetAPIError(s3err.ErrSignatureIncorrService)); err != nil {
return err
}
return nil
})
}
func Authentication_credentials_incorrect_region(s *S3Conf) error {
testName := "Authentication_credentials_incorrect_region"
cfg := *s
if cfg.awsRegion == "us-east-1" {
cfg.awsRegion = "us-west-1"
} else {
cfg.awsRegion = "us-east-1"
}
return authHandler(&cfg, &authConfig{
testName: testName,
path: "my-bucket",
method: http.MethodGet,
body: nil,
service: "s3",
date: time.Now(),
}, func(req *http.Request) error {
client := http.Client{
Timeout: shortTimeout,
}
apiErr := s3err.APIError{
Code: "SignatureDoesNotMatch",
Description: fmt.Sprintf("Credential should be scoped to a valid Region, not %v", cfg.awsRegion),
HTTPStatusCode: http.StatusForbidden,
}
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if err := checkAuthErr(resp, apiErr); err != nil {
return err
}
return nil
})
}
func Authentication_credentials_invalid_date(s *S3Conf) error {
testName := "Authentication_credentials_invalid_date"
return authHandler(s, &authConfig{
testName: testName,
path: "my-bucket",
method: http.MethodGet,
body: nil,
service: "s3",
date: time.Now(),
}, func(req *http.Request) error {
authHdr := req.Header.Get("Authorization")
regExp := regexp.MustCompile("Credential=[^,]+,")
hdr := regExp.ReplaceAllString(authHdr, "Credential=access/3223423234/us-east-1/s3/aws4_request,")
req.Header.Set("Authorization", hdr)
client := http.Client{
Timeout: shortTimeout,
}
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if err := checkAuthErr(resp, s3err.GetAPIError(s3err.ErrSignatureDateDoesNotMatch)); err != nil {
return err
}
return nil
})
}
func Authentication_credentials_future_date(s *S3Conf) error {
testName := "Authentication_credentials_future_date"
return authHandler(s, &authConfig{
testName: testName,
path: "my-bucket",
method: http.MethodGet,
body: nil,
service: "s3",
date: time.Now().Add(time.Duration(5) * 24 * time.Hour),
}, func(req *http.Request) error {
client := http.Client{
Timeout: shortTimeout,
}
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return err
}
var errResp s3err.APIErrorResponse
err = xml.Unmarshal(body, &errResp)
if err != nil {
return err
}
if resp.StatusCode != http.StatusForbidden {
return fmt.Errorf("expected response status code to be %v, instead got %v", http.StatusForbidden, resp.StatusCode)
}
if errResp.Code != "SignatureDoesNotMatch" {
return fmt.Errorf("expected error code to be %v, instead got %v", "SignatureDoesNotMatch", errResp.Code)
}
if !strings.Contains(errResp.Message, "Signature not yet current:") {
return fmt.Errorf("expected future date error message, instead got %v", errResp.Message)
}
return nil
})
}
func Authentication_credentials_past_date(s *S3Conf) error {
testName := "Authentication_credentials_past_date"
return authHandler(s, &authConfig{
testName: testName,
path: "my-bucket",
method: http.MethodGet,
body: nil,
service: "s3",
date: time.Now().Add(time.Duration(-5) * 24 * time.Hour),
}, func(req *http.Request) error {
client := http.Client{
Timeout: shortTimeout,
}
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return err
}
var errResp s3err.APIErrorResponse
err = xml.Unmarshal(body, &errResp)
if err != nil {
return err
}
if resp.StatusCode != http.StatusForbidden {
return fmt.Errorf("expected response status code to be %v, instead got %v", http.StatusForbidden, resp.StatusCode)
}
if errResp.Code != "SignatureDoesNotMatch" {
return fmt.Errorf("expected error code to be %v, instead got %v", "SignatureDoesNotMatch", errResp.Code)
}
if !strings.Contains(errResp.Message, "Signature expired:") {
return fmt.Errorf("expected past date error message, instead got %v", errResp.Message)
}
return nil
})
}
func Authentication_credentials_non_existing_access_key(s *S3Conf) error {
testName := "Authentication_credentials_non_existing_access_key"
return authHandler(s, &authConfig{
testName: testName,
path: "my-bucket",
method: http.MethodGet,
body: nil,
service: "s3",
date: time.Now(),
}, func(req *http.Request) error {
authHdr := req.Header.Get("Authorization")
regExp := regexp.MustCompile("Credential=([^/]+)")
hdr := regExp.ReplaceAllString(authHdr, "Credential=a_rarely_existing_access_key_id_a7s86df78as6df89790a8sd7f")
req.Header.Set("Authorization", hdr)
client := http.Client{
Timeout: shortTimeout,
}
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if err := checkAuthErr(resp, s3err.GetAPIError(s3err.ErrInvalidAccessKeyID)); err != nil {
return err
}
return nil
})
}
func Authentication_invalid_signed_headers(s *S3Conf) error {
testName := "Authentication_invalid_signed_headers"
return authHandler(s, &authConfig{
testName: testName,
path: "my-bucket",
method: http.MethodGet,
body: nil,
service: "s3",
date: time.Now(),
}, func(req *http.Request) error {
authHdr := req.Header.Get("Authorization")
regExp := regexp.MustCompile("SignedHeaders=[^,]+,")
hdr := regExp.ReplaceAllString(authHdr, "SignedHeaders-host;x-amz-content-sha256;x-amz-date,")
req.Header.Set("Authorization", hdr)
client := http.Client{
Timeout: shortTimeout,
}
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if err := checkAuthErr(resp, s3err.GetAPIError(s3err.ErrInvalidQueryParams)); err != nil {
return err
}
return nil
})
}
func Authentication_missing_date_header(s *S3Conf) error {
testName := "Authentication_missing_date_header"
return authHandler(s, &authConfig{
testName: testName,
path: "my-bucket",
method: http.MethodGet,
body: nil,
service: "s3",
date: time.Now(),
}, func(req *http.Request) error {
client := http.Client{
Timeout: shortTimeout,
}
req.Header.Set("X-Amz-Date", "")
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if err := checkAuthErr(resp, s3err.GetAPIError(s3err.ErrMissingDateHeader)); err != nil {
return err
}
return nil
})
}
func Authentication_invalid_date_header(s *S3Conf) error {
testName := "Authentication_invalid_date_header"
return authHandler(s, &authConfig{
testName: testName,
path: "my-bucket",
method: http.MethodGet,
body: nil,
service: "s3",
date: time.Now(),
}, func(req *http.Request) error {
client := http.Client{
Timeout: shortTimeout,
}
req.Header.Set("X-Amz-Date", "03032006")
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if err := checkAuthErr(resp, s3err.GetAPIError(s3err.ErrMalformedDate)); err != nil {
return err
}
return nil
})
}
func Authentication_date_mismatch(s *S3Conf) error {
testName := "Authentication_date_mismatch"
return authHandler(s, &authConfig{
testName: testName,
path: "my-bucket",
method: http.MethodGet,
body: nil,
service: "s3",
date: time.Now(),
}, func(req *http.Request) error {
client := http.Client{
Timeout: shortTimeout,
}
req.Header.Set("X-Amz-Date", "20220830T095525Z")
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if err := checkAuthErr(resp, s3err.GetAPIError(s3err.ErrSignatureDateDoesNotMatch)); err != nil {
return err
}
return nil
})
}
func Authentication_incorrect_payload_hash(s *S3Conf) error {
testName := "Authentication_incorrect_payload_hash"
return authHandler(s, &authConfig{
testName: testName,
path: "my-bucket",
method: http.MethodGet,
body: nil,
service: "s3",
date: time.Now(),
}, func(req *http.Request) error {
client := http.Client{
Timeout: shortTimeout,
}
req.Header.Set("X-Amz-Content-Sha256", "7sa6df576dsa5f675sad67f")
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if err := checkAuthErr(resp, s3err.GetAPIError(s3err.ErrContentSHA256Mismatch)); err != nil {
return err
}
return nil
})
}
func Authentication_incorrect_md5(s *S3Conf) error {
testName := "Authentication_incorrect_md5"
return authHandler(s, &authConfig{
testName: testName,
path: "my-bucket",
method: http.MethodGet,
body: nil,
service: "s3",
date: time.Now(),
}, func(req *http.Request) error {
client := http.Client{
Timeout: shortTimeout,
}
req.Header.Set("Content-Md5", "sadfasdf87sad6f87==")
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if err := checkAuthErr(resp, s3err.GetAPIError(s3err.ErrInvalidDigest)); err != nil {
return err
}
return nil
})
}
func Authentication_signature_error_incorrect_secret_key(s *S3Conf) error {
testName := "Authentication_signature_error_incorrect_secret_key"
cfg := *s
cfg.awsSecret = s.awsSecret + "a"
return authHandler(&cfg, &authConfig{
testName: testName,
path: "my-bucket",
method: http.MethodGet,
body: nil,
service: "s3",
date: time.Now(),
}, func(req *http.Request) error {
client := http.Client{
Timeout: shortTimeout,
}
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if err := checkAuthErr(resp, s3err.GetAPIError(s3err.ErrSignatureDoesNotMatch)); err != nil {
return err
}
return nil
})
}
func CreateBucket_invalid_bucket_name(s *S3Conf) error {
testName := "CreateBucket_invalid_bucket_name"
runF(testName)
err := setup(s, "aa")
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidBucketName)); err != nil {
failF("%v: %v", testName, err)
return fmt.Errorf("%v: %w", testName, err)
}
err = setup(s, ".gitignore")
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidBucketName)); err != nil {
failF("%v: %v", testName, err)
return fmt.Errorf("%v: %w", testName, err)
}
err = setup(s, "my-bucket.")
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidBucketName)); err != nil {
failF("%v: %v", testName, err)
return fmt.Errorf("%v: %w", testName, err)
}
err = setup(s, "bucket-%")
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidBucketName)); err != nil {
failF("%v: %v", testName, err)
return fmt.Errorf("%v: %w", testName, err)
}
passF(testName)
return nil
}
func CreateBucket_as_user(s *S3Conf) error {
testName := "CreateBucket_as_user"
runF(testName)
usr := user{
access: "grt1",
secret: "grt1secret",
role: "user",
}
cfg := *s
cfg.awsID = usr.access
cfg.awsSecret = usr.secret
err := createUsers(s, []user{usr})
if err != nil {
failF("%v: %v", testName, err)
return fmt.Errorf("%v: %w", testName, err)
}
err = setup(&cfg, getBucketName())
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrAccessDenied)); err != nil {
failF("%v: %v", testName, err)
return fmt.Errorf("%v: %w", testName, err)
}
passF(testName)
return nil
}
func CreateBucket_existing_bucket(s *S3Conf) error {
testName := "CreateBucket_existing_bucket"
runF(testName)
bucket := getBucketName()
err := setup(s, bucket)
if err != nil {
failF("%v: %v", testName, err)
return fmt.Errorf("%v: %w", testName, err)
}
err = setup(s, bucket)
var bne *types.BucketAlreadyExists
if !errors.As(err, &bne) {
failF("%v: %v", testName, err)
return fmt.Errorf("%v: %w", testName, err)
}
err = teardown(s, bucket)
if err != nil {
failF("%v: %v", err)
return fmt.Errorf("%v: %w", testName, err)
}
passF(testName)
return nil
}
func HeadBucket_non_existing_bucket(s *S3Conf) error {
testName := "HeadBucket_non_existing_bucket"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
bcktName := getBucketName()
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
_, err := s3client.HeadBucket(ctx, &s3.HeadBucketInput{
Bucket: &bcktName,
})
cancel()
if err := checkSdkApiErr(err, "NotFound"); err != nil {
return err
}
return nil
})
}
func HeadBucket_success(s *S3Conf) error {
testName := "HeadBucket_success"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
_, err := s3client.HeadBucket(ctx, &s3.HeadBucketInput{
Bucket: &bucket,
})
cancel()
if err != nil {
return err
}
return nil
})
}
func ListBuckets_as_user(s *S3Conf) error {
testName := "ListBuckets_as_user"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
buckets := []s3response.ListAllMyBucketsEntry{{Name: bucket}}
for i := 0; i < 6; i++ {
bckt := getBucketName()
err := setup(s, bckt)
if err != nil {
return err
}
buckets = append(buckets, s3response.ListAllMyBucketsEntry{
Name: bckt,
})
}
usr := user{
access: "grt1",
secret: "grt1secret",
role: "user",
}
err := createUsers(s, []user{usr})
if err != nil {
return err
}
cfg := *s
cfg.awsID = usr.access
cfg.awsSecret = usr.secret
bckts := []string{}
for i := 0; i < 3; i++ {
bckts = append(bckts, buckets[i].Name)
}
err = changeBucketsOwner(s, bckts, usr.access)
if err != nil {
return err
}
userClient := s3.NewFromConfig(cfg.Config())
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
out, err := userClient.ListBuckets(ctx, &s3.ListBucketsInput{})
cancel()
if err != nil {
return err
}
if *out.Owner.ID != usr.access {
return fmt.Errorf("expected buckets owner to be %v, instead got %v", usr.access, *out.Owner.ID)
}
if ok := compareBuckets(out.Buckets, buckets[:3]); !ok {
return fmt.Errorf("expected list buckets result to be %v, instead got %v", buckets[:3], out.Buckets)
}
for _, elem := range buckets[1:] {
err = teardown(s, elem.Name)
if err != nil {
return err
}
}
return nil
})
}
func ListBuckets_as_admin(s *S3Conf) error {
testName := "ListBuckets_as_admin"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
buckets := []s3response.ListAllMyBucketsEntry{{Name: bucket}}
for i := 0; i < 6; i++ {
bckt := getBucketName()
err := setup(s, bckt)
if err != nil {
return err
}
buckets = append(buckets, s3response.ListAllMyBucketsEntry{
Name: bckt,
})
}
usr := user{
access: "grt1",
secret: "grt1secret",
role: "user",
}
admin := user{
access: "admin1",
secret: "admin1secret",
role: "admin",
}
err := createUsers(s, []user{usr, admin})
if err != nil {
return err
}
cfg := *s
cfg.awsID = admin.access
cfg.awsSecret = admin.secret
bckts := []string{}
for i := 0; i < 3; i++ {
bckts = append(bckts, buckets[i].Name)
}
err = changeBucketsOwner(s, bckts, usr.access)
if err != nil {
return err
}
adminClient := s3.NewFromConfig(cfg.Config())
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
out, err := adminClient.ListBuckets(ctx, &s3.ListBucketsInput{})
cancel()
if err != nil {
return err
}
if *out.Owner.ID != admin.access {
return fmt.Errorf("expected buckets owner to be %v, instead got %v", admin.access, *out.Owner.ID)
}
if ok := compareBuckets(out.Buckets, buckets); !ok {
return fmt.Errorf("expected list buckets result to be %v, instead got %v", buckets, out.Buckets)
}
for _, elem := range buckets[1:] {
err = teardown(s, elem.Name)
if err != nil {
return err
}
}
return nil
})
}
func ListBuckets_success(s *S3Conf) error {
testName := "ListBuckets_success"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
buckets := []s3response.ListAllMyBucketsEntry{{Name: bucket}}
for i := 0; i < 5; i++ {
bckt := getBucketName()
err := setup(s, bckt)
if err != nil {
return err
}
buckets = append(buckets, s3response.ListAllMyBucketsEntry{
Name: bckt,
})
}
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
out, err := s3client.ListBuckets(ctx, &s3.ListBucketsInput{})
cancel()
if err != nil {
return err
}
if *out.Owner.ID != s.awsID {
return fmt.Errorf("expected owner to be %v, instead got %v", s.awsID, *out.Owner.ID)
}
if ok := compareBuckets(out.Buckets, buckets); !ok {
return fmt.Errorf("expected list buckets result to be %v, instead got %v", buckets, out.Buckets)
}
for _, elem := range buckets[1:] {
err = teardown(s, elem.Name)
if err != nil {
return err
}
}
return nil
})
}
func CreateDeleteBucket_success(s *S3Conf) error {
testName := "CreateBucket_success"
runF(testName)
bucket := getBucketName()
err := setup(s, bucket)
if err != nil {
failF("%v: %v", err)
return fmt.Errorf("%v: %w", testName, err)
}
err = teardown(s, bucket)
if err != nil {
failF("%v: %v", err)
return fmt.Errorf("%v: %w", testName, err)
}
passF(testName)
return nil
}
func DeleteBucket_non_existing_bucket(s *S3Conf) error {
testName := "DeleteBucket_non_existing_bucket"
runF(testName)
bucket := getBucketName()
s3client := s3.NewFromConfig(s.Config())
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
_, err := s3client.DeleteBucket(ctx, &s3.DeleteBucketInput{
Bucket: &bucket,
})
cancel()
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchBucket)); err != nil {
failF("%v: %v", testName, err)
return fmt.Errorf("%v: %w", testName, err)
}
passF(testName)
return nil
}
func DeleteBucket_non_empty_bucket(s *S3Conf) error {
testName := "DeleteBucket_non_empty_bucket"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
err := putObjects(s3client, []string{"foo"}, bucket)
if err != nil {
return err
}
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
_, err = s3client.DeleteBucket(ctx, &s3.DeleteBucketInput{
Bucket: &bucket,
})
cancel()
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrBucketNotEmpty)); err != nil {
return err
}
return nil
})
}
func DeleteBucket_success_status_code(s *S3Conf) error {
testName := "DeleteBucket_success_status_code"
runF(testName)
bucket := getBucketName()
err := setup(s, bucket)
if err != nil {
failF("%v: %v", testName, err)
return fmt.Errorf("%v: %w", testName, err)
}
req, err := createSignedReq(http.MethodDelete, s.endpoint, bucket, s.awsID, s.awsSecret, "s3", s.awsRegion, nil, time.Now())
if err != nil {
failF("%v: %v", testName, err)
return fmt.Errorf("%v: %w", testName, err)
}
client := http.Client{
Timeout: shortTimeout,
}
resp, err := client.Do(req)
if err != nil {
failF("%v: %v", testName, err)
return fmt.Errorf("%v: %w", testName, err)
}
if resp.StatusCode != http.StatusNoContent {
failF("%v: expected response status to be %v, instead got %v", testName, http.StatusNoContent, resp.StatusCode)
return fmt.Errorf("%v: expected response status to be %v, instead got %v", testName, http.StatusNoContent, resp.StatusCode)
}
passF(testName)
return nil
}
func PutObject_non_existing_bucket(s *S3Conf) error {
testName := "PutObject_non_existing_bucket"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
err := putObjects(s3client, []string{"my-obj"}, "non-existing-bucket")
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchBucket)); err != nil {
return err
}
return nil
})
}
func PutObject_special_chars(s *S3Conf) error {
testName := "PutObject_special_chars"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
err := putObjects(s3client, []string{"my%key", "my^key", "my*key", "my.key", "my-key", "my_key", "my!key", "my'key", "my(key", "my)key", "my\\key", "my{}key", "my[]key", "my`key", "my+key", "my%25key", "my@key"}, bucket)
if err != nil {
return err
}
return nil
})
}
func PutObject_invalid_long_tags(s *S3Conf) error {
testName := "PutObject_invalid_long_tags"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
key := "my-obj"
tagging := fmt.Sprintf("%v=val", genRandString(200))
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
_, err := s3client.PutObject(ctx, &s3.PutObjectInput{
Bucket: &bucket,
Key: &key,
Tagging: &tagging,
})
cancel()
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidTag)); err != nil {
return err
}
tagging = fmt.Sprintf("key=%v", genRandString(300))
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
_, err = s3client.PutObject(ctx, &s3.PutObjectInput{
Bucket: &bucket,
Key: &key,
Tagging: &tagging,
})
cancel()
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidTag)); err != nil {
return err
}
return nil
})
}
func PutObject_success(s *S3Conf) error {
testName := "PutObject_success"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
err := putObjects(s3client, []string{"my-obj"}, bucket)
if err != nil {
return err
}
return nil
})
}
func PutObject_invalid_credentials(s *S3Conf) error {
testName := "PutObject_invalid_credentials"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
newconf := *s
newconf.awsSecret = newconf.awsSecret + "badpassword"
client := s3.NewFromConfig(newconf.Config())
err := putObjects(client, []string{"my-obj"}, bucket)
return checkApiErr(err, s3err.GetAPIError(s3err.ErrSignatureDoesNotMatch))
})
}
func HeadObject_non_existing_object(s *S3Conf) error {
testName := "HeadObject_non_existing_object"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
_, err := s3client.HeadObject(ctx, &s3.HeadObjectInput{
Bucket: &bucket,
Key: getPtr("my-obj"),
})
cancel()
if err := checkSdkApiErr(err, "NotFound"); err != nil {
return err
}
return nil
})
}
func HeadObject_success(s *S3Conf) error {
testName := "HeadObject_success"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
obj, dataLen := "my-obj", int64(1234567)
meta := map[string]string{
"key1": "val1",
"key2": "val2",
}
_, _, err := putObjectWithData(dataLen, &s3.PutObjectInput{Bucket: &bucket, Key: &obj, Metadata: meta}, s3client)
if err != nil {
return err
}
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
out, err := s3client.HeadObject(ctx, &s3.HeadObjectInput{
Bucket: &bucket,
Key: &obj,
})
defer cancel()
if err != nil {
return err
}
if !areMapsSame(out.Metadata, meta) {
return fmt.Errorf("incorrect object metadata")
}
contentLength := int64(0)
if out.ContentLength != nil {
contentLength = *out.ContentLength
}
if contentLength != dataLen {
return fmt.Errorf("expected data length %v, instead got %v", dataLen, contentLength)
}
return nil
})
}
func GetObject_non_existing_key(s *S3Conf) error {
testName := "GetObject_non_existing_key"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
_, err := s3client.GetObject(ctx, &s3.GetObjectInput{
Bucket: &bucket,
Key: getPtr("non-existing-key"),
})
cancel()
var bae *types.NoSuchKey
if !errors.As(err, &bae) {
return err
}
return nil
})
}
func GetObject_invalid_ranges(s *S3Conf) error {
testName := "GetObject_invalid_ranges"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
dataLength, obj := int64(1234567), "my-obj"
_, _, err := putObjectWithData(dataLength, &s3.PutObjectInput{
Bucket: &bucket,
Key: &obj,
}, s3client)
if err != nil {
return err
}
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
_, err = s3client.GetObject(ctx, &s3.GetObjectInput{
Bucket: &bucket,
Key: &obj,
Range: getPtr("bytes=invalid-range"),
})
cancel()
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidRange)); err != nil {
return err
}
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
_, err = s3client.GetObject(ctx, &s3.GetObjectInput{
Bucket: &bucket,
Key: &obj,
Range: getPtr("bytes=33-10"),
})
cancel()
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidRange)); err != nil {
return err
}
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
_, err = s3client.GetObject(ctx, &s3.GetObjectInput{
Bucket: &bucket,
Key: &obj,
Range: getPtr("bytes=1000000000-999999999999"),
})
cancel()
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidRange)); err != nil {
return err
}
return nil
})
}
func GetObject_with_meta(s *S3Conf) error {
testName := "GetObject_with_meta"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
obj := "my-obj"
meta := map[string]string{
"key1": "val1",
"key2": "val2",
}
_, _, err := putObjectWithData(0, &s3.PutObjectInput{Bucket: &bucket, Key: &obj, Metadata: meta}, s3client)
if err != nil {
return err
}
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
out, err := s3client.GetObject(ctx, &s3.GetObjectInput{
Bucket: &bucket,
Key: &obj,
})
defer cancel()
if err != nil {
return err
}
if !areMapsSame(out.Metadata, meta) {
return fmt.Errorf("incorrect object metadata")
}
return nil
})
}
func GetObject_success(s *S3Conf) error {
testName := "GetObject_success"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
dataLength, obj := int64(1234567), "my-obj"
csum, _, err := putObjectWithData(dataLength, &s3.PutObjectInput{
Bucket: &bucket,
Key: &obj,
}, s3client)
if err != nil {
return err
}
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
out, err := s3client.GetObject(ctx, &s3.GetObjectInput{
Bucket: &bucket,
Key: &obj,
})
defer cancel()
if err != nil {
return err
}
if *out.ContentLength != dataLength {
return fmt.Errorf("expected content-length %v, instead got %v", dataLength, out.ContentLength)
}
bdy, err := io.ReadAll(out.Body)
if err != nil {
return err
}
defer out.Body.Close()
outCsum := sha256.Sum256(bdy)
if outCsum != csum {
return fmt.Errorf("invalid object data")
}
return nil
})
}
func GetObject_by_range_success(s *S3Conf) error {
testName := "GetObject_by_range_success"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
dataLength, obj := int64(1234567), "my-obj"
_, data, err := putObjectWithData(dataLength, &s3.PutObjectInput{
Bucket: &bucket,
Key: &obj,
}, s3client)
if err != nil {
return err
}
rangeString := "bytes=100-200"
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
out, err := s3client.GetObject(ctx, &s3.GetObjectInput{
Bucket: &bucket,
Key: &obj,
Range: &rangeString,
})
defer cancel()
if err != nil {
return err
}
defer out.Body.Close()
if getString(out.ContentRange) != fmt.Sprintf("bytes 100-200/%v", dataLength) {
return fmt.Errorf("expected content range: %v, instead got: %v", fmt.Sprintf("bytes 100-200/%v", dataLength), getString(out.ContentRange))
}
if getString(out.AcceptRanges) != rangeString {
return fmt.Errorf("expected accept range: %v, instead got: %v", rangeString, getString(out.AcceptRanges))
}
b, err := io.ReadAll(out.Body)
if err != nil {
return err
}
// bytes range is inclusive, go range for second value is not
if !isEqual(b, data[100:201]) {
return fmt.Errorf("data mismatch of range")
}
rangeString = "bytes=100-"
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
out, err = s3client.GetObject(ctx, &s3.GetObjectInput{
Bucket: &bucket,
Key: &obj,
Range: &rangeString,
})
defer cancel()
if err != nil {
return err
}
defer out.Body.Close()
b, err = io.ReadAll(out.Body)
if err != nil {
return err
}
// bytes range is inclusive, go range for second value is not
if !isEqual(b, data[100:]) {
return fmt.Errorf("data mismatch of range")
}
return nil
})
}
func ListObjects_non_existing_bucket(s *S3Conf) error {
testName := "ListObjects_non_existing_bucket"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
bckt := getBucketName()
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
_, err := s3client.ListObjects(ctx, &s3.ListObjectsInput{
Bucket: &bckt,
})
cancel()
if err := checkSdkApiErr(err, "NoSuchBucket"); err != nil {
return err
}
return nil
})
}
func ListObjects_with_prefix(s *S3Conf) error {
testName := "ListObjects_with_prefix"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
prefix := "obj"
objWithPrefix := []string{prefix + "/foo", prefix + "/bar", prefix + "/baz/bla"}
err := putObjects(s3client, append(objWithPrefix, []string{"xzy/csf", "hell"}...), bucket)
if err != nil {
return err
}
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
out, err := s3client.ListObjects(ctx, &s3.ListObjectsInput{
Bucket: &bucket,
Prefix: &prefix,
})
cancel()
if err != nil {
return err
}
if *out.Prefix != prefix {
return fmt.Errorf("expected prefix %v, instead got %v", prefix, *out.Prefix)
}
if !compareObjects(objWithPrefix, out.Contents) {
return fmt.Errorf("unexpected output for list objects with prefix")
}
return nil
})
}
func ListObject_truncated(s *S3Conf) error {
testName := "ListObject_truncated"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
maxKeys := int32(2)
err := putObjects(s3client, []string{"foo", "bar", "baz"}, bucket)
if err != nil {
return err
}
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
out1, err := s3client.ListObjects(ctx, &s3.ListObjectsInput{
Bucket: &bucket,
MaxKeys: &maxKeys,
})
cancel()
if err != nil {
return err
}
if !*out1.IsTruncated {
return fmt.Errorf("expected out1put to be truncated")
}
if *out1.MaxKeys != maxKeys {
return fmt.Errorf("expected max-keys to be %v, instead got %v", maxKeys, out1.MaxKeys)
}
if *out1.NextMarker != "baz" {
return fmt.Errorf("expected nex-marker to be baz, instead got %v", *out1.NextMarker)
}
if !compareObjects([]string{"bar", "baz"}, out1.Contents) {
return fmt.Errorf("unexpected output for list objects with max-keys")
}
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
out2, err := s3client.ListObjects(ctx, &s3.ListObjectsInput{
Bucket: &bucket,
Marker: out1.NextMarker,
})
cancel()
if err != nil {
return err
}
if *out2.IsTruncated {
return fmt.Errorf("expected output not to be truncated")
}
if *out2.Marker != *out1.NextMarker {
return fmt.Errorf("expected marker to be %v, instead got %v", *out1.NextMarker, *out2.Marker)
}
if !compareObjects([]string{"foo"}, out2.Contents) {
return fmt.Errorf("unexpected output for list objects with max-keys")
}
return nil
})
}
func ListObjects_invalid_max_keys(s *S3Conf) error {
testName := "ListObjects_invalid_max_keys"
maxKeys := int32(-5)
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
_, err := s3client.ListObjects(ctx, &s3.ListObjectsInput{
Bucket: &bucket,
MaxKeys: &maxKeys,
})
cancel()
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidMaxKeys)); err != nil {
return err
}
return nil
})
}
func ListObjects_max_keys_0(s *S3Conf) error {
testName := "ListObjects_max_keys_0"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
objects := []string{"foo", "bar", "baz"}
err := putObjects(s3client, objects, bucket)
if err != nil {
return err
}
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
maxKeys := int32(0)
out, err := s3client.ListObjects(ctx, &s3.ListObjectsInput{
Bucket: &bucket,
MaxKeys: &maxKeys,
})
cancel()
if err != nil {
return nil
}
if len(out.Contents) > 0 {
return fmt.Errorf("unexpected output for list objects with max-keys 0")
}
return nil
})
}
func ListObjects_delimiter(s *S3Conf) error {
testName := "ListObjects_delimiter"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
err := putObjects(s3client, []string{"foo/bar/baz", "foo/bar/xyzzy", "quux/thud", "asdf"}, bucket)
if err != nil {
return err
}
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
out, err := s3client.ListObjects(ctx, &s3.ListObjectsInput{
Bucket: &bucket,
Delimiter: getPtr("/"),
})
cancel()
if err != nil {
return err
}
if *out.Delimiter != "/" {
return fmt.Errorf("expected delimiter to be /, instead got %v", *out.Delimiter)
}
if len(out.Contents) != 1 || *out.Contents[0].Key != "asdf" {
return fmt.Errorf("expected result [\"asdf\"], instead got %v", out.Contents)
}
if !comparePrefixes([]string{"foo/", "quux/"}, out.CommonPrefixes) {
return fmt.Errorf("expected common prefixes to be %v, instead got %v", []string{"foo/", "quux/"}, out.CommonPrefixes)
}
return nil
})
}
func ListObjects_max_keys_none(s *S3Conf) error {
testName := "ListObjects_max_keys_none"
return 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) error {
testName := "ListObjects_marker_not_from_obj_list"
return 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) error {
testName := "DeleteObject_non_existing_object"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
_, err := s3client.DeleteObject(ctx, &s3.DeleteObjectInput{
Bucket: &bucket,
Key: getPtr("my-obj"),
})
cancel()
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchKey)); err != nil {
return err
}
return nil
})
}
func DeleteObject_success(s *S3Conf) error {
testName := "DeleteObject_success"
return 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.DeleteObject(ctx, &s3.DeleteObjectInput{
Bucket: &bucket,
Key: &obj,
})
cancel()
if err != nil {
return err
}
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
_, err = s3client.GetObject(ctx, &s3.GetObjectInput{
Bucket: &bucket,
Key: &obj,
})
defer cancel()
if err := checkSdkApiErr(err, "NoSuchKey"); err != nil {
return err
}
return nil
})
}
func DeleteObject_success_status_code(s *S3Conf) error {
testName := "DeleteObject_success_status_code"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
obj := "my-obj"
err := putObjects(s3client, []string{obj}, bucket)
if err != nil {
return err
}
req, err := createSignedReq(http.MethodDelete, s.endpoint, fmt.Sprintf("%v/%v", bucket, obj), s.awsID, s.awsSecret, "s3", s.awsRegion, nil, time.Now())
if err != nil {
return err
}
client := http.Client{
Timeout: shortTimeout,
}
resp, err := client.Do(req)
if err != nil {
return err
}
if resp.StatusCode != http.StatusNoContent {
return fmt.Errorf("expected response status to be %v, instead got %v", http.StatusNoContent, resp.StatusCode)
}
return nil
})
}
func DeleteObjects_empty_input(s *S3Conf) error {
testName := "DeleteObjects_empty_input"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
objects := []string{"foo", "bar", "baz"}
err := putObjects(s3client, objects, bucket)
if err != nil {
return err
}
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
out, err := s3client.DeleteObjects(ctx, &s3.DeleteObjectsInput{
Bucket: &bucket,
Delete: &types.Delete{
Objects: []types.ObjectIdentifier{},
},
})
cancel()
if err != nil {
return err
}
if len(out.Deleted) != 0 {
return fmt.Errorf("expected deleted object count 0, instead got %v", len(out.Deleted))
}
if len(out.Errors) != 0 {
return fmt.Errorf("expected 0 errors, instead got %v", len(out.Errors))
}
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
res, err := s3client.ListObjects(ctx, &s3.ListObjectsInput{
Bucket: &bucket,
})
cancel()
if err != nil {
return err
}
if !compareObjects(objects, res.Contents) {
return fmt.Errorf("unexpected output for list objects with prefix")
}
return nil
})
}
func DeleteObjects_non_existing_objects(s *S3Conf) error {
testName := "DeleteObjects_empty_input"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
delObjects := []types.ObjectIdentifier{{Key: getPtr("obj1")}, {Key: getPtr("obj2")}}
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
out, err := s3client.DeleteObjects(ctx, &s3.DeleteObjectsInput{
Bucket: &bucket,
Delete: &types.Delete{
Objects: delObjects,
},
})
cancel()
if err != nil {
return err
}
if len(out.Deleted) != 0 {
return fmt.Errorf("expected deleted object count 0, instead got %v", len(out.Deleted))
}
if len(out.Errors) != 2 {
return fmt.Errorf("expected 2 errors, instead got %v", len(out.Errors))
}
for _, delErr := range out.Errors {
if *delErr.Code != "NoSuchKey" {
return fmt.Errorf("expected NoSuchKey error, instead got %v", *delErr.Code)
}
}
return nil
})
}
func DeleteObjects_success(s *S3Conf) error {
testName := "DeleteObjects_success"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
objects, objToDel := []string{"obj1", "obj2", "obj3"}, []string{"foo", "bar", "baz"}
err := putObjects(s3client, append(objToDel, objects...), bucket)
if err != nil {
return err
}
delObjects := []types.ObjectIdentifier{}
for _, key := range objToDel {
k := key
delObjects = append(delObjects, types.ObjectIdentifier{Key: &k})
}
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
out, err := s3client.DeleteObjects(ctx, &s3.DeleteObjectsInput{
Bucket: &bucket,
Delete: &types.Delete{
Objects: delObjects,
},
})
cancel()
if err != nil {
return err
}
if len(out.Deleted) != 3 {
return fmt.Errorf("expected deleted object count 3, instead got %v", len(out.Deleted))
}
if len(out.Errors) != 0 {
return fmt.Errorf("expected 2 errors, instead got %v", len(out.Errors))
}
if !compareDelObjects(objToDel, out.Deleted) {
return fmt.Errorf("unexpected deleted output")
}
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
res, err := s3client.ListObjects(ctx, &s3.ListObjectsInput{
Bucket: &bucket,
})
cancel()
if err != nil {
return err
}
if !compareObjects(objects, res.Contents) {
return fmt.Errorf("unexpected output for list objects with prefix")
}
return nil
})
}
func CopyObject_non_existing_dst_bucket(s *S3Conf) error {
testName := "CopyObject_non_existing_dst_bucket"
return 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("bucket/obj"),
})
cancel()
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchBucket)); err != nil {
return err
}
return nil
})
}
func CopyObject_not_owned_source_bucket(s *S3Conf) error {
testName := "CopyObject_not_owned_source_bucket"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
srcObj := "my-obj"
err := putObjects(s3client, []string{srcObj}, bucket)
if err != nil {
return err
}
usr := user{
access: "admin1",
secret: "admin1secret",
role: "admin",
}
cfg := *s
cfg.awsID = usr.access
cfg.awsSecret = usr.secret
err = createUsers(s, []user{usr})
if err != nil {
return err
}
dstBucket := getBucketName()
err = setup(&cfg, dstBucket)
if err != nil {
return err
}
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
_, err = s3client.CopyObject(ctx, &s3.CopyObjectInput{
Bucket: &dstBucket,
Key: getPtr("obj-1"),
CopySource: getPtr(fmt.Sprintf("%v/%v", bucket, srcObj)),
})
cancel()
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrAccessDenied)); err != nil {
return err
}
err = teardown(s, dstBucket)
if err != nil {
return err
}
return nil
})
}
func CopyObject_copy_to_itself(s *S3Conf) error {
testName := "CopyObject_copy_to_itself"
return 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)),
})
cancel()
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidCopyDest)); err != nil {
return err
}
return nil
})
}
func CopyObject_to_itself_with_new_metadata(s *S3Conf) error {
testName := "CopyObject_to_itself_with_new_metadata"
return 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) error {
testName := "CopyObject_success"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
dataLength, obj := int64(1234567), "my-obj"
dstBucket := getBucketName()
err := setup(s, dstBucket)
if err != nil {
return err
}
csum, _, err := putObjectWithData(dataLength, &s3.PutObjectInput{
Bucket: &bucket,
Key: &obj,
}, s3client)
if err != nil {
return err
}
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
_, err = s3client.CopyObject(ctx, &s3.CopyObjectInput{
Bucket: &dstBucket,
Key: &obj,
CopySource: getPtr(fmt.Sprintf("%v/%v", bucket, obj)),
})
cancel()
if err != nil {
return err
}
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
out, err := s3client.GetObject(ctx, &s3.GetObjectInput{
Bucket: &dstBucket,
Key: &obj,
})
defer cancel()
if err != nil {
return err
}
if *out.ContentLength != dataLength {
return fmt.Errorf("expected content-length %v, instead got %v", dataLength, out.ContentLength)
}
bdy, err := io.ReadAll(out.Body)
if err != nil {
return err
}
defer out.Body.Close()
outCsum := sha256.Sum256(bdy)
if outCsum != csum {
return fmt.Errorf("invalid object data")
}
err = teardown(s, dstBucket)
if err != nil {
return nil
}
return nil
})
}
func PutObjectTagging_non_existing_object(s *S3Conf) error {
testName := "PutObjectTagging_non_existing_object"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
_, err := s3client.PutObjectTagging(ctx, &s3.PutObjectTaggingInput{
Bucket: &bucket,
Key: getPtr("my-obj"),
Tagging: &types.Tagging{TagSet: []types.Tag{}}})
cancel()
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchKey)); err != nil {
return err
}
return nil
})
}
func PutObjectTagging_long_tags(s *S3Conf) error {
testName := "PutObjectTagging_long_tags"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
obj := "my-obj"
tagging := types.Tagging{TagSet: []types.Tag{{Key: getPtr(genRandString(129)), Value: getPtr("val")}}}
err := putObjects(s3client, []string{obj}, bucket)
if err != nil {
return err
}
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
_, err = s3client.PutObjectTagging(ctx, &s3.PutObjectTaggingInput{
Bucket: &bucket,
Key: &obj,
Tagging: &tagging})
cancel()
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidTag)); err != nil {
return err
}
tagging = types.Tagging{TagSet: []types.Tag{{Key: getPtr("key"), Value: getPtr(genRandString(257))}}}
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
_, err = s3client.PutObjectTagging(ctx, &s3.PutObjectTaggingInput{
Bucket: &bucket,
Key: &obj,
Tagging: &tagging})
cancel()
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidTag)); err != nil {
return err
}
return nil
})
}
func PutObjectTagging_success(s *S3Conf) error {
testName := "PutObjectTagging_success"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
obj := "my-obj"
tagging := types.Tagging{TagSet: []types.Tag{{Key: getPtr("key1"), Value: getPtr("val2")}, {Key: getPtr("key2"), Value: getPtr("val2")}}}
err := putObjects(s3client, []string{obj}, bucket)
if err != nil {
return err
}
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
_, err = s3client.PutObjectTagging(ctx, &s3.PutObjectTaggingInput{
Bucket: &bucket,
Key: &obj,
Tagging: &tagging})
cancel()
if err != nil {
return err
}
return nil
})
}
func GetObjectTagging_non_existing_object(s *S3Conf) error {
testName := "GetObjectTagging_non_existing_object"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
_, err := s3client.GetObjectTagging(ctx, &s3.GetObjectTaggingInput{
Bucket: &bucket,
Key: getPtr("my-obj"),
})
cancel()
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchKey)); err != nil {
return err
}
return nil
})
}
func GetObjectTagging_success(s *S3Conf) error {
testName := "PutObjectTagging_success"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
obj := "my-obj"
tagging := types.Tagging{TagSet: []types.Tag{{Key: getPtr("key1"), Value: getPtr("val2")}, {Key: getPtr("key2"), Value: getPtr("val2")}}}
err := putObjects(s3client, []string{obj}, bucket)
if err != nil {
return err
}
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
_, err = s3client.PutObjectTagging(ctx, &s3.PutObjectTaggingInput{
Bucket: &bucket,
Key: &obj,
Tagging: &tagging})
cancel()
if err != nil {
return err
}
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
out, err := s3client.GetObjectTagging(ctx, &s3.GetObjectTaggingInput{
Bucket: &bucket,
Key: &obj,
})
cancel()
if err != nil {
return nil
}
if !areTagsSame(out.TagSet, tagging.TagSet) {
return fmt.Errorf("expected %v instead got %v", tagging.TagSet, out.TagSet)
}
return nil
})
}
func DeleteObjectTagging_non_existing_object(s *S3Conf) error {
testName := "DeleteObjectTagging_non_existing_object"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
_, err := s3client.DeleteObjectTagging(ctx, &s3.DeleteObjectTaggingInput{
Bucket: &bucket,
Key: getPtr("my-obj"),
})
cancel()
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchKey)); err != nil {
return err
}
return nil
})
}
func DeleteObjectTagging_success_status(s *S3Conf) error {
testName := "DeleteObjectTagging_success_status"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
obj := "my-obj"
err := putObjects(s3client, []string{obj}, bucket)
if err != nil {
return err
}
tagging := types.Tagging{
TagSet: []types.Tag{
{
Key: getPtr("Hello"),
Value: getPtr("World"),
},
},
}
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
_, err = s3client.PutObjectTagging(ctx, &s3.PutObjectTaggingInput{
Bucket: &bucket,
Key: &obj,
Tagging: &tagging,
})
cancel()
if err != nil {
return err
}
req, err := createSignedReq(http.MethodDelete, s.endpoint, fmt.Sprintf("%v/%v?tagging", bucket, obj), s.awsID, s.awsSecret, "s3", s.awsRegion, nil, time.Now())
if err != nil {
return err
}
client := http.Client{
Timeout: shortTimeout,
}
resp, err := client.Do(req)
if err != nil {
return err
}
if resp.StatusCode != http.StatusNoContent {
return fmt.Errorf("expected response status to be %v, instead got %v", http.StatusNoContent, resp.StatusCode)
}
return nil
})
}
func DeleteObjectTagging_success(s *S3Conf) error {
testName := "DeleteObjectTagging_success"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
obj := "my-obj"
tagging := types.Tagging{TagSet: []types.Tag{{Key: getPtr("key1"), Value: getPtr("val2")}, {Key: getPtr("key2"), Value: getPtr("val2")}}}
err := putObjects(s3client, []string{obj}, bucket)
if err != nil {
return err
}
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
_, err = s3client.PutObjectTagging(ctx, &s3.PutObjectTaggingInput{
Bucket: &bucket,
Key: &obj,
Tagging: &tagging})
cancel()
if err != nil {
return err
}
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
_, err = s3client.DeleteObjectTagging(ctx, &s3.DeleteObjectTaggingInput{
Bucket: &bucket,
Key: &obj,
})
cancel()
if err != nil {
return nil
}
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
out, err := s3client.GetObjectTagging(ctx, &s3.GetObjectTaggingInput{
Bucket: &bucket,
Key: &obj,
})
cancel()
if err != nil {
return nil
}
if len(out.TagSet) > 0 {
return fmt.Errorf("expected empty tag set, instead got %v", out.TagSet)
}
return nil
})
}
func CreateMultipartUpload_non_existing_bucket(s *S3Conf) error {
testName := "CreateMultipartUpload_non_existing_bucket"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
bucketName := getBucketName()
_, err := createMp(s3client, bucketName, "my-obj")
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchBucket)); err != nil {
return err
}
return nil
})
}
func CreateMultipartUpload_success(s *S3Conf) error {
testName := "CreateMultipartUpload_success"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
obj := "my-obj"
out, err := createMp(s3client, bucket, obj)
if err != nil {
return err
}
if *out.Bucket != bucket {
return fmt.Errorf("expected bucket name %v, instead got %v", bucket, *out.Bucket)
}
if *out.Key != obj {
return fmt.Errorf("expected object name %v, instead got %v", obj, *out.Key)
}
if _, err := uuid.Parse(*out.UploadId); err != nil {
return err
}
return nil
})
}
func UploadPart_non_existing_bucket(s *S3Conf) error {
testName := "UploadPart_non_existing_bucket"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
bucketName := getBucketName()
partNumber := int32(1)
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
_, err := s3client.UploadPart(ctx, &s3.UploadPartInput{
Bucket: &bucketName,
Key: getPtr("my-obj"),
UploadId: getPtr("uploadId"),
PartNumber: &partNumber,
})
cancel()
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchBucket)); err != nil {
return err
}
return nil
})
}
func UploadPart_invalid_part_number(s *S3Conf) error {
testName := "UploadPart_invalid_part_number"
partNumber := int32(-10)
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
_, err := s3client.UploadPart(ctx, &s3.UploadPartInput{
Bucket: &bucket,
Key: getPtr("my-obj"),
UploadId: getPtr("uploadId"),
PartNumber: &partNumber,
})
cancel()
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidPart)); err != nil {
return err
}
return nil
})
}
func UploadPart_non_existing_mp_upload(s *S3Conf) error {
testName := "UploadPart_non_existing_mp_upload"
partNumber := int32(1)
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
_, err := s3client.UploadPart(ctx, &s3.UploadPartInput{
Bucket: &bucket,
Key: getPtr("my-obj"),
UploadId: getPtr("uploadId"),
PartNumber: &partNumber,
})
cancel()
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchUpload)); err != nil {
return err
}
return nil
})
}
func UploadPart_non_existing_key(s *S3Conf) error {
testName := "UploadPart_non_existing_key"
partNumber := int32(1)
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
obj := "my-obj"
out, err := createMp(s3client, bucket, obj)
if err != nil {
return err
}
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
_, err = s3client.UploadPart(ctx, &s3.UploadPartInput{
Bucket: &bucket,
Key: getPtr("non-existing-object-key"),
UploadId: out.UploadId,
PartNumber: &partNumber,
})
cancel()
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchUpload)); err != nil {
return err
}
return nil
})
}
func UploadPart_success(s *S3Conf) error {
testName := "UploadPart_success"
partNumber := int32(1)
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
obj := "my-obj"
out, err := createMp(s3client, bucket, obj)
if err != nil {
return err
}
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
res, err := s3client.UploadPart(ctx, &s3.UploadPartInput{
Bucket: &bucket,
Key: &obj,
UploadId: out.UploadId,
PartNumber: &partNumber,
})
cancel()
if err != nil {
return err
}
if *res.ETag == "" {
return fmt.Errorf("expected a valid etag, instead got empty")
}
return nil
})
}
func UploadPartCopy_non_existing_bucket(s *S3Conf) error {
testName := "UploadPartCopy_non_existing_bucket"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
bucketName := getBucketName()
partNumber := int32(1)
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
_, err := s3client.UploadPartCopy(ctx, &s3.UploadPartCopyInput{
Bucket: &bucketName,
CopySource: getPtr("Copy-Source"),
UploadId: getPtr("uploadId"),
Key: getPtr("my-obj"),
PartNumber: &partNumber,
})
cancel()
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchBucket)); err != nil {
return err
}
return nil
})
}
func UploadPartCopy_incorrect_uploadId(s *S3Conf) error {
testName := "UploadPartCopy_incorrect_uploadId"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
obj, srcBucket, srcObj := "my-obj", getBucketName(), "src-obj"
err := setup(s, srcBucket)
if err != nil {
return err
}
err = putObjects(s3client, []string{srcObj}, srcBucket)
if err != nil {
return err
}
_, err = createMp(s3client, bucket, obj)
if err != nil {
return err
}
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
partNumber := int32(1)
_, err = s3client.UploadPartCopy(ctx, &s3.UploadPartCopyInput{
Bucket: &bucket,
CopySource: getPtr(srcBucket + "/" + srcObj),
UploadId: getPtr("incorrect-upload-id"),
Key: &obj,
PartNumber: &partNumber,
})
cancel()
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchUpload)); err != nil {
return err
}
err = teardown(s, srcBucket)
if err != nil {
return err
}
return nil
})
}
func UploadPartCopy_incorrect_object_key(s *S3Conf) error {
testName := "UploadPartCopy_incorrect_object_key"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
obj, srcBucket, srcObj := "my-obj", getBucketName(), "src-obj"
err := setup(s, srcBucket)
if err != nil {
return err
}
err = putObjects(s3client, []string{srcObj}, srcBucket)
if err != nil {
return err
}
out, err := createMp(s3client, bucket, obj)
if err != nil {
return err
}
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
partNumber := int32(1)
_, err = s3client.UploadPartCopy(ctx, &s3.UploadPartCopyInput{
Bucket: &bucket,
CopySource: getPtr(srcBucket + "/" + srcObj),
UploadId: out.UploadId,
Key: getPtr("non-existing-object-key"),
PartNumber: &partNumber,
})
cancel()
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchUpload)); err != nil {
return err
}
err = teardown(s, srcBucket)
if err != nil {
return err
}
return nil
})
}
func UploadPartCopy_invalid_part_number(s *S3Conf) error {
testName := "UploadPartCopy_invalid_part_number"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
partNumber := int32(-10)
_, err := s3client.UploadPartCopy(ctx, &s3.UploadPartCopyInput{
Bucket: &bucket,
CopySource: getPtr("Copy-Source"),
UploadId: getPtr("uploadId"),
Key: getPtr("non-existing-object-key"),
PartNumber: &partNumber,
})
cancel()
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidPart)); err != nil {
return err
}
return nil
})
}
func UploadPartCopy_invalid_copy_source(s *S3Conf) error {
testName := "UploadPartCopy_invalid_copy_source"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
obj := "my-obj"
out, err := createMp(s3client, bucket, obj)
if err != nil {
return err
}
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
partNumber := int32(1)
_, err = s3client.UploadPartCopy(ctx, &s3.UploadPartCopyInput{
Bucket: &bucket,
CopySource: getPtr("invalid-copy-source"),
UploadId: out.UploadId,
Key: &obj,
PartNumber: &partNumber,
})
cancel()
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidCopySource)); err != nil {
return err
}
return nil
})
}
func UploadPartCopy_non_existing_source_bucket(s *S3Conf) error {
testName := "UploadPartCopy_non_existing_source_bucket"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
obj := "my-obj"
out, err := createMp(s3client, bucket, obj)
if err != nil {
return err
}
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
partNumber := int32(1)
_, err = s3client.UploadPartCopy(ctx, &s3.UploadPartCopyInput{
Bucket: &bucket,
CopySource: getPtr("src/bucket/src/obj"),
UploadId: out.UploadId,
Key: &obj,
PartNumber: &partNumber,
})
cancel()
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchBucket)); err != nil {
return err
}
return nil
})
}
func UploadPartCopy_non_existing_source_object_key(s *S3Conf) error {
testName := "UploadPartCopy_non_existing_source_object_key"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
obj, srcBucket := "my-obj", getBucketName()
err := setup(s, srcBucket)
if err != nil {
return nil
}
out, err := createMp(s3client, bucket, obj)
if err != nil {
return err
}
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
partNumber := int32(1)
_, err = s3client.UploadPartCopy(ctx, &s3.UploadPartCopyInput{
Bucket: &bucket,
CopySource: getPtr(srcBucket + "/non/existing/obj/key"),
UploadId: out.UploadId,
Key: &obj,
PartNumber: &partNumber,
})
cancel()
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchKey)); err != nil {
return err
}
err = teardown(s, srcBucket)
if err != nil {
return err
}
return nil
})
}
func UploadPartCopy_success(s *S3Conf) error {
testName := "UploadPartCopy_success"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
obj, srcBucket, srcObj := "my-obj", getBucketName(), "src-obj"
err := setup(s, srcBucket)
if err != nil {
return err
}
objSize := 5 * 1024 * 1024
_, _, err = putObjectWithData(int64(objSize), &s3.PutObjectInput{
Bucket: &srcBucket,
Key: &srcObj,
}, s3client)
if err != nil {
return err
}
out, err := createMp(s3client, bucket, obj)
if err != nil {
return err
}
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
partNumber := int32(1)
copyOut, err := s3client.UploadPartCopy(ctx, &s3.UploadPartCopyInput{
Bucket: &bucket,
CopySource: getPtr(srcBucket + "/" + srcObj),
UploadId: out.UploadId,
Key: &obj,
PartNumber: &partNumber,
})
cancel()
if err != nil {
return err
}
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
res, err := s3client.ListParts(ctx, &s3.ListPartsInput{
Bucket: &bucket,
Key: &obj,
UploadId: out.UploadId,
})
cancel()
if err != nil {
return err
}
if len(res.Parts) != 1 {
return fmt.Errorf("expected parts to be 1, instead got %v", len(res.Parts))
}
if *res.Parts[0].PartNumber != 1 {
return fmt.Errorf("expected part-number to be 1, instead got %v", res.Parts[0].PartNumber)
}
if *res.Parts[0].Size != int64(objSize) {
return fmt.Errorf("expected part size to be %v, instead got %v", objSize, res.Parts[0].Size)
}
if *res.Parts[0].ETag != *copyOut.CopyPartResult.ETag {
return fmt.Errorf("expected part etag to be %v, instead got %v", *copyOut.CopyPartResult.ETag, *res.Parts[0].ETag)
}
err = teardown(s, srcBucket)
if err != nil {
return err
}
return nil
})
}
func UploadPartCopy_by_range_invalid_range(s *S3Conf) error {
testName := "UploadPartCopy_by_range_invalid_range"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
obj, srcBucket, srcObj := "my-obj", getBucketName(), "src-obj"
err := setup(s, srcBucket)
if err != nil {
return err
}
objSize := 5 * 1024 * 1024
_, _, err = putObjectWithData(int64(objSize), &s3.PutObjectInput{
Bucket: &srcBucket,
Key: &srcObj,
}, s3client)
if err != nil {
return err
}
out, err := createMp(s3client, bucket, obj)
if err != nil {
return err
}
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
partNumber := int32(1)
_, err = s3client.UploadPartCopy(ctx, &s3.UploadPartCopyInput{
Bucket: &bucket,
CopySource: getPtr(srcBucket + "/" + srcObj),
UploadId: out.UploadId,
Key: &obj,
PartNumber: &partNumber,
CopySourceRange: getPtr("invalid-range"),
})
cancel()
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidRange)); err != nil {
return err
}
err = teardown(s, srcBucket)
if err != nil {
return err
}
return nil
})
}
func UploadPartCopy_greater_range_than_obj_size(s *S3Conf) error {
testName := "UploadPartCopy_greater_range_than_obj_size"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
obj, srcBucket, srcObj := "my-obj", getBucketName(), "src-obj"
err := setup(s, srcBucket)
if err != nil {
return err
}
srcObjSize := 5 * 1024 * 1024
_, _, err = putObjectWithData(int64(srcObjSize), &s3.PutObjectInput{
Bucket: &srcBucket,
Key: &srcObj,
}, s3client)
if err != nil {
return err
}
out, err := createMp(s3client, bucket, obj)
if err != nil {
return err
}
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
partNumber := int32(1)
_, err = s3client.UploadPartCopy(ctx, &s3.UploadPartCopyInput{
Bucket: &bucket,
CopySource: getPtr(srcBucket + "/" + srcObj),
UploadId: out.UploadId,
Key: &obj,
CopySourceRange: getPtr(fmt.Sprintf("bytes=0-%v", srcObjSize+50)), // The specified range is greater than the actual object size
PartNumber: &partNumber,
})
cancel()
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidRange)); err != nil {
return err
}
err = teardown(s, srcBucket)
if err != nil {
return err
}
return nil
})
}
func UploadPartCopy_by_range_success(s *S3Conf) error {
testName := "UploadPartCopy_by_range_success"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
obj, srcBucket, srcObj := "my-obj", getBucketName(), "src-obj"
err := setup(s, srcBucket)
if err != nil {
return err
}
objSize := 5 * 1024 * 1024
_, _, err = putObjectWithData(int64(objSize), &s3.PutObjectInput{
Bucket: &srcBucket,
Key: &srcObj,
}, s3client)
if err != nil {
return err
}
out, err := createMp(s3client, bucket, obj)
if err != nil {
return err
}
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
partNumber := int32(1)
copyOut, err := s3client.UploadPartCopy(ctx, &s3.UploadPartCopyInput{
Bucket: &bucket,
CopySource: getPtr(srcBucket + "/" + srcObj),
CopySourceRange: getPtr("bytes=100-200"),
UploadId: out.UploadId,
Key: &obj,
PartNumber: &partNumber,
})
cancel()
if err != nil {
return err
}
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
res, err := s3client.ListParts(ctx, &s3.ListPartsInput{
Bucket: &bucket,
Key: &obj,
UploadId: out.UploadId,
})
cancel()
if err != nil {
return err
}
if len(res.Parts) != 1 {
return fmt.Errorf("expected parts to be 1, instead got %v", len(res.Parts))
}
if *res.Parts[0].PartNumber != 1 {
return fmt.Errorf("expected part-number to be 1, instead got %v", res.Parts[0].PartNumber)
}
if *res.Parts[0].Size != 101 {
return fmt.Errorf("expected part size to be %v, instead got %v", 101, res.Parts[0].Size)
}
if *res.Parts[0].ETag != *copyOut.CopyPartResult.ETag {
return fmt.Errorf("expected part etag to be %v, instead got %v", *copyOut.CopyPartResult.ETag, *res.Parts[0].ETag)
}
err = teardown(s, srcBucket)
if err != nil {
return err
}
return nil
})
}
func ListParts_incorrect_uploadId(s *S3Conf) error {
testName := "ListParts_incorrect_uploadId"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
_, err := s3client.ListParts(ctx, &s3.ListPartsInput{
Bucket: &bucket,
Key: getPtr("my-obj"),
UploadId: getPtr("invalid uploadId"),
})
cancel()
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchUpload)); err != nil {
return err
}
return nil
})
}
func ListParts_incorrect_object_key(s *S3Conf) error {
testName := "ListParts_incorrect_object_key"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
obj := "my-obj"
out, err := createMp(s3client, bucket, obj)
if err != nil {
return err
}
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
_, err = s3client.ListParts(ctx, &s3.ListPartsInput{
Bucket: &bucket,
Key: getPtr("incorrect-object-key"),
UploadId: out.UploadId,
})
cancel()
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchUpload)); err != nil {
return err
}
return nil
})
}
func ListParts_success(s *S3Conf) error {
testName := "ListParts_success"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
obj := "my-obj"
out, err := createMp(s3client, bucket, obj)
if err != nil {
return err
}
parts, err := uploadParts(s3client, 5*1024*1024, 5, bucket, obj, *out.UploadId)
if err != nil {
return err
}
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
res, err := s3client.ListParts(ctx, &s3.ListPartsInput{
Bucket: &bucket,
Key: &obj,
UploadId: out.UploadId,
})
cancel()
if err != nil {
return err
}
if ok := compareParts(parts, res.Parts); !ok {
return fmt.Errorf("expected parts %+v, instead got %+v", parts, res.Parts)
}
return nil
})
}
func ListMultipartUploads_non_existing_bucket(s *S3Conf) error {
testName := "ListMultipartUploads_non_existing_bucket"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
bucketName := getBucketName()
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
_, err := s3client.ListMultipartUploads(ctx, &s3.ListMultipartUploadsInput{
Bucket: &bucketName,
})
cancel()
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchBucket)); err != nil {
return err
}
return nil
})
}
func ListMultipartUploads_empty_result(s *S3Conf) error {
testName := "ListMultipartUploads_empty_result"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
out, err := s3client.ListMultipartUploads(ctx, &s3.ListMultipartUploadsInput{
Bucket: &bucket,
})
cancel()
if err != nil {
return err
}
if len(out.Uploads) != 0 {
return fmt.Errorf("expected empty uploads, instead got %+v", out.Uploads)
}
return nil
})
}
func ListMultipartUploads_invalid_max_uploads(s *S3Conf) error {
testName := "ListMultipartUploads_invalid_max_uploads"
maxUploads := int32(-3)
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
_, err := s3client.ListMultipartUploads(ctx, &s3.ListMultipartUploadsInput{
Bucket: &bucket,
MaxUploads: &maxUploads,
})
cancel()
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidMaxKeys)); err != nil {
return err
}
return nil
})
}
func ListMultipartUploads_max_uploads(s *S3Conf) error {
testName := "ListMultipartUploads_max_uploads"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
uploads := []types.MultipartUpload{}
for i := 1; i < 6; i++ {
out, err := createMp(s3client, bucket, fmt.Sprintf("obj%v", i))
if err != nil {
return err
}
uploads = append(uploads, types.MultipartUpload{UploadId: out.UploadId, Key: out.Key})
}
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
maxUploads := int32(2)
out, err := s3client.ListMultipartUploads(ctx, &s3.ListMultipartUploadsInput{
Bucket: &bucket,
MaxUploads: &maxUploads,
})
cancel()
if err != nil {
return err
}
if !*out.IsTruncated {
return fmt.Errorf("expected the output to be truncated")
}
if *out.MaxUploads != 2 {
return fmt.Errorf("expected max-uploads to be 2, instead got %v", out.MaxUploads)
}
if ok := compareMultipartUploads(out.Uploads, uploads[:2]); !ok {
return fmt.Errorf("expected multipart uploads to be %v, instead got %v", uploads[:2], out.Uploads)
}
if *out.NextKeyMarker != *uploads[1].Key {
return fmt.Errorf("expected next-key-marker to be %v, instead got %v", *uploads[1].Key, *out.NextKeyMarker)
}
if *out.NextUploadIdMarker != *uploads[1].UploadId {
return fmt.Errorf("expected next-upload-id-marker to be %v, instead got %v", *uploads[1].Key, *out.NextKeyMarker)
}
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
out, err = s3client.ListMultipartUploads(ctx, &s3.ListMultipartUploadsInput{
Bucket: &bucket,
KeyMarker: out.NextKeyMarker,
})
cancel()
if err != nil {
return err
}
if ok := compareMultipartUploads(out.Uploads, uploads[2:]); !ok {
return fmt.Errorf("expected multipart uploads to be %v, instead got %v", uploads[2:], out.Uploads)
}
return nil
})
}
func ListMultipartUploads_incorrect_next_key_marker(s *S3Conf) error {
testName := "ListMultipartUploads_incorrect_next_key_marker"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
for i := 1; i < 6; i++ {
_, err := createMp(s3client, bucket, fmt.Sprintf("obj%v", i))
if err != nil {
return err
}
}
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
out, err := s3client.ListMultipartUploads(ctx, &s3.ListMultipartUploadsInput{
Bucket: &bucket,
KeyMarker: getPtr("incorrect_object_key"),
})
cancel()
if err != nil {
return err
}
if len(out.Uploads) != 0 {
return fmt.Errorf("expected empty list of multipart uploads, instead got %v", out.Uploads)
}
return nil
})
}
func ListMultipartUploads_ignore_upload_id_marker(s *S3Conf) error {
testName := "ListMultipartUploads_ignore_upload_id_marker"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
uploads := []types.MultipartUpload{}
for i := 1; i < 6; i++ {
out, err := createMp(s3client, bucket, fmt.Sprintf("obj%v", i))
if err != nil {
return err
}
uploads = append(uploads, types.MultipartUpload{UploadId: out.UploadId, Key: out.Key})
}
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
out, err := s3client.ListMultipartUploads(ctx, &s3.ListMultipartUploadsInput{
Bucket: &bucket,
UploadIdMarker: uploads[2].UploadId,
})
cancel()
if err != nil {
return err
}
if ok := compareMultipartUploads(out.Uploads, uploads); !ok {
return fmt.Errorf("expected multipart uploads to be %v, instead got %v", uploads, out.Uploads)
}
return nil
})
}
func ListMultipartUploads_success(s *S3Conf) error {
testName := "ListMultipartUploads_max_uploads"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
obj1, obj2 := "my-obj-1", "my-obj-2"
out1, err := createMp(s3client, bucket, obj1)
if err != nil {
return err
}
out2, err := createMp(s3client, bucket, obj2)
if err != nil {
return err
}
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
out, err := s3client.ListMultipartUploads(ctx, &s3.ListMultipartUploadsInput{
Bucket: &bucket,
})
cancel()
if err != nil {
return err
}
expected := []types.MultipartUpload{
{
Key: &obj1,
UploadId: out1.UploadId,
},
{
Key: &obj2,
UploadId: out2.UploadId,
},
}
if len(out.Uploads) != 2 {
return fmt.Errorf("expected 2 upload, instead got %v", len(out.Uploads))
}
if ok := compareMultipartUploads(out.Uploads, expected); !ok {
return fmt.Errorf("expected uploads %v, instead got %v", expected, out.Uploads)
}
return nil
})
}
func AbortMultipartUpload_non_existing_bucket(s *S3Conf) error {
testName := "AbortMultipartUpload_non_existing_bucket"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
_, err := s3client.AbortMultipartUpload(ctx, &s3.AbortMultipartUploadInput{
Bucket: getPtr("incorrectBucket"),
Key: getPtr("my-obj"),
UploadId: getPtr("uploadId"),
})
cancel()
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchBucket)); err != nil {
return err
}
return nil
})
}
func AbortMultipartUpload_incorrect_uploadId(s *S3Conf) error {
testName := "AbortMultipartUpload_incorrect_uploadId"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
_, err := s3client.AbortMultipartUpload(ctx, &s3.AbortMultipartUploadInput{
Bucket: &bucket,
Key: getPtr("my-obj"),
UploadId: getPtr("invalid uploadId"),
})
cancel()
if err := checkSdkApiErr(err, "NoSuchUpload"); err != nil {
return err
}
return nil
})
}
func AbortMultipartUpload_incorrect_object_key(s *S3Conf) error {
testName := "AbortMultipartUpload_incorrect_object_key"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
obj := "my-obj"
out, err := createMp(s3client, bucket, obj)
if err != nil {
return err
}
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
_, err = s3client.AbortMultipartUpload(ctx, &s3.AbortMultipartUploadInput{
Bucket: &bucket,
Key: getPtr("incorrect-object-key"),
UploadId: out.UploadId,
})
cancel()
if err := checkSdkApiErr(err, "NoSuchUpload"); err != nil {
return err
}
return nil
})
}
func AbortMultipartUpload_success(s *S3Conf) error {
testName := "AbortMultipartUpload_success"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
obj := "my-obj"
out, err := createMp(s3client, bucket, obj)
if err != nil {
return err
}
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
_, err = s3client.AbortMultipartUpload(ctx, &s3.AbortMultipartUploadInput{
Bucket: &bucket,
Key: &obj,
UploadId: out.UploadId,
})
cancel()
if err != nil {
return err
}
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
res, err := s3client.ListMultipartUploads(ctx, &s3.ListMultipartUploadsInput{
Bucket: &bucket,
})
cancel()
if err != nil {
return err
}
if len(res.Uploads) != 0 {
return fmt.Errorf("expected 0 upload, instead got %v", len(res.Uploads))
}
return nil
})
}
func AbortMultipartUpload_success_status_code(s *S3Conf) error {
testName := "AbortMultipartUpload_success_status_code"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
obj := "my-obj"
out, err := createMp(s3client, bucket, obj)
if err != nil {
return err
}
req, err := createSignedReq(http.MethodDelete, s.endpoint, fmt.Sprintf("%v/%v?uploadId=%v", bucket, obj, *out.UploadId), s.awsID, s.awsSecret, "s3", s.awsRegion, nil, time.Now())
if err != nil {
return err
}
client := http.Client{
Timeout: shortTimeout,
}
resp, err := client.Do(req)
if err != nil {
return err
}
if resp.StatusCode != http.StatusNoContent {
return fmt.Errorf("expected response status to be %v, instead got %v", http.StatusNoContent, resp.StatusCode)
}
return nil
})
}
func CompletedMultipartUpload_non_existing_bucket(s *S3Conf) error {
testName := "CompletedMultipartUpload_non_existing_bucket"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
_, err := s3client.AbortMultipartUpload(ctx, &s3.AbortMultipartUploadInput{
Bucket: getPtr("non-existing-bucket"),
Key: getPtr("some/key"),
UploadId: getPtr("uploadId"),
})
cancel()
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchBucket)); err != nil {
return err
}
return nil
})
}
func CompleteMultipartUpload_invalid_part_number(s *S3Conf) error {
testName := "CompleteMultipartUpload_invalid_part_number"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
obj := "my-obj"
out, err := createMp(s3client, bucket, obj)
if err != nil {
return err
}
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
partNumber := int32(1)
res, err := s3client.UploadPart(ctx, &s3.UploadPartInput{
Bucket: &bucket,
Key: &obj,
UploadId: out.UploadId,
PartNumber: &partNumber,
})
cancel()
if err != nil {
return err
}
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
partNumber = int32(5)
_, err = s3client.CompleteMultipartUpload(ctx, &s3.CompleteMultipartUploadInput{
Bucket: &bucket,
Key: &obj,
UploadId: out.UploadId,
MultipartUpload: &types.CompletedMultipartUpload{
Parts: []types.CompletedPart{
{
ETag: res.ETag,
PartNumber: &partNumber,
},
},
},
})
cancel()
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidPart)); err != nil {
return err
}
return nil
})
}
func CompleteMultipartUpload_invalid_ETag(s *S3Conf) error {
testName := "CompleteMultipartUpload_invalid_ETag"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
obj := "my-obj"
out, err := createMp(s3client, bucket, obj)
if err != nil {
return err
}
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
partNumber := int32(1)
_, err = s3client.UploadPart(ctx, &s3.UploadPartInput{
Bucket: &bucket,
Key: &obj,
UploadId: out.UploadId,
PartNumber: &partNumber,
})
cancel()
if err != nil {
return err
}
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
_, err = s3client.CompleteMultipartUpload(ctx, &s3.CompleteMultipartUploadInput{
Bucket: &bucket,
Key: &obj,
UploadId: out.UploadId,
MultipartUpload: &types.CompletedMultipartUpload{
Parts: []types.CompletedPart{
{
ETag: getPtr("invalidETag"),
PartNumber: &partNumber,
},
},
},
})
cancel()
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidPart)); err != nil {
return err
}
return nil
})
}
func CompleteMultipartUpload_success(s *S3Conf) error {
testName := "CompleteMultipartUpload_success"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
obj := "my-obj"
out, err := createMp(s3client, bucket, obj)
if err != nil {
return err
}
objSize := 5 * 1024 * 1024
parts, err := uploadParts(s3client, objSize, 5, 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)
res, err := s3client.CompleteMultipartUpload(ctx, &s3.CompleteMultipartUploadInput{
Bucket: &bucket,
Key: &obj,
UploadId: out.UploadId,
MultipartUpload: &types.CompletedMultipartUpload{
Parts: compParts,
},
})
cancel()
if err != nil {
return err
}
if *res.Key != obj {
return fmt.Errorf("expected object key to be %v, instead got %v", obj, *res.Key)
}
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.ETag != *res.ETag {
return fmt.Errorf("expected the uploaded object etag to be %v, instead got %v", *res.ETag, *resp.ETag)
}
if *resp.ContentLength != int64(objSize) {
return fmt.Errorf("expected the uploaded object size to be %v, instead got %v", objSize, resp.ContentLength)
}
return nil
})
}
func PutBucketAcl_non_existing_bucket(s *S3Conf) error {
testName := "PutBucketAcl_non_existing_bucket"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
_, err := s3client.PutBucketAcl(ctx, &s3.PutBucketAclInput{
Bucket: getPtr(getBucketName()),
})
cancel()
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchBucket)); err != nil {
return err
}
return nil
})
}
func PutBucketAcl_invalid_acl_canned_and_acp(s *S3Conf) error {
testName := "PutBucketAcl_invalid_acl_canned_and_acp"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
_, err := s3client.PutBucketAcl(ctx, &s3.PutBucketAclInput{
Bucket: &bucket,
ACL: types.BucketCannedACLPrivate,
GrantRead: getPtr("user1"),
})
cancel()
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidRequest)); err != nil {
return err
}
return nil
})
}
func PutBucketAcl_invalid_acl_canned_and_grants(s *S3Conf) error {
testName := "PutBucketAcl_invalid_acl_canned_and_grants"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
_, err := s3client.PutBucketAcl(ctx, &s3.PutBucketAclInput{
Bucket: &bucket,
ACL: types.BucketCannedACLPrivate,
AccessControlPolicy: &types.AccessControlPolicy{
Grants: []types.Grant{
{
Grantee: &types.Grantee{
ID: getPtr("awsID"),
Type: types.TypeCanonicalUser,
},
},
},
Owner: &types.Owner{
ID: &s.awsID,
},
},
})
cancel()
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidRequest)); err != nil {
return err
}
return nil
})
}
func PutBucketAcl_invalid_acl_acp_and_grants(s *S3Conf) error {
testName := "PutBucketAcl_invalid_acl_acp_and_grants"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
_, err := s3client.PutBucketAcl(ctx, &s3.PutBucketAclInput{
Bucket: &bucket,
GrantFullControl: getPtr("userAccess"),
AccessControlPolicy: &types.AccessControlPolicy{
Grants: []types.Grant{
{
Grantee: &types.Grantee{
ID: getPtr("awsID"),
Type: types.TypeCanonicalUser,
},
},
},
Owner: &types.Owner{
ID: &s.awsID,
},
},
})
cancel()
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidRequest)); err != nil {
return err
}
return nil
})
}
func PutBucketAcl_invalid_owner(s *S3Conf) error {
testName := "PutBucketAcl_invalid_acl_acp_and_grants"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
_, err := s3client.PutBucketAcl(ctx, &s3.PutBucketAclInput{
Bucket: &bucket,
AccessControlPolicy: &types.AccessControlPolicy{
Grants: []types.Grant{
{
Grantee: &types.Grantee{
ID: getPtr("awsID"),
Type: types.TypeCanonicalUser,
},
},
},
Owner: &types.Owner{
ID: getPtr("invalidOwner"),
},
},
})
cancel()
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrAccessDenied)); err != nil {
return err
}
return nil
})
}
func PutBucketAcl_success_access_denied(s *S3Conf) error {
testName := "PutBucketAcl_success_access_denied"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
err := createUsers(s, []user{{"grt1", "grt1secret", "user"}})
if err != nil {
return err
}
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
_, err = s3client.PutBucketAcl(ctx, &s3.PutBucketAclInput{
Bucket: &bucket,
AccessControlPolicy: &types.AccessControlPolicy{
Grants: []types.Grant{
{
Grantee: &types.Grantee{
ID: getPtr("grt1"),
Type: types.TypeCanonicalUser,
},
Permission: types.PermissionRead,
},
},
Owner: &types.Owner{
ID: &s.awsID,
},
},
})
cancel()
if err != nil {
return err
}
newConf := *s
newConf.awsID = "grt1"
newConf.awsSecret = "grt1secret"
userClient := s3.NewFromConfig(newConf.Config())
err = putObjects(userClient, []string{"my-obj"}, bucket)
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrAccessDenied)); err != nil {
return err
}
return nil
})
}
func PutBucketAcl_success_canned_acl(s *S3Conf) error {
testName := "PutBucketAcl_success_canned_acl"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
err := createUsers(s, []user{{"grt1", "grt1secret", "user"}})
if err != nil {
return err
}
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
_, err = s3client.PutBucketAcl(ctx, &s3.PutBucketAclInput{
Bucket: &bucket,
AccessControlPolicy: &types.AccessControlPolicy{
Owner: &types.Owner{
ID: &s.awsID,
},
},
ACL: types.BucketCannedACLPublicReadWrite,
})
cancel()
if err != nil {
return err
}
newConf := *s
newConf.awsID = "grt1"
newConf.awsSecret = "grt1secret"
userClient := s3.NewFromConfig(newConf.Config())
err = putObjects(userClient, []string{"my-obj"}, bucket)
if err != nil {
return err
}
return nil
})
}
func PutBucketAcl_success_acp(s *S3Conf) error {
testName := "PutBucketAcl_success_acp"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
err := createUsers(s, []user{{"grt1", "grt1secret", "user"}})
if err != nil {
return err
}
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
_, err = s3client.PutBucketAcl(ctx, &s3.PutBucketAclInput{
Bucket: &bucket,
AccessControlPolicy: &types.AccessControlPolicy{
Owner: &types.Owner{
ID: &s.awsID,
},
},
GrantRead: getPtr("grt1"),
})
cancel()
if err != nil {
return err
}
newConf := *s
newConf.awsID = "grt1"
newConf.awsSecret = "grt1secret"
userClient := s3.NewFromConfig(newConf.Config())
err = putObjects(userClient, []string{"my-obj"}, bucket)
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrAccessDenied)); err != nil {
return err
}
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
_, err = userClient.HeadBucket(ctx, &s3.HeadBucketInput{
Bucket: &bucket,
})
cancel()
if err != nil {
return err
}
return nil
})
}
func PutBucketAcl_success_grants(s *S3Conf) error {
testName := "PutBucketAcl_success_grants"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
err := createUsers(s, []user{{"grt1", "grt1secret", "user"}})
if err != nil {
return err
}
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
_, err = s3client.PutBucketAcl(ctx, &s3.PutBucketAclInput{
Bucket: &bucket,
AccessControlPolicy: &types.AccessControlPolicy{
Grants: []types.Grant{
{
Grantee: &types.Grantee{
ID: getPtr("grt1"),
Type: types.TypeCanonicalUser,
},
Permission: types.PermissionFullControl,
},
},
Owner: &types.Owner{
ID: &s.awsID,
},
},
})
cancel()
if err != nil {
return err
}
newConf := *s
newConf.awsID = "grt1"
newConf.awsSecret = "grt1secret"
userClient := s3.NewFromConfig(newConf.Config())
err = putObjects(userClient, []string{"my-obj"}, bucket)
if err != nil {
return err
}
return nil
})
}
func GetBucketAcl_non_existing_bucket(s *S3Conf) error {
testName := "GetBucketAcl_non_existing_bucket"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
_, err := s3client.GetBucketAcl(ctx, &s3.GetBucketAclInput{
Bucket: getPtr(getBucketName()),
})
cancel()
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchBucket)); err != nil {
return err
}
return nil
})
}
func GetBucketAcl_access_denied(s *S3Conf) error {
testName := "GetBucketAcl_access_denied"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
err := createUsers(s, []user{{"grt1", "grt1secret", "user"}})
if err != nil {
return err
}
newConf := *s
newConf.awsID = "grt1"
newConf.awsSecret = "grt1secret"
userClient := s3.NewFromConfig(newConf.Config())
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
_, err = userClient.GetBucketAcl(ctx, &s3.GetBucketAclInput{
Bucket: &bucket,
})
cancel()
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrAccessDenied)); err != nil {
return err
}
return nil
})
}
func GetBucketAcl_success(s *S3Conf) error {
testName := "GetBucketAcl_success"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
err := createUsers(s, []user{
{"grt1", "grt1secret", "user"},
{"grt2", "grt2secret", "user"},
{"grt3", "grt3secret", "user"},
})
if err != nil {
return err
}
grants := []types.Grant{
{
Grantee: &types.Grantee{
ID: getPtr("grt1"),
Type: types.TypeCanonicalUser,
},
Permission: types.PermissionFullControl,
},
{
Grantee: &types.Grantee{
ID: getPtr("grt2"),
Type: types.TypeCanonicalUser,
},
Permission: types.PermissionReadAcp,
},
{
Grantee: &types.Grantee{
ID: getPtr("grt3"),
Type: types.TypeCanonicalUser,
},
Permission: types.PermissionWrite,
},
}
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
_, err = s3client.PutBucketAcl(ctx, &s3.PutBucketAclInput{
Bucket: &bucket,
AccessControlPolicy: &types.AccessControlPolicy{
Grants: grants,
Owner: &types.Owner{
ID: &s.awsID,
},
},
})
cancel()
if err != nil {
return err
}
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
out, err := s3client.GetBucketAcl(ctx, &s3.GetBucketAclInput{
Bucket: &bucket,
})
cancel()
if err != nil {
return err
}
if ok := compareGrants(out.Grants, grants); !ok {
return fmt.Errorf("expected grants to be %v, instead got %v", grants, out.Grants)
}
if *out.Owner.ID != s.awsID {
return fmt.Errorf("expected bucket owner to be %v, instead got %v", s.awsID, *out.Owner.ID)
}
return nil
})
}
// Posix related tests
func PutObject_overwrite_dir_obj(s *S3Conf) error {
testName := "PutObject_overwrite_dir_obj"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
err := putObjects(s3client, []string{"foo/", "foo"}, bucket)
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrExistingObjectIsDirectory)); err != nil {
return err
}
return nil
})
}
func PutObject_overwrite_file_obj(s *S3Conf) error {
testName := "PutObject_overwrite_file_obj"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
err := putObjects(s3client, []string{"foo", "foo/"}, bucket)
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrObjectParentIsFile)); err != nil {
return err
}
return nil
})
}
func PutObject_dir_obj_with_data(s *S3Conf) error {
testName := "PutObject_dir_obj_with_data"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
_, _, err := putObjectWithData(int64(20), &s3.PutObjectInput{
Bucket: &bucket,
Key: getPtr("obj/"),
}, s3client)
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrDirectoryObjectContainsData)); err != nil {
return err
}
return nil
})
}
func CreateMultipartUpload_dir_obj(s *S3Conf) error {
testName := "CreateMultipartUpload_dir_obj"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
_, err := createMp(s3client, bucket, "obj/")
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrDirectoryObjectContainsData)); err != nil {
return err
}
return nil
})
}