Update Go AWS SDK to the latest version

This commit is contained in:
Andrey Smirnov
2019-07-13 00:03:55 +03:00
committed by Andrey Smirnov
parent d08be990ef
commit 94a72b23ff
2183 changed files with 885887 additions and 228114 deletions
+8 -3
View File
@@ -273,7 +273,7 @@ type DeleteObjectsIterator struct {
inc bool
}
// Next will increment the default iterator's index and and ensure that there
// Next will increment the default iterator's index and ensure that there
// is another object to iterator to.
func (iter *DeleteObjectsIterator) Next() bool {
if iter.inc {
@@ -338,6 +338,11 @@ func (d *BatchDelete) Delete(ctx aws.Context, iter BatchDeleteIterator) error {
}
}
// iter.Next() could return false (above) plus populate iter.Err()
if iter.Err() != nil {
errs = append(errs, newError(iter.Err(), nil, nil))
}
if input != nil && len(input.Delete.Objects) > 0 {
if err := deleteBatch(ctx, d, input, objects); err != nil {
errs = append(errs, err...)
@@ -453,7 +458,7 @@ type DownloadObjectsIterator struct {
inc bool
}
// Next will increment the default iterator's index and and ensure that there
// Next will increment the default iterator's index and ensure that there
// is another object to iterator to.
func (batcher *DownloadObjectsIterator) Next() bool {
if batcher.inc {
@@ -492,7 +497,7 @@ type UploadObjectsIterator struct {
inc bool
}
// Next will increment the default iterator's index and and ensure that there
// Next will increment the default iterator's index and ensure that there
// is another object to iterator to.
func (batcher *UploadObjectsIterator) Next() bool {
if batcher.inc {
+15 -5
View File
@@ -39,6 +39,8 @@ type Downloader struct {
// The number of goroutines to spin up in parallel when sending parts.
// If this is set to zero, the DefaultDownloadConcurrency value will be used.
//
// Concurrency of 1 will download the parts sequentially.
//
// Concurrency is ignored if the Range input parameter is provided.
Concurrency int
@@ -124,7 +126,8 @@ type maxRetrier interface {
}
// Download downloads an object in S3 and writes the payload into w using
// concurrent GET requests.
// concurrent GET requests. The n int64 returned is the size of the object downloaded
// in bytes.
//
// Additional functional options can be provided to configure the individual
// download. These options are copies of the Downloader instance Download is called from.
@@ -135,6 +138,9 @@ type maxRetrier interface {
// The w io.WriterAt can be satisfied by an os.File to do multipart concurrent
// downloads, or in memory []byte wrapper using aws.WriteAtBuffer.
//
// Specifying a Downloader.Concurrency of 1 will cause the Downloader to
// download the parts from S3 sequentially.
//
// If the GetObjectInput's Range value is provided that will cause the downloader
// to perform a single GetObjectInput request for that object's range. This will
// caused the part size, and concurrency configurations to be ignored.
@@ -143,11 +149,12 @@ func (d Downloader) Download(w io.WriterAt, input *s3.GetObjectInput, options ..
}
// DownloadWithContext downloads an object in S3 and writes the payload into w
// using concurrent GET requests.
// using concurrent GET requests. The n int64 returned is the size of the object downloaded
// in bytes.
//
// DownloadWithContext is the same as Download with the additional support for
// Context input parameters. The Context must not be nil. A nil Context will
// cause a panic. Use the Context to add deadlining, timeouts, ect. The
// cause a panic. Use the Context to add deadlining, timeouts, etc. The
// DownloadWithContext may create sub-contexts for individual underlying
// requests.
//
@@ -160,6 +167,9 @@ func (d Downloader) Download(w io.WriterAt, input *s3.GetObjectInput, options ..
// The w io.WriterAt can be satisfied by an os.File to do multipart concurrent
// downloads, or in memory []byte wrapper using aws.WriteAtBuffer.
//
// Specifying a Downloader.Concurrency of 1 will cause the Downloader to
// download the parts from S3 sequentially.
//
// It is safe to call this method concurrently across goroutines.
//
// If the GetObjectInput's Range value is provided that will cause the downloader
@@ -207,14 +217,14 @@ func (d Downloader) DownloadWithContext(ctx aws.Context, w io.WriterAt, input *s
//
// objects := []s3manager.BatchDownloadObject {
// {
// Input: &s3.GetObjectInput {
// Object: &s3.GetObjectInput {
// Bucket: aws.String("bucket"),
// Key: aws.String("foo"),
// },
// Writer: fooFile,
// },
// {
// Input: &s3.GetObjectInput {
// Object: &s3.GetObjectInput {
// Bucket: aws.String("bucket"),
// Key: aws.String("bar"),
// },
+26 -19
View File
@@ -11,6 +11,7 @@ import (
"strconv"
"strings"
"sync"
"sync/atomic"
"testing"
"time"
@@ -582,10 +583,22 @@ func TestDownload_WithFailure(t *testing.T) {
svc := s3.New(unit.Session)
svc.Handlers.Send.Clear()
first := true
reqCount := int64(0)
startingByte := 0
svc.Handlers.Send.PushBack(func(r *request.Request) {
if first {
first = false
switch atomic.LoadInt64(&reqCount) {
case 1:
// Give a chance for the multipart chunks to be queued up
time.Sleep(1 * time.Second)
r.HTTPResponse = &http.Response{
Header: http.Header{},
Body: ioutil.NopCloser(&bytes.Buffer{}),
}
r.Error = awserr.New("ConnectionError", "some connection error", nil)
r.Retryable = aws.Bool(false)
default:
body := bytes.NewReader(make([]byte, s3manager.DefaultDownloadPartSize))
r.HTTPResponse = &http.Response{
StatusCode: http.StatusOK,
@@ -596,22 +609,18 @@ func TestDownload_WithFailure(t *testing.T) {
}
r.HTTPResponse.Header.Set("Content-Length", strconv.Itoa(body.Len()))
r.HTTPResponse.Header.Set("Content-Range",
fmt.Sprintf("bytes 0-%d/%d", body.Len()-1, body.Len()*10))
return
fmt.Sprintf("bytes %d-%d/%d", startingByte, body.Len()-1, body.Len()*10))
startingByte += body.Len()
if reqCount > 0 {
// sleep here to ensure context switching between goroutines
time.Sleep(25 * time.Millisecond)
}
}
// Give a chance for the multipart chunks to be queued up
time.Sleep(1 * time.Second)
r.HTTPResponse = &http.Response{
Header: http.Header{},
Body: ioutil.NopCloser(&bytes.Buffer{}),
}
r.Error = awserr.New("ConnectionError", "some connection error", nil)
r.Retryable = aws.Bool(false)
atomic.AddInt64(&reqCount, 1)
})
start := time.Now()
d := s3manager.NewDownloaderWithClient(svc, func(d *s3manager.Downloader) {
d.Concurrency = 2
})
@@ -628,10 +637,8 @@ func TestDownload_WithFailure(t *testing.T) {
t.Fatalf("expect error, got none")
}
limit := start.Add(5 * time.Second)
dur := time.Now().Sub(start)
if time.Now().After(limit) {
t.Errorf("expect time to be less than %v, took %v", limit, dur)
if atomic.LoadInt64(&reqCount) > 3 {
t.Errorf("expect no more than 3 requests, but received %d", reqCount)
}
}
@@ -0,0 +1,26 @@
// +build integration
package s3manager_test
import (
"testing"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/s3/s3manager"
)
func TestGetBucketRegion(t *testing.T) {
expectRegion := aws.StringValue(integSess.Config.Region)
ctx := aws.BackgroundContext()
region, err := s3manager.GetBucketRegion(ctx, integSess,
aws.StringValue(bucketName), expectRegion)
if err != nil {
t.Fatalf("expect no error, got %v", err)
}
if e, a := expectRegion, region; e != a {
t.Errorf("expect %s bucket region, got %s", e, a)
}
}
@@ -0,0 +1,86 @@
// +build integration
package s3manager_test
import (
"crypto/md5"
"fmt"
"io"
"os"
"testing"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/awstesting/integration"
"github.com/aws/aws-sdk-go/awstesting/integration/s3integ"
"github.com/aws/aws-sdk-go/service/s3"
"github.com/aws/aws-sdk-go/service/s3/s3manager"
)
func init() {
integSess = integration.SessionWithDefaultRegion("us-west-2")
}
var integSess *session.Session
var bucketName *string
var svc *s3.S3
func TestMain(m *testing.M) {
svc = s3.New(integSess)
bucketName = aws.String(s3integ.GenerateBucketName())
if err := s3integ.SetupTest(svc, *bucketName); err != nil {
panic(err)
}
var result int
defer func() {
if err := s3integ.CleanupTest(svc, *bucketName); err != nil {
fmt.Fprintln(os.Stderr, err)
}
if r := recover(); r != nil {
fmt.Fprintln(os.Stderr, "S3 integrationt tests paniced,", r)
result = 1
}
os.Exit(result)
}()
result = m.Run()
}
type dlwriter struct {
buf []byte
}
func newDLWriter(size int) *dlwriter {
return &dlwriter{buf: make([]byte, size)}
}
func (d dlwriter) WriteAt(p []byte, pos int64) (n int, err error) {
if pos > int64(len(d.buf)) {
return 0, io.EOF
}
written := 0
for i, b := range p {
if i >= len(d.buf) {
break
}
d.buf[pos+int64(i)] = b
written++
}
return written, nil
}
func validate(t *testing.T, key string, md5value string) {
mgr := s3manager.NewDownloader(integSess)
params := &s3.GetObjectInput{Bucket: bucketName, Key: &key}
w := newDLWriter(1024 * 1024 * 20)
n, err := mgr.Download(w, params)
if err != nil {
t.Fatalf("expect no error, got %v", err)
}
if e, a := md5value, fmt.Sprintf("%x", md5.Sum(w.buf[0:n])); e != a {
t.Errorf("expect %s md5 value, got %s", e, a)
}
}
@@ -0,0 +1,88 @@
// +build integration
package s3manager_test
import (
"bytes"
"crypto/md5"
"fmt"
"regexp"
"strings"
"testing"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/service/s3"
"github.com/aws/aws-sdk-go/service/s3/s3manager"
)
var integBuf12MB = make([]byte, 1024*1024*12)
var integMD512MB = fmt.Sprintf("%x", md5.Sum(integBuf12MB))
func TestUploadConcurrently(t *testing.T) {
key := "12mb-1"
mgr := s3manager.NewUploader(integSess)
out, err := mgr.Upload(&s3manager.UploadInput{
Bucket: bucketName,
Key: &key,
Body: bytes.NewReader(integBuf12MB),
})
if err != nil {
t.Fatalf("expect no error, got %v", err)
}
if len(out.UploadID) == 0 {
t.Errorf("expect upload ID but was empty")
}
re := regexp.MustCompile(`^https?://.+/` + key + `$`)
if e, a := re.String(), out.Location; !re.MatchString(a) {
t.Errorf("expect %s to match URL regexp %q, did not", e, a)
}
validate(t, key, integMD512MB)
}
func TestUploadFailCleanup(t *testing.T) {
// Break checksum on 2nd part so it fails
part := 0
svc.Handlers.Build.PushBack(func(r *request.Request) {
if r.Operation.Name == "UploadPart" {
if part == 1 {
r.HTTPRequest.Header.Set("X-Amz-Content-Sha256", "000")
}
part++
}
})
key := "12mb-leave"
mgr := s3manager.NewUploaderWithClient(svc, func(u *s3manager.Uploader) {
u.LeavePartsOnError = false
})
_, err := mgr.Upload(&s3manager.UploadInput{
Bucket: bucketName,
Key: &key,
Body: bytes.NewReader(integBuf12MB),
})
if err == nil {
t.Fatalf("expect error, but did not get one")
}
aerr := err.(awserr.Error)
if e, a := "MissingRegion", aerr.Code(); strings.Contains(a, e) {
t.Errorf("expect %q to not be in error code %q", e, a)
}
uploadID := ""
merr := err.(s3manager.MultiUploadFailure)
if uploadID = merr.UploadID(); len(uploadID) == 0 {
t.Errorf("expect upload ID to not be empty, but was")
}
_, err = svc.ListParts(&s3.ListPartsInput{
Bucket: bucketName, Key: &key, UploadId: &uploadID,
})
if err == nil {
t.Errorf("expect error for list parts, but got none")
}
}
@@ -9,13 +9,21 @@ import (
"github.com/aws/aws-sdk-go/service/s3/s3manager"
)
var _ DownloaderAPI = (*s3manager.Downloader)(nil)
// DownloaderAPI is the interface type for s3manager.Downloader.
type DownloaderAPI interface {
Download(io.WriterAt, *s3.GetObjectInput, ...func(*s3manager.Downloader)) (int64, error)
DownloadWithContext(aws.Context, io.WriterAt, *s3.GetObjectInput, ...func(*s3manager.Downloader)) (int64, error)
}
var _ DownloaderAPI = (*s3manager.Downloader)(nil)
// DownloadWithIterator is the interface type for the contained method of the same name.
type DownloadWithIterator interface {
DownloadWithIterator(aws.Context, s3manager.BatchDownloadIterator, ...func(*s3manager.Downloader)) error
}
var _ UploaderAPI = (*s3manager.Uploader)(nil)
var _ UploadWithIterator = (*s3manager.Uploader)(nil)
// UploaderAPI is the interface type for s3manager.Uploader.
type UploaderAPI interface {
@@ -23,4 +31,16 @@ type UploaderAPI interface {
UploadWithContext(aws.Context, *s3manager.UploadInput, ...func(*s3manager.Uploader)) (*s3manager.UploadOutput, error)
}
var _ UploaderAPI = (*s3manager.Uploader)(nil)
// UploadWithIterator is the interface for uploading objects to S3 using the S3
// upload manager.
type UploadWithIterator interface {
UploadWithIterator(aws.Context, s3manager.BatchUploadIterator, ...func(*s3manager.Uploader)) error
}
var _ BatchDelete = (*s3manager.BatchDelete)(nil)
// BatchDelete is the interface type for batch deleting objects from S3 using
// the S3 manager. (separated for user to compose).
type BatchDelete interface {
Delete(aws.Context, s3manager.BatchDeleteIterator) error
}
+41 -115
View File
@@ -6,12 +6,12 @@ import (
"io"
"sort"
"sync"
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/aws/awsutil"
"github.com/aws/aws-sdk-go/aws/client"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/service/s3"
"github.com/aws/aws-sdk-go/service/s3/s3iface"
@@ -96,100 +96,6 @@ func (m multiUploadError) UploadID() string {
return m.uploadID
}
// UploadInput contains all input for upload requests to Amazon S3.
type UploadInput struct {
// The canned ACL to apply to the object.
ACL *string `location:"header" locationName:"x-amz-acl" type:"string"`
Bucket *string `location:"uri" locationName:"Bucket" type:"string" required:"true"`
// Specifies caching behavior along the request/reply chain.
CacheControl *string `location:"header" locationName:"Cache-Control" type:"string"`
// Specifies presentational information for the object.
ContentDisposition *string `location:"header" locationName:"Content-Disposition" type:"string"`
// Specifies what content encodings have been applied to the object and thus
// what decoding mechanisms must be applied to obtain the media-type referenced
// by the Content-Type header field.
ContentEncoding *string `location:"header" locationName:"Content-Encoding" type:"string"`
// The language the content is in.
ContentLanguage *string `location:"header" locationName:"Content-Language" type:"string"`
// The base64-encoded 128-bit MD5 digest of the part data.
ContentMD5 *string `location:"header" locationName:"Content-MD5" type:"string"`
// A standard MIME type describing the format of the object data.
ContentType *string `location:"header" locationName:"Content-Type" type:"string"`
// The date and time at which the object is no longer cacheable.
Expires *time.Time `location:"header" locationName:"Expires" type:"timestamp" timestampFormat:"rfc822"`
// Gives the grantee READ, READ_ACP, and WRITE_ACP permissions on the object.
GrantFullControl *string `location:"header" locationName:"x-amz-grant-full-control" type:"string"`
// Allows grantee to read the object data and its metadata.
GrantRead *string `location:"header" locationName:"x-amz-grant-read" type:"string"`
// Allows grantee to read the object ACL.
GrantReadACP *string `location:"header" locationName:"x-amz-grant-read-acp" type:"string"`
// Allows grantee to write the ACL for the applicable object.
GrantWriteACP *string `location:"header" locationName:"x-amz-grant-write-acp" type:"string"`
Key *string `location:"uri" locationName:"Key" type:"string" required:"true"`
// A map of metadata to store with the object in S3.
Metadata map[string]*string `location:"headers" locationName:"x-amz-meta-" type:"map"`
// Confirms that the requester knows that she or he will be charged for the
// request. Bucket owners need not specify this parameter in their requests.
// Documentation on downloading objects from requester pays buckets can be found
// at http://docs.aws.amazon.com/AmazonS3/latest/dev/ObjectsinRequesterPaysBuckets.html
RequestPayer *string `location:"header" locationName:"x-amz-request-payer" type:"string"`
// Specifies the algorithm to use to when encrypting the object (e.g., AES256,
// aws:kms).
SSECustomerAlgorithm *string `location:"header" locationName:"x-amz-server-side-encryption-customer-algorithm" type:"string"`
// Specifies the customer-provided encryption key for Amazon S3 to use in encrypting
// data. This value is used to store the object and then it is discarded; Amazon
// does not store the encryption key. The key must be appropriate for use with
// the algorithm specified in the x-amz-server-side-encryption-customer-algorithm
// header.
SSECustomerKey *string `location:"header" locationName:"x-amz-server-side-encryption-customer-key" type:"string"`
// Specifies the 128-bit MD5 digest of the encryption key according to RFC 1321.
// Amazon S3 uses this header for a message integrity check to ensure the encryption
// key was transmitted without error.
SSECustomerKeyMD5 *string `location:"header" locationName:"x-amz-server-side-encryption-customer-key-MD5" type:"string"`
// Specifies the AWS KMS key ID to use for object encryption. All GET and PUT
// requests for an object protected by AWS KMS will fail if not made via SSL
// or using SigV4. Documentation on configuring any of the officially supported
// AWS SDKs and CLI can be found at http://docs.aws.amazon.com/AmazonS3/latest/dev/UsingAWSSDK.html#specify-signature-version
SSEKMSKeyId *string `location:"header" locationName:"x-amz-server-side-encryption-aws-kms-key-id" type:"string"`
// The Server-side encryption algorithm used when storing this object in S3
// (e.g., AES256, aws:kms).
ServerSideEncryption *string `location:"header" locationName:"x-amz-server-side-encryption" type:"string"`
// The type of storage to use for the object. Defaults to 'STANDARD'.
StorageClass *string `location:"header" locationName:"x-amz-storage-class" type:"string"`
// The tag-set for the object. The tag-set must be encoded as URL Query parameters
Tagging *string `location:"header" locationName:"x-amz-tagging" type:"string"`
// If the bucket is configured as a website, redirects requests for this object
// to another object in the same bucket or to an external URL. Amazon S3 stores
// the value of this header in the object metadata.
WebsiteRedirectLocation *string `location:"header" locationName:"x-amz-website-redirect-location" type:"string"`
// The readable body payload to send to S3.
Body io.Reader
}
// UploadOutput represents a response from the Upload() call.
type UploadOutput struct {
// The URL where the object was uploaded to.
@@ -239,8 +145,15 @@ type Uploader struct {
// MaxUploadParts is the max number of parts which will be uploaded to S3.
// Will be used to calculate the partsize of the object to be uploaded.
// E.g: 5GB file, with MaxUploadParts set to 100, will upload the file
// as 100, 50MB parts.
// With a limited of s3.MaxUploadParts (10,000 parts).
// as 100, 50MB parts. With a limited of s3.MaxUploadParts (10,000 parts).
//
// MaxUploadParts must not be used to limit the total number of bytes uploaded.
// Use a type like to io.LimitReader (https://golang.org/pkg/io/#LimitedReader)
// instead. An io.LimitReader is helpful when uploading an unbounded reader
// to S3, and you know its maximum size. Otherwise the reader's io.EOF returned
// error must be used to signal end of stream.
//
// Defaults to package const's MaxUploadParts value.
MaxUploadParts int
// The client to use when uploading to S3.
@@ -357,7 +270,7 @@ func (u Uploader) Upload(input *UploadInput, options ...func(*Uploader)) (*Uploa
//
// UploadWithContext is the same as Upload with the additional support for
// Context input parameters. The Context must not be nil. A nil Context will
// cause a panic. Use the context to add deadlining, timeouts, ect. The
// cause a panic. Use the context to add deadlining, timeouts, etc. The
// UploadWithContext may create sub-contexts for individual underlying requests.
//
// Additional functional options can be provided to configure the individual
@@ -395,7 +308,7 @@ func (u Uploader) UploadWithContext(ctx aws.Context, input *UploadInput, opts ..
// },
// }
//
// iter := &s3managee.UploadObjectsIterator{Objects: objects}
// iter := &s3manager.UploadObjectsIterator{Objects: objects}
// if err := svc.UploadWithIterator(aws.BackgroundContext(), iter); err != nil {
// return err
// }
@@ -477,6 +390,9 @@ func (u *uploader) init() {
if u.cfg.PartSize == 0 {
u.cfg.PartSize = DefaultUploadPartSize
}
if u.cfg.MaxUploadParts == 0 {
u.cfg.MaxUploadParts = MaxUploadParts
}
u.bufferPool = sync.Pool{
New: func() interface{} { return make([]byte, u.cfg.PartSize) },
@@ -631,21 +547,6 @@ func (u *multiuploader) upload(firstBuf io.ReadSeeker, firstPart []byte) (*Uploa
// Read and queue the rest of the parts
for u.geterr() == nil && err == nil {
num++
// This upload exceeded maximum number of supported parts, error now.
if num > int64(u.cfg.MaxUploadParts) || num > int64(MaxUploadParts) {
var msg string
if num > int64(u.cfg.MaxUploadParts) {
msg = fmt.Sprintf("exceeded total allowed configured MaxUploadParts (%d). Adjust PartSize to fit in this limit",
u.cfg.MaxUploadParts)
} else {
msg = fmt.Sprintf("exceeded total allowed S3 limit MaxUploadParts (%d). Adjust PartSize to fit in this limit",
MaxUploadParts)
}
u.seterr(awserr.New("TotalPartsExceeded", msg, nil))
break
}
var reader io.ReadSeeker
var nextChunkLen int
var part []byte
@@ -666,6 +567,21 @@ func (u *multiuploader) upload(firstBuf io.ReadSeeker, firstPart []byte) (*Uploa
break
}
num++
// This upload exceeded maximum number of supported parts, error now.
if num > int64(u.cfg.MaxUploadParts) || num > int64(MaxUploadParts) {
var msg string
if num > int64(u.cfg.MaxUploadParts) {
msg = fmt.Sprintf("exceeded total allowed configured MaxUploadParts (%d). Adjust PartSize to fit in this limit",
u.cfg.MaxUploadParts)
} else {
msg = fmt.Sprintf("exceeded total allowed S3 limit MaxUploadParts (%d). Adjust PartSize to fit in this limit",
MaxUploadParts)
}
u.seterr(awserr.New("TotalPartsExceeded", msg, nil))
break
}
ch <- chunk{buf: reader, part: part, num: num}
}
@@ -683,8 +599,18 @@ func (u *multiuploader) upload(firstBuf io.ReadSeeker, firstPart []byte) (*Uploa
uploadID: u.uploadID,
}
}
// Create a presigned URL of the S3 Get Object in order to have parity with
// single part upload.
getReq, _ := u.cfg.S3.GetObjectRequest(&s3.GetObjectInput{
Bucket: u.in.Bucket,
Key: u.in.Key,
})
getReq.Config.Credentials = credentials.AnonymousCredentials
uploadLocation, _, _ := getReq.PresignRequest(1)
return &UploadOutput{
Location: aws.StringValue(complete.Location),
Location: uploadLocation,
VersionID: complete.VersionId,
UploadID: u.uploadID,
}, nil
+129
View File
@@ -0,0 +1,129 @@
// Code generated by private/model/cli/gen-api/main.go. DO NOT EDIT.
package s3manager
import (
"io"
"time"
)
// UploadInput provides the input parameters for uploading a stream or buffer
// to an object in an Amazon S3 bucket. This type is similar to the s3
// package's PutObjectInput with the exception that the Body member is an
// io.Reader instead of an io.ReadSeeker.
type UploadInput struct {
_ struct{} `type:"structure" payload:"Body"`
// The canned ACL to apply to the object.
ACL *string `location:"header" locationName:"x-amz-acl" type:"string" enum:"ObjectCannedACL"`
// The readable body payload to send to S3.
Body io.Reader
// Name of the bucket to which the PUT operation was initiated.
//
// Bucket is a required field
Bucket *string `location:"uri" locationName:"Bucket" type:"string" required:"true"`
// Specifies caching behavior along the request/reply chain.
CacheControl *string `location:"header" locationName:"Cache-Control" type:"string"`
// Specifies presentational information for the object.
ContentDisposition *string `location:"header" locationName:"Content-Disposition" type:"string"`
// Specifies what content encodings have been applied to the object and thus
// what decoding mechanisms must be applied to obtain the media-type referenced
// by the Content-Type header field.
ContentEncoding *string `location:"header" locationName:"Content-Encoding" type:"string"`
// The language the content is in.
ContentLanguage *string `location:"header" locationName:"Content-Language" type:"string"`
// The base64-encoded 128-bit MD5 digest of the part data. This parameter is
// auto-populated when using the command from the CLI. This parameted is required
// if object lock parameters are specified.
ContentMD5 *string `location:"header" locationName:"Content-MD5" type:"string"`
// A standard MIME type describing the format of the object data.
ContentType *string `location:"header" locationName:"Content-Type" type:"string"`
// The date and time at which the object is no longer cacheable.
Expires *time.Time `location:"header" locationName:"Expires" type:"timestamp"`
// Gives the grantee READ, READ_ACP, and WRITE_ACP permissions on the object.
GrantFullControl *string `location:"header" locationName:"x-amz-grant-full-control" type:"string"`
// Allows grantee to read the object data and its metadata.
GrantRead *string `location:"header" locationName:"x-amz-grant-read" type:"string"`
// Allows grantee to read the object ACL.
GrantReadACP *string `location:"header" locationName:"x-amz-grant-read-acp" type:"string"`
// Allows grantee to write the ACL for the applicable object.
GrantWriteACP *string `location:"header" locationName:"x-amz-grant-write-acp" type:"string"`
// Object key for which the PUT operation was initiated.
//
// Key is a required field
Key *string `location:"uri" locationName:"Key" min:"1" type:"string" required:"true"`
// A map of metadata to store with the object in S3.
Metadata map[string]*string `location:"headers" locationName:"x-amz-meta-" type:"map"`
// The Legal Hold status that you want to apply to the specified object.
ObjectLockLegalHoldStatus *string `location:"header" locationName:"x-amz-object-lock-legal-hold" type:"string" enum:"ObjectLockLegalHoldStatus"`
// The object lock mode that you want to apply to this object.
ObjectLockMode *string `location:"header" locationName:"x-amz-object-lock-mode" type:"string" enum:"ObjectLockMode"`
// The date and time when you want this object's object lock to expire.
ObjectLockRetainUntilDate *time.Time `location:"header" locationName:"x-amz-object-lock-retain-until-date" type:"timestamp" timestampFormat:"iso8601"`
// Confirms that the requester knows that she or he will be charged for the
// request. Bucket owners need not specify this parameter in their requests.
// Documentation on downloading objects from requester pays buckets can be found
// at http://docs.aws.amazon.com/AmazonS3/latest/dev/ObjectsinRequesterPaysBuckets.html
RequestPayer *string `location:"header" locationName:"x-amz-request-payer" type:"string" enum:"RequestPayer"`
// Specifies the algorithm to use to when encrypting the object (e.g., AES256).
SSECustomerAlgorithm *string `location:"header" locationName:"x-amz-server-side-encryption-customer-algorithm" type:"string"`
// Specifies the customer-provided encryption key for Amazon S3 to use in encrypting
// data. This value is used to store the object and then it is discarded; Amazon
// does not store the encryption key. The key must be appropriate for use with
// the algorithm specified in the x-amz-server-side-encryption-customer-algorithm
// header.
SSECustomerKey *string `marshal-as:"blob" location:"header" locationName:"x-amz-server-side-encryption-customer-key" type:"string" sensitive:"true"`
// Specifies the 128-bit MD5 digest of the encryption key according to RFC 1321.
// Amazon S3 uses this header for a message integrity check to ensure the encryption
// key was transmitted without error.
SSECustomerKeyMD5 *string `location:"header" locationName:"x-amz-server-side-encryption-customer-key-MD5" type:"string"`
// Specifies the AWS KMS Encryption Context to use for object encryption. The
// value of this header is a base64-encoded UTF-8 string holding JSON with the
// encryption context key-value pairs.
SSEKMSEncryptionContext *string `location:"header" locationName:"x-amz-server-side-encryption-context" type:"string" sensitive:"true"`
// Specifies the AWS KMS key ID to use for object encryption. All GET and PUT
// requests for an object protected by AWS KMS will fail if not made via SSL
// or using SigV4. Documentation on configuring any of the officially supported
// AWS SDKs and CLI can be found at http://docs.aws.amazon.com/AmazonS3/latest/dev/UsingAWSSDK.html#specify-signature-version
SSEKMSKeyId *string `location:"header" locationName:"x-amz-server-side-encryption-aws-kms-key-id" type:"string" sensitive:"true"`
// The Server-side encryption algorithm used when storing this object in S3
// (e.g., AES256, aws:kms).
ServerSideEncryption *string `location:"header" locationName:"x-amz-server-side-encryption" type:"string" enum:"ServerSideEncryption"`
// The type of storage to use for the object. Defaults to 'STANDARD'.
StorageClass *string `location:"header" locationName:"x-amz-storage-class" type:"string" enum:"StorageClass"`
// The tag-set for the object. The tag-set must be encoded as URL Query parameters.
// (For example, "Key1=Value1")
Tagging *string `location:"header" locationName:"x-amz-tagging" type:"string"`
// If the bucket is configured as a website, redirects requests for this object
// to another object in the same bucket or to an external URL. Amazon S3 stores
// the value of this header in the object metadata.
WebsiteRedirectLocation *string `location:"header" locationName:"x-amz-website-redirect-location" type:"string"`
}
+334 -32
View File
@@ -1,3 +1,5 @@
// +build go1.8
package s3manager_test
import (
@@ -7,12 +9,15 @@ import (
"io/ioutil"
"net/http"
"net/http/httptest"
"os"
"reflect"
"regexp"
"sort"
"strconv"
"strings"
"sync"
"testing"
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
@@ -104,7 +109,7 @@ func TestUploadOrderMulti(t *testing.T) {
resp, err := u.Upload(&s3manager.UploadInput{
Bucket: aws.String("Bucket"),
Key: aws.String("Key"),
Key: aws.String("Key - value"),
Body: bytes.NewReader(buf12MB),
ServerSideEncryption: aws.String("aws:kms"),
SSEKMSKeyId: aws.String("KmsId"),
@@ -120,8 +125,8 @@ func TestUploadOrderMulti(t *testing.T) {
t.Errorf("Expected %v, but received %v", expected, *ops)
}
if "https://location" != resp.Location {
t.Errorf("Expected %q, but received %q", "https://location", resp.Location)
if e, a := `https://s3.mock-region.amazonaws.com/Bucket/Key%20-%20value`, resp.Location; e != a {
t.Errorf("Expected %q, but received %q", e, a)
}
if "UPLOAD-ID" != resp.UploadID {
@@ -129,7 +134,7 @@ func TestUploadOrderMulti(t *testing.T) {
}
if "VERSION-ID" != *resp.VersionID {
t.Errorf("Expected %q, but received %q", "VERSION-ID", resp.VersionID)
t.Errorf("Expected %q, but received %q", "VERSION-ID", *resp.VersionID)
}
// Validate input values
@@ -268,12 +273,12 @@ func TestUploadFailIfPartSizeTooSmall(t *testing.T) {
}
aerr := err.(awserr.Error)
if "ConfigError" != aerr.Code() {
t.Errorf("Expected %q, but received %q", "ConfigError", aerr.Code())
if e, a := "ConfigError", aerr.Code(); e != a {
t.Errorf("Expected %q, but received %q", e, a)
}
if strings.Contains("part size must be at least", aerr.Message()) {
t.Errorf("Expected string to contain %q, but received %q", "part size must be at least", aerr.Message())
if e, a := "part size must be at least", aerr.Message(); !strings.Contains(a, e) {
t.Errorf("expect %v to be in %v", e, a)
}
}
@@ -282,7 +287,7 @@ func TestUploadOrderSingle(t *testing.T) {
mgr := s3manager.NewUploaderWithClient(s)
resp, err := mgr.Upload(&s3manager.UploadInput{
Bucket: aws.String("Bucket"),
Key: aws.String("Key"),
Key: aws.String("Key - value"),
Body: bytes.NewReader(buf2MB),
ServerSideEncryption: aws.String("aws:kms"),
SSEKMSKeyId: aws.String("KmsId"),
@@ -297,12 +302,12 @@ func TestUploadOrderSingle(t *testing.T) {
t.Errorf("Expected %v, but received %v", vals, *ops)
}
if len(resp.Location) == 0 {
t.Error("Expected Location to not be empty")
if e, a := `https://s3.mock-region.amazonaws.com/Bucket/Key%20-%20value`, resp.Location; e != a {
t.Errorf("Expected %q, but received %q", e, a)
}
if e := "VERSION-ID"; e != *resp.VersionID {
t.Errorf("Expected %q, but received %q", e, resp.VersionID)
t.Errorf("Expected %q, but received %q", e, *resp.VersionID)
}
if len(resp.UploadID) > 0 {
@@ -785,53 +790,45 @@ func TestUploadInputS3PutObjectInputPairity(t *testing.T) {
}
type testIncompleteReader struct {
Buf []byte
Count int
Size int64
read int64
}
func (r *testIncompleteReader) Read(p []byte) (n int, err error) {
if r.Count < 0 {
return 0, io.ErrUnexpectedEOF
r.read += int64(len(p))
if r.read >= r.Size {
return int(r.read - r.Size), io.ErrUnexpectedEOF
}
r.Count--
return copy(p, r.Buf), nil
return len(p), nil
}
func TestUploadUnexpectedEOF(t *testing.T) {
s, ops, args := loggingSvc(emptyList)
s, ops, _ := loggingSvc(emptyList)
mgr := s3manager.NewUploaderWithClient(s, func(u *s3manager.Uploader) {
u.Concurrency = 1
u.PartSize = s3manager.MinUploadPartSize
})
_, err := mgr.Upload(&s3manager.UploadInput{
Bucket: aws.String("Bucket"),
Key: aws.String("Key"),
Body: &testIncompleteReader{
Buf: make([]byte, 1024*1024*5),
Count: 1,
Size: int64(s3manager.MinUploadPartSize + 1),
},
})
if err == nil {
t.Error("Expected error, but received none")
}
// Ensure upload started.
if e, a := "CreateMultipartUpload", (*ops)[0]; e != a {
t.Errorf("Expected %q, but received %q", e, a)
}
if e, a := "UploadPart", (*ops)[1]; e != a {
t.Errorf("Expected %q, but received %q", e, a)
}
// Part may or may not be sent because of timing of sending parts and
// reading next part in upload manager. Just check for the last abort.
if e, a := "AbortMultipartUpload", (*ops)[len(*ops)-1]; e != a {
t.Errorf("Expected %q, but received %q", e, a)
}
// Part lengths
if e, a := 1024*1024*5, buflen(val((*args)[1], "Body")); e != a {
t.Errorf("Expected %d, but received %d", e, a)
}
}
func compareStructType(a, b reflect.Type) map[string]int {
@@ -1001,3 +998,308 @@ func TestUploadWithContextCanceled(t *testing.T) {
t.Errorf("expected error message to contain %q, but did not %q", e, a)
}
}
// S3 Uploader incorrectly fails an upload if the content being uploaded
// has a size of MinPartSize * MaxUploadParts.
// Github: aws/aws-sdk-go#2557
func TestUploadMaxPartsEOF(t *testing.T) {
s, ops, _ := loggingSvc(emptyList)
mgr := s3manager.NewUploaderWithClient(s, func(u *s3manager.Uploader) {
u.Concurrency = 1
u.PartSize = s3manager.DefaultUploadPartSize
u.MaxUploadParts = 2
})
f := bytes.NewReader(make([]byte, int(mgr.PartSize)*mgr.MaxUploadParts))
r1 := io.NewSectionReader(f, 0, s3manager.DefaultUploadPartSize)
r2 := io.NewSectionReader(f, s3manager.DefaultUploadPartSize, 2*s3manager.DefaultUploadPartSize)
body := io.MultiReader(r1, r2)
_, err := mgr.Upload(&s3manager.UploadInput{
Bucket: aws.String("Bucket"),
Key: aws.String("Key"),
Body: body,
})
if err != nil {
t.Fatalf("expect no error, got %v", err)
}
expectOps := []string{
"CreateMultipartUpload",
"UploadPart",
"UploadPart",
"CompleteMultipartUpload",
}
if e, a := expectOps, *ops; !reflect.DeepEqual(e, a) {
t.Errorf("expect %v ops, got %v", e, a)
}
}
func createTempFile(t *testing.T, size int64) (*os.File, func(*testing.T), error) {
file, err := ioutil.TempFile(os.TempDir(), aws.SDKName+t.Name())
if err != nil {
return nil, nil, err
}
filename := file.Name()
if err := file.Truncate(size); err != nil {
return nil, nil, err
}
return file,
func(t *testing.T) {
if err := file.Close(); err != nil {
t.Errorf("failed to close temp file, %s, %v", filename, err)
}
if err := os.Remove(filename); err != nil {
t.Errorf("failed to remove temp file, %s, %v", filename, err)
}
},
nil
}
func buildFailHandlers(tb testing.TB, parts, retry int) []http.Handler {
handlers := make([]http.Handler, parts)
for i := 0; i < len(handlers); i++ {
handlers[i] = &failPartHandler{
tb: tb,
failsRemaining: retry,
successHandler: successPartHandler{tb: tb},
}
}
return handlers
}
func TestUploadRetry(t *testing.T) {
const numParts, retries = 3, 10
testFile, testFileCleanup, err := createTempFile(t, s3manager.DefaultUploadPartSize*numParts)
if err != nil {
t.Fatalf("failed to create test file, %v", err)
}
defer testFileCleanup(t)
cases := map[string]struct {
Body io.Reader
PartHandlers func(testing.TB) []http.Handler
}{
"bytes.Buffer": {
Body: bytes.NewBuffer(make([]byte, s3manager.DefaultUploadPartSize*numParts)),
PartHandlers: func(tb testing.TB) []http.Handler {
return buildFailHandlers(tb, numParts, retries)
},
},
"bytes.Reader": {
Body: bytes.NewReader(make([]byte, s3manager.DefaultUploadPartSize*numParts)),
PartHandlers: func(tb testing.TB) []http.Handler {
return buildFailHandlers(tb, numParts, retries)
},
},
"os.File": {
Body: testFile,
PartHandlers: func(tb testing.TB) []http.Handler {
return buildFailHandlers(tb, numParts, retries)
},
},
}
for name, c := range cases {
t.Run(name, func(t *testing.T) {
mux := newMockS3UploadServer(t, c.PartHandlers(t))
server := httptest.NewServer(mux)
defer server.Close()
sess := unit.Session.Copy(&aws.Config{
Endpoint: aws.String(server.URL),
S3ForcePathStyle: aws.Bool(true),
DisableSSL: aws.Bool(true),
Logger: t,
MaxRetries: aws.Int(retries + 1),
SleepDelay: func(time.Duration) {},
LogLevel: aws.LogLevel(
aws.LogDebugWithRequestErrors | aws.LogDebugWithRequestRetries,
),
//Credentials: credentials.AnonymousCredentials,
})
uploader := s3manager.NewUploader(sess, func(u *s3manager.Uploader) {
// u.Concurrency = 1
})
_, err := uploader.Upload(&s3manager.UploadInput{
Bucket: aws.String("bucket"),
Key: aws.String("key"),
Body: c.Body,
})
if err != nil {
t.Fatalf("expect no error, got %v", err)
}
})
}
}
type mockS3UploadServer struct {
*http.ServeMux
tb testing.TB
partHandler []http.Handler
}
func newMockS3UploadServer(tb testing.TB, partHandler []http.Handler) *mockS3UploadServer {
s := &mockS3UploadServer{
ServeMux: http.NewServeMux(),
partHandler: partHandler,
tb: tb,
}
s.HandleFunc("/", s.handleRequest)
return s
}
func (s mockS3UploadServer) handleRequest(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
_, hasUploads := r.URL.Query()["uploads"]
switch {
case r.Method == "POST" && hasUploads:
// CreateMultipartUpload
w.Header().Set("Content-Length", strconv.Itoa(len(createUploadResp)))
w.Write([]byte(createUploadResp))
case r.Method == "PUT":
// UploadPart
partNumStr := r.URL.Query().Get("partNumber")
id, err := strconv.Atoi(partNumStr)
if err != nil {
failRequest(w, 400, "BadRequest",
fmt.Sprintf("unable to parse partNumber, %q, %v",
partNumStr, err))
return
}
id--
if id < 0 || id >= len(s.partHandler) {
failRequest(w, 400, "BadRequest",
fmt.Sprintf("invalid partNumber %v", id))
return
}
s.partHandler[id].ServeHTTP(w, r)
case r.Method == "POST":
// CompleteMultipartUpload
w.Header().Set("Content-Length", strconv.Itoa(len(completeUploadResp)))
w.Write([]byte(completeUploadResp))
case r.Method == "DELETE":
// AbortMultipartUpload
w.Header().Set("Content-Length", strconv.Itoa(len(abortUploadResp)))
w.WriteHeader(200)
w.Write([]byte(abortUploadResp))
default:
failRequest(w, 400, "BadRequest",
fmt.Sprintf("invalid request %v %v", r.Method, r.URL))
}
}
func failRequest(w http.ResponseWriter, status int, code, msg string) {
msg = fmt.Sprintf(baseRequestErrorResp, code, msg)
w.Header().Set("Content-Length", strconv.Itoa(len(msg)))
w.WriteHeader(status)
w.Write([]byte(msg))
}
type successPartHandler struct {
tb testing.TB
}
func (h successPartHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
n, err := io.Copy(ioutil.Discard, r.Body)
if err != nil {
failRequest(w, 400, "BadRequest",
fmt.Sprintf("failed to read body, %v", err))
return
}
contLenStr := r.Header.Get("Content-Length")
expectLen, err := strconv.ParseInt(contLenStr, 10, 64)
if err != nil {
h.tb.Logf("expect content-length, got %q, %v", contLenStr, err)
failRequest(w, 400, "BadRequest",
fmt.Sprintf("unable to get content-length %v", err))
return
}
if e, a := expectLen, n; e != a {
h.tb.Logf("expect %v read, got %v", e, a)
failRequest(w, 400, "BadRequest",
fmt.Sprintf(
"content-length and body do not match, %v, %v", e, a))
return
}
w.Header().Set("Content-Length", strconv.Itoa(len(uploadPartResp)))
w.Write([]byte(uploadPartResp))
}
type failPartHandler struct {
tb testing.TB
failsRemaining int
successHandler http.Handler
}
func (h *failPartHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
if h.failsRemaining == 0 && h.successHandler != nil {
h.successHandler.ServeHTTP(w, r)
return
}
io.Copy(ioutil.Discard, r.Body)
failRequest(w, 500, "InternalException",
fmt.Sprintf("mock error, partNumber %v", r.URL.Query().Get("partNumber")))
h.failsRemaining--
}
const createUploadResp = `
<CreateMultipartUploadResponse>
<Bucket>bucket</Bucket>
<Key>key</Key>
<UploadId>abc123</UploadId>
</CreateMultipartUploadResponse>
`
const uploadPartResp = `
<UploadPartResponse>
<ETag>key</ETag>
</UploadPartResponse>
`
const baseRequestErrorResp = `
<Error>
<Code>%s</Code>
<Message>%s</Message>
<RequestId>request-id</RequestId>
<HostId>host-id</HostId>
</Error>
`
const completeUploadResp = `
<CompleteMultipartUploadResponse>
<Bucket>bucket</Bucket>
<Key>key</Key>
<ETag>key</ETag>
<Location>https://bucket.us-west-2.amazonaws.com/key</Location>
<UploadId>abc123</UploadId>
</CompleteMultipartUploadResponse>
`
const abortUploadResp = `
<AbortMultipartUploadResponse>
</AbortMultipartUploadResponse>
`