diff --git a/.travis.yml b/.travis.yml index ce8630344..8fb856614 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,7 @@ go: - 1.3.3 - 1.4.3 - 1.5.4 - - 1.6.1 + - 1.6.2 os: - linux @@ -33,6 +33,7 @@ install: - export GOBIN="$GOPATH/bin" - export PATH="$PATH:$GOBIN" - go env + - ulimit -n 2048 script: - go run run_integration_tests.go diff --git a/Vagrantfile b/Vagrantfile index 19c73673b..a26aa6b61 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -87,16 +87,16 @@ Vagrant.configure(2) do |config| # fix permissions on synced folder config.vm.provision "fix perms", :type => :shell, :inline => fix_perms - # fix network card - config.vm.provider "virtualbox" do |v| - v.customize ["modifyvm", :id, "--nictype1", "virtio"] - end - config.vm.define "linux" do |b| b.vm.box = "ubuntu/trusty64" b.vm.provision "packages linux", :type => :shell, :inline => packages_linux b.vm.provision "install gimme", :type => :shell, :inline => install_gimme b.vm.provision "prepare user", :type => :shell, :privileged => false, :inline => prepare_user("linux") + + # fix network card + config.vm.provider "virtualbox" do |v| + v.customize ["modifyvm", :id, "--nictype1", "virtio"] + end end config.vm.define "freebsd" do |b| diff --git a/src/restic/backend/s3/s3.go b/src/restic/backend/s3/s3.go index d929d8589..484bfe733 100644 --- a/src/restic/backend/s3/s3.go +++ b/src/restic/backend/s3/s3.go @@ -16,7 +16,7 @@ const connLimit = 10 // s3 is a backend which stores the data on an S3 endpoint. type s3 struct { - client minio.CloudStorageClient + client *minio.Client connChan chan struct{} bucketname string prefix string @@ -39,7 +39,7 @@ func Open(cfg Config) (backend.Backend, error) { debug.Log("s3.Open", "BucketExists(%v) returned err %v, trying to create the bucket", cfg.Bucket, err) // create new bucket with default ACL in default region - err = client.MakeBucket(cfg.Bucket, "", "") + err = client.MakeBucket(cfg.Bucket, "") if err != nil { return nil, err diff --git a/vendor/manifest b/vendor/manifest index ce00ac7fd..1b55eeb43 100644 --- a/vendor/manifest +++ b/vendor/manifest @@ -28,8 +28,8 @@ { "importpath": "github.com/minio/minio-go", "repository": "https://github.com/minio/minio-go", - "revision": "a4cd3caabd5f9c35ac100110eb60c2b80798f1af", - "branch": "HEAD" + "revision": "867b27701ad16db4a9f4dad40d28187ca8433ec9", + "branch": "master" }, { "importpath": "github.com/pkg/sftp", diff --git a/vendor/src/github.com/minio/minio-go/API.md b/vendor/src/github.com/minio/minio-go/API.md new file mode 100644 index 000000000..8e1c82dc9 --- /dev/null +++ b/vendor/src/github.com/minio/minio-go/API.md @@ -0,0 +1,535 @@ +## API Documentation + +### Minio client object creation +Minio client object is created using minio-go: +```go +package main + +import ( + "fmt" + + "github.com/minio/minio-go" +) + +func main() { + s3Client, err := minio.New("s3.amazonaws.com", "YOUR-ACCESSKEYID", "YOUR-SECRETACCESSKEY", false) + if err !!= nil { + fmt.Println(err) + return + } +} +``` + +s3Client can be used to perform operations on S3 storage. APIs are described below. + +### Bucket operations + +* [`MakeBucket`](#MakeBucket) +* [`ListBuckets`](#ListBuckets) +* [`BucketExists`](#BucketExists) +* [`RemoveBucket`](#RemoveBucket) +* [`ListObjects`](#ListObjects) +* [`ListIncompleteUploads`](#ListIncompleteUploads) + +### Object operations + +* [`GetObject`](#GetObject) +* [`PutObject`](#PutObject) +* [`CopyObject`](#CopyObject) +* [`StatObject`](#StatObject) +* [`RemoveObject`](#RemoveObject) +* [`RemoveIncompleteUpload`](#RemoveIncompleteUpload) + +### File operations. + +* [`FPutObject`](#FPutObject) +* [`FGetObject`](#FPutObject) + +### Bucket policy operations. + +* [`SetBucketPolicy`](#SetBucketPolicy) +* [`GetBucketPolicy`](#GetBucketPolicy) +* [`RemoveBucketPolicy`](#RemoveBucketPolicy) + +### Presigned operations + +* [`PresignedGetObject`](#PresignedGetObject) +* [`PresignedPutObject`](#PresignedPutObject) +* [`PresignedPostPolicy`](#PresignedPostPolicy) + +### Bucket operations +--------------------------------------- + +#### MakeBucket(bucketName string, location string) error +Create a new bucket. + +__Parameters__ +* `bucketName` _string_ - Name of the bucket. +* `location` _string_ - region valid values are _us-west-1_, _us-west-2_, _eu-west-1_, _eu-central-1_, _ap-southeast-1_, _ap-northeast-1_, _ap-southeast-2_, _sa-east-1_ + +__Example__ +```go +err := s3Client.MakeBucket("mybucket", "us-west-1") +if err != nil { + fmt.Println(err) + return +} +fmt.Println("Successfully created mybucket.") +``` +--------------------------------------- + +#### ListBuckets() ([]BucketInfo, error) +Lists all buckets. + +`bucketList` lists bucket in the format: +* `bucket.Name` _string_: bucket name +* `bucket.CreationDate` time.Time : date when bucket was created + +__Example__ +```go +buckets, err := s3Client.ListBuckets() +if err != nil { + fmt.Println(err) + return +} +for _, bucket := range buckets { + fmt.Println(bucket) +} +``` +--------------------------------------- + +#### BucketExists(bucketName string) error +Check if bucket exists. + +__Parameters__ +* `bucketName` _string_ : name of the bucket + +__Example__ +```go +err := s3Client.BucketExists("mybucket") +if err != nil { + fmt.Println(err) + return +} +``` +--------------------------------------- + +#### RemoveBucket(bucketName string) error +Remove a bucket. + +__Parameters__ +* `bucketName` _string_ : name of the bucket + +__Example__ +```go +err := s3Client.RemoveBucket("mybucket") +if err != nil { + fmt.Println(err) + return +} +``` +--------------------------------------- + +#### GetBucketPolicy(bucketName string, objectPrefix string) error +Get access permissions on a bucket or a prefix. + +__Parameters__ +* `bucketName` _string_ : name of the bucket +* `objectPrefix` _string_ : name of the object prefix + +__Example__ +```go +bucketPolicy, err := s3Client.GetBucketPolicy("mybucket", "") +if err != nil { + fmt.Println(err) + return +} +fmt.Println("Access permissions for mybucket is", bucketPolicy) +``` +--------------------------------------- + +#### SetBucketPolicy(bucketname string, objectPrefix string, policy BucketPolicy) error +Set access permissions on bucket or an object prefix. + +__Parameters__ +* `bucketName` _string_: name of the bucket +* `objectPrefix` _string_ : name of the object prefix +* `policy` _BucketPolicy_: policy can be _BucketPolicyNone_, _BucketPolicyReadOnly_, _BucketPolicyReadWrite_, _BucketPolicyWriteOnly_ + +__Example__ +```go +err := s3Client.SetBucketPolicy("mybucket", "myprefix", BucketPolicyReadWrite) +if err != nil { + fmt.Println(err) + return +} +``` +--------------------------------------- + +#### RemoveBucketPolicy(bucketname string, objectPrefix string) error +Remove existing permissions on bucket or an object prefix. + +__Parameters__ +* `bucketName` _string_: name of the bucket +* `objectPrefix` _string_ : name of the object prefix + +__Example__ +```go +err := s3Client.RemoveBucketPolicy("mybucket", "myprefix") +if err != nil { + fmt.Println(err) + return +} +``` + +--------------------------------------- + +#### ListObjects(bucketName string, prefix string, recursive bool, doneCh chan struct{}) <-chan ObjectInfo +List objects in a bucket. + +__Parameters__ +* `bucketName` _string_: name of the bucket +* `objectPrefix` _string_: the prefix of the objects that should be listed +* `recursive` _bool_: `true` indicates recursive style listing and `false` indicates directory style listing delimited by '/' +* `doneCh` chan struct{} : channel for pro-actively closing the internal go routine + +__Return Value__ +* `<-chan ObjectInfo` _chan ObjectInfo_: Read channel for all the objects in the bucket, the object is of the format: + * `objectInfo.Key` _string_: name of the object + * `objectInfo.Size` _int64_: size of the object + * `objectInfo.ETag` _string_: etag of the object + * `objectInfo.LastModified` _time.Time_: modified time stamp + +__Example__ +```go +// Create a done channel to control 'ListObjects' go routine. +doneCh := make(chan struct{}) + +// Indicate to our routine to exit cleanly upon return. +defer close(doneCh) + +isRecursive := true +objectCh := s3Client.ListObjects("mybucket", "myprefix", isRecursive, doneCh) +for object := range objectCh { + if object.Err != nil { + fmt.Println(object.Err) + return + } + fmt.Println(object) +} + +``` + +--------------------------------------- + +#### ListIncompleteUploads(bucketName string, prefix string, recursive bool, doneCh chan struct{}) <-chan ObjectMultipartInfo +List partially uploaded objects in a bucket. + +__Parameters__ +* `bucketname` _string_: name of the bucket +* `prefix` _string_: prefix of the object names that are partially uploaded +* `recursive` bool: directory style listing when false, recursive listing when true +* `doneCh` chan struct{} : channel for pro-actively closing the internal go routine + +__Return Value__ +* `<-chan ObjectMultipartInfo` _chan ObjectMultipartInfo_ : emits multipart objects of the format: + * `multiPartObjInfo.Key` _string_: name of the incomplete object + * `multiPartObjInfo.UploadID` _string_: upload ID of the incomplete object + * `multiPartObjInfo.Size` _int64_: size of the incompletely uploaded object + +__Example__ +```go +// Create a done channel to control 'ListObjects' go routine. +doneCh := make(chan struct{}) + +// Indicate to our routine to exit cleanly upon return. +defer close(doneCh) + +isRecursive := true +multiPartObjectCh := s3Client.ListIncompleteUploads("mybucket", "myprefix", isRecursive, doneCh) +for multiPartObject := range multiPartObjectCh { + if multiPartObject.Err != nil { + fmt.Println(multiPartObject.Err) + return + } + fmt.Println(multiPartObject) +} +``` + +--------------------------------------- +### Object operations + +#### GetObject(bucketName string, objectName string) *Object +Download an object. + +__Parameters__ +* `bucketName` _string_: name of the bucket +* `objectName` _string_: name of the object + +__Return Value__ +* `object` _*Object_ : _Object_ represents object reader. + +__Example__ +```go +object, err := s3Client.GetObject("mybucket", "photo.jpg") +if err != nil { + fmt.Println(err) + return +} +localFile _ := os.Open("/tmp/local-file") +if _, err := io.Copy(localFile, object); err != nil { + fmt.Println(err) + return +} +``` +--------------------------------------- +--------------------------------------- + +#### FGetObject(bucketName string, objectName string, filePath string) error +Callback is called with `error` in case of error or `null` in case of success + +__Parameters__ +* `bucketName` _string_: name of the bucket +* `objectName` _string_: name of the object +* `filePath` _string_: path to which the object data will be written to + +__Example__ +```go +err := s3Client.FGetObject("mybucket", "photo.jpg", "/tmp/photo.jpg") +if err != nil { + fmt.Println(err) + return +} +``` +--------------------------------------- + +#### PutObject(bucketName string, objectName string, reader io.Reader, contentType string) (n int, err error) +Upload contents from `io.Reader` to objectName. + +__Parameters__ +* `bucketName` _string_: name of the bucket +* `objectName` _string_: name of the object +* `reader` _io.Reader_: Any golang object implementing io.Reader +* `contentType` _string_: content type of the object. + +__Example__ +```go +file, err := os.Open("my-testfile") +if err != nil { + fmt.Println(err) + return +} +defer file.Close() + +n, err := s3Client.PutObject("my-bucketname", "my-objectname", object, "application/octet-stream") +if err != nil { + fmt.Println(err) + return +} +``` + +--------------------------------------- + +#### CopyObject(bucketName string, objectName string, objectSource string, conditions CopyConditions) error +Copy a source object into a new object with the provided name in the provided bucket. + +__Parameters__ +* `bucketName` _string_: name of the bucket +* `objectName` _string_: name of the object +* `objectSource` _string_: name of the object source. +* `conditions` _CopyConditions_: Collection of supported CopyObject conditions. ['x-amz-copy-source', 'x-amz-copy-source-if-match', 'x-amz-copy-source-if-none-match', 'x-amz-copy-source-if-unmodified-since', 'x-amz-copy-source-if-modified-since'] + +__Example__ +```go +// All following conditions are allowed and can be combined together. + +// Set copy conditions. +var copyConds = minio.NewCopyConditions() +// Set modified condition, copy object modified since 2014 April. +copyConds.SetModified(time.Date(2014, time.April, 0, 0, 0, 0, 0, time.UTC)) + +// Set unmodified condition, copy object unmodified since 2014 April. +// copyConds.SetUnmodified(time.Date(2014, time.April, 0, 0, 0, 0, 0, time.UTC)) + +// Set matching ETag condition, copy object which matches the following ETag. +// copyConds.SetMatchETag("31624deb84149d2f8ef9c385918b653a") + +// Set matching ETag except condition, copy object which does not match the following ETag. +// copyConds.SetMatchETagExcept("31624deb84149d2f8ef9c385918b653a") + +err := s3Client.CopyObject("my-bucketname", "my-objectname", "/my-sourcebucketname/my-sourceobjectname", copyConds) +if err != nil { + fmt.Println(err) + return +} +``` + +--------------------------------------- + +#### FPutObject(bucketName string, objectName string, filePath string, contentType string) error +Uploads the object using contents from a file + +__Parameters__ +* `bucketName` _string_: name of the bucket +* `objectName` _string_: name of the object +* `filePath` _string_: file path of the file to be uploaded +* `contentType` _string_: content type of the object + +__Example__ +```go +n, err := s3Client.FPutObject("my-bucketname", "my-objectname", "/tmp/my-filename.csv", "application/csv") +if err != nil { + fmt.Println(err) + return +} +``` +--------------------------------------- + +#### StatObject(bucketName string, objectName string) (ObjectInfo, error) +Get metadata of an object. + +__Parameters__ +* `bucketName` _string_: name of the bucket +* `objectName` _string_: name of the object + +__Return Value__ + `objInfo` _ObjectInfo_ : object stat info for following format: + * `objInfo.Size` _int64_: size of the object + * `objInfo.ETag` _string_: etag of the object + * `objInfo.ContentType` _string_: Content-Type of the object + * `objInfo.LastModified` _string_: modified time stamp + +__Example__ +```go +objInfo, err := s3Client.StatObject("mybucket", "photo.jpg") +if err != nil { + fmt.Println(err) + return +} +fmt.Println(objInfo) +``` +--------------------------------------- + +#### RemoveObject(bucketName string, objectName string) error +Remove an object. + +__Parameters__ +* `bucketName` _string_: name of the bucket +* `objectName` _string_: name of the object + +__Example__ +```go +err := s3Client.RemoveObject("mybucket", "photo.jpg") +if err != nil { + fmt.Println(err) + return +} +``` +--------------------------------------- + +#### RemoveIncompleteUpload(bucketName string, objectName string) error +Remove an partially uploaded object. + +__Parameters__ +* `bucketName` _string_: name of the bucket +* `objectName` _string_: name of the object + +__Example__ +```go +err := s3Client.RemoveIncompleteUpload("mybucket", "photo.jpg") +if err != nil { + fmt.Println(err) + return +} +``` + +### Presigned operations +--------------------------------------- + +#### PresignedGetObject(bucketName, objectName string, expiry time.Duration, reqParams url.Values) error +Generate a presigned URL for GET. + +__Parameters__ +* `bucketName` _string_: name of the bucket. +* `objectName` _string_: name of the object. +* `expiry` _time.Duration_: expiry in seconds. +* `reqParams` _url.Values_ : additional response header overrides supports _response-expires_, _response-content-type_, _response-cache-control_, _response-content-disposition_ + +__Example__ +```go +// Set request parameters for content-disposition. +reqParams := make(url.Values) +reqParams.Set("response-content-disposition", "attachment; filename=\"your-filename.txt\"") + +// Generates a presigned url which expires in a day. +presignedURL, err := s3Client.PresignedGetObject("mybucket", "photo.jpg", time.Second * 24 * 60 * 60, reqParams) +if err != nil { + fmt.Println(err) + return +} +``` + +--------------------------------------- + +#### PresignedPutObject(bucketName string, objectName string, expiry time.Duration) (string, error) +Generate a presigned URL for PUT. +
+NOTE: you can upload to S3 only with specified object name. +
+ +__Parameters__ +* `bucketName` _string_: name of the bucket +* `objectName` _string_: name of the object +* `expiry` _time.Duration_: expiry in seconds + +__Example__ +```go +// Generates a url which expires in a day. +presignedURL, err := s3Client.PresignedPutObject("mybucket", "photo.jpg", time.Second * 24 * 60 * 60) +if err != nil { + fmt.Println(err) + return +} +``` + +--------------------------------------- +
+#### PresignedPostPolicy(policy PostPolicy) (map[string]string, error) +PresignedPostPolicy we can provide policies specifying conditions restricting +what you want to allow in a POST request, such as bucket name where objects can be +uploaded, key name prefixes that you want to allow for the object being created and more. + +We need to create our policy first: +```go +policy := minio.NewPostPolicy() +``` +Apply upload policy restrictions: +```go +policy.SetBucket("my-bucketname") +policy.SetKey("my-objectname") +policy.SetExpires(time.Now().UTC().AddDate(0, 0, 10)) // expires in 10 days + +// Only allow 'png' images. +policy.SetContentType("image/png") + +// Only allow content size in range 1KB to 1MB. +policy.SetContentLengthRange(1024, 1024*1024) +``` +Get the POST form key/value object: +```go +formData, err := s3Client.PresignedPostPolicy(policy) +if err != nil { + fmt.Println(err) + return +} +``` + +POST your content from the command line using `curl`: +```go +fmt.Printf("curl ") +for k, v := range m { + fmt.Printf("-F %s=%s ", k, v) +} +fmt.Printf("-F file=@/etc/bash.bashrc ") +fmt.Printf("https://my-bucketname.s3.amazonaws.com\n") +``` diff --git a/vendor/src/github.com/minio/minio-go/CONTRIBUTING.md b/vendor/src/github.com/minio/minio-go/CONTRIBUTING.md index b4b224eef..8b1ee86c6 100644 --- a/vendor/src/github.com/minio/minio-go/CONTRIBUTING.md +++ b/vendor/src/github.com/minio/minio-go/CONTRIBUTING.md @@ -15,6 +15,8 @@ - Run `go fmt` - Squash your commits into a single commit. `git rebase -i`. It's okay to force update your pull request. - Make sure `go test -race ./...` and `go build` completes. + NOTE: go test runs functional tests and requires you to have a AWS S3 account. Set them as environment variables + ``ACCESS_KEY`` and ``SECRET_KEY``. To run shorter version of the tests please use ``go test -short -race ./...`` * Read [Effective Go](https://github.com/golang/go/wiki/CodeReviewComments) article from Golang project - `minio-go` project is strictly conformant with Golang style diff --git a/vendor/src/github.com/minio/minio-go/INSTALLGO.md b/vendor/src/github.com/minio/minio-go/INSTALLGO.md index c3762bbfc..81c3d53f5 100644 --- a/vendor/src/github.com/minio/minio-go/INSTALLGO.md +++ b/vendor/src/github.com/minio/minio-go/INSTALLGO.md @@ -71,7 +71,7 @@ export GOROOT=$(brew --prefix)/Cellar/go/${GOVERSION}/libexec export PATH=$PATH:${GOPATH}/bin ``` -##### Source the new enviornment +##### Source the new environment ```sh $ source ~/.bash_profile diff --git a/vendor/src/github.com/minio/minio-go/README.md b/vendor/src/github.com/minio/minio-go/README.md index 8e4da4317..3db4f7b8f 100644 --- a/vendor/src/github.com/minio/minio-go/README.md +++ b/vendor/src/github.com/minio/minio-go/README.md @@ -61,12 +61,14 @@ func main() { ## Documentation +[API documentation](./API.md) + +## Examples + ### Bucket Operations. -* [MakeBucket(bucketName, BucketACL, location) error](examples/s3/makebucket.go) +* [MakeBucket(bucketName, location) error](examples/s3/makebucket.go) * [BucketExists(bucketName) error](examples/s3/bucketexists.go) * [RemoveBucket(bucketName) error](examples/s3/removebucket.go) -* [GetBucketACL(bucketName) (BucketACL, error)](examples/s3/getbucketacl.go) -* [SetBucketACL(bucketName, BucketACL) error)](examples/s3/setbucketacl.go) * [ListBuckets() []BucketInfo](examples/s3/listbuckets.go) * [ListObjects(bucketName, objectPrefix, recursive, chan<- struct{}) <-chan ObjectInfo](examples/s3/listobjects.go) * [ListIncompleteUploads(bucketName, prefix, recursive, chan<- struct{}) <-chan ObjectMultipartInfo](examples/s3/listincompleteuploads.go) @@ -83,10 +85,15 @@ func main() { * [FGetObject(bucketName, objectName, filePath) error](examples/s3/fgetobject.go) ### Presigned Operations. -* [PresignedGetObject(bucketName, objectName, time.Duration) (string, error)](examples/s3/presignedgetobject.go) +* [PresignedGetObject(bucketName, objectName, time.Duration, url.Values) (string, error)](examples/s3/presignedgetobject.go) * [PresignedPutObject(bucketName, objectName, time.Duration) (string, error)](examples/s3/presignedputobject.go) * [PresignedPostPolicy(NewPostPolicy()) (map[string]string, error)](examples/s3/presignedpostpolicy.go) +### Bucket Policy Operations. +* [SetBucketPolicy(bucketName, objectPrefix, BucketPolicy) error](examples/s3/setbucketpolicy.go) +* [GetBucketPolicy(bucketName, objectPrefix) (BucketPolicy, error)](examples/s3/getbucketpolicy.go) +* [RemoveBucketPolicy(bucketName, objectPrefix) error](examples/s3/removebucketpolicy.go) + ### API Reference [![GoDoc](http://img.shields.io/badge/go-documentation-blue.svg?style=flat-square)](http://godoc.org/github.com/minio/minio-go) diff --git a/vendor/src/github.com/minio/minio-go/api-definitions.go b/vendor/src/github.com/minio/minio-go/api-datatypes.go similarity index 100% rename from vendor/src/github.com/minio/minio-go/api-definitions.go rename to vendor/src/github.com/minio/minio-go/api-datatypes.go diff --git a/vendor/src/github.com/minio/minio-go/api-error-response.go b/vendor/src/github.com/minio/minio-go/api-error-response.go index 647165112..e6789aff5 100644 --- a/vendor/src/github.com/minio/minio-go/api-error-response.go +++ b/vendor/src/github.com/minio/minio-go/api-error-response.go @@ -47,7 +47,7 @@ type ErrorResponse struct { // Region where the bucket is located. This header is returned // only in HEAD bucket and ListObjects response. - AmzBucketRegion string + Region string } // ToErrorResponse - Returns parsed ErrorResponse struct from body and @@ -98,65 +98,54 @@ func httpRespToErrorResponse(resp *http.Response, bucketName, objectName string) case http.StatusNotFound: if objectName == "" { errResp = ErrorResponse{ - Code: "NoSuchBucket", - Message: "The specified bucket does not exist.", - BucketName: bucketName, - RequestID: resp.Header.Get("x-amz-request-id"), - HostID: resp.Header.Get("x-amz-id-2"), - AmzBucketRegion: resp.Header.Get("x-amz-bucket-region"), + Code: "NoSuchBucket", + Message: "The specified bucket does not exist.", + BucketName: bucketName, + RequestID: resp.Header.Get("x-amz-request-id"), + HostID: resp.Header.Get("x-amz-id-2"), + Region: resp.Header.Get("x-amz-bucket-region"), } } else { errResp = ErrorResponse{ - Code: "NoSuchKey", - Message: "The specified key does not exist.", - BucketName: bucketName, - Key: objectName, - RequestID: resp.Header.Get("x-amz-request-id"), - HostID: resp.Header.Get("x-amz-id-2"), - AmzBucketRegion: resp.Header.Get("x-amz-bucket-region"), + Code: "NoSuchKey", + Message: "The specified key does not exist.", + BucketName: bucketName, + Key: objectName, + RequestID: resp.Header.Get("x-amz-request-id"), + HostID: resp.Header.Get("x-amz-id-2"), + Region: resp.Header.Get("x-amz-bucket-region"), } } case http.StatusForbidden: errResp = ErrorResponse{ - Code: "AccessDenied", - Message: "Access Denied.", - BucketName: bucketName, - Key: objectName, - RequestID: resp.Header.Get("x-amz-request-id"), - HostID: resp.Header.Get("x-amz-id-2"), - AmzBucketRegion: resp.Header.Get("x-amz-bucket-region"), + Code: "AccessDenied", + Message: "Access Denied.", + BucketName: bucketName, + Key: objectName, + RequestID: resp.Header.Get("x-amz-request-id"), + HostID: resp.Header.Get("x-amz-id-2"), + Region: resp.Header.Get("x-amz-bucket-region"), } case http.StatusConflict: errResp = ErrorResponse{ - Code: "Conflict", - Message: "Bucket not empty.", - BucketName: bucketName, - RequestID: resp.Header.Get("x-amz-request-id"), - HostID: resp.Header.Get("x-amz-id-2"), - AmzBucketRegion: resp.Header.Get("x-amz-bucket-region"), + Code: "Conflict", + Message: "Bucket not empty.", + BucketName: bucketName, + RequestID: resp.Header.Get("x-amz-request-id"), + HostID: resp.Header.Get("x-amz-id-2"), + Region: resp.Header.Get("x-amz-bucket-region"), } default: errResp = ErrorResponse{ - Code: resp.Status, - Message: resp.Status, - BucketName: bucketName, - RequestID: resp.Header.Get("x-amz-request-id"), - HostID: resp.Header.Get("x-amz-id-2"), - AmzBucketRegion: resp.Header.Get("x-amz-bucket-region"), + Code: resp.Status, + Message: resp.Status, + BucketName: bucketName, + RequestID: resp.Header.Get("x-amz-request-id"), + HostID: resp.Header.Get("x-amz-id-2"), + Region: resp.Header.Get("x-amz-bucket-region"), } } } - - // AccessDenied without a signature mismatch code, usually means - // that the bucket policy has certain restrictions where some API - // operations are not allowed. Handle this case so that top level - // callers can interpret this easily and fall back if needed to a - // lower functionality call. Read each individual API specific - // code for such fallbacks. - if errResp.Code == "AccessDenied" && errResp.Message == "Access Denied" { - errResp.Code = "NotImplemented" - errResp.Message = "Operation is not allowed according to your bucket policy." - } return errResp } diff --git a/vendor/src/github.com/minio/minio-go/api-error-response_test.go b/vendor/src/github.com/minio/minio-go/api-error-response_test.go new file mode 100644 index 000000000..a4e5bdc0f --- /dev/null +++ b/vendor/src/github.com/minio/minio-go/api-error-response_test.go @@ -0,0 +1,277 @@ +/* + * Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015 Minio, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required bZy applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package minio + +import ( + "bytes" + "encoding/xml" + "fmt" + "io/ioutil" + "net/http" + "reflect" + "strconv" + "testing" +) + +// Tests validate the Error generator function for http response with error. +func TestHttpRespToErrorResponse(t *testing.T) { + // 'genAPIErrorResponse' generates ErrorResponse for given APIError. + // provides a encodable populated response values. + genAPIErrorResponse := func(err APIError, bucketName string) ErrorResponse { + var errResp = ErrorResponse{} + errResp.Code = err.Code + errResp.Message = err.Description + errResp.BucketName = bucketName + return errResp + } + + // Encodes the response headers into XML format. + encodeErr := func(response interface{}) []byte { + var bytesBuffer bytes.Buffer + bytesBuffer.WriteString(xml.Header) + encode := xml.NewEncoder(&bytesBuffer) + encode.Encode(response) + return bytesBuffer.Bytes() + } + + // `createAPIErrorResponse` Mocks XML error response from the server. + createAPIErrorResponse := func(APIErr APIError, bucketName string) *http.Response { + // generate error response. + // response body contains the XML error message. + resp := &http.Response{} + errorResponse := genAPIErrorResponse(APIErr, bucketName) + encodedErrorResponse := encodeErr(errorResponse) + // write Header. + resp.StatusCode = APIErr.HTTPStatusCode + resp.Body = ioutil.NopCloser(bytes.NewBuffer(encodedErrorResponse)) + + return resp + } + + // 'genErrResponse' contructs error response based http Status Code + genErrResponse := func(resp *http.Response, code, message, bucketName, objectName string) ErrorResponse { + errResp := ErrorResponse{ + Code: code, + Message: message, + BucketName: bucketName, + Key: objectName, + RequestID: resp.Header.Get("x-amz-request-id"), + HostID: resp.Header.Get("x-amz-id-2"), + Region: resp.Header.Get("x-amz-bucket-region"), + } + return errResp + } + + // Generate invalid argument error. + genInvalidError := func(message string) error { + errResp := ErrorResponse{ + Code: "InvalidArgument", + Message: message, + RequestID: "minio", + } + return errResp + } + + // Set common http response headers. + setCommonHeaders := func(resp *http.Response) *http.Response { + // set headers. + resp.Header = make(http.Header) + resp.Header.Set("x-amz-request-id", "xyz") + resp.Header.Set("x-amz-id-2", "abc") + resp.Header.Set("x-amz-bucket-region", "us-east-1") + return resp + } + + // Generate http response with empty body. + // Set the StatusCode to the arugment supplied. + // Sets common headers. + genEmptyBodyResponse := func(statusCode int) *http.Response { + resp := &http.Response{} + // set empty response body. + resp.Body = ioutil.NopCloser(bytes.NewBuffer([]byte(""))) + // set headers. + setCommonHeaders(resp) + // set status code. + resp.StatusCode = statusCode + return resp + } + + // Decode XML error message from the http response body. + decodeXMLError := func(resp *http.Response, t *testing.T) error { + var errResp ErrorResponse + err := xmlDecoder(resp.Body, &errResp) + if err != nil { + t.Fatal("XML decoding of response body failed") + } + return errResp + } + + // List of APIErrors used to generate/mock server side XML error response. + APIErrors := []APIError{ + { + Code: "NoSuchBucketPolicy", + Description: "The specified bucket does not have a bucket policy.", + HTTPStatusCode: http.StatusNotFound, + }, + } + + // List of expected response. + // Used for asserting the actual response. + expectedErrResponse := []error{ + genInvalidError("Response is empty. " + "Please report this issue at https://github.com/minio/minio-go/issues."), + decodeXMLError(createAPIErrorResponse(APIErrors[0], "minio-bucket"), t), + genErrResponse(setCommonHeaders(&http.Response{}), "NoSuchBucket", "The specified bucket does not exist.", "minio-bucket", ""), + genErrResponse(setCommonHeaders(&http.Response{}), "NoSuchKey", "The specified key does not exist.", "minio-bucket", "Asia/"), + genErrResponse(setCommonHeaders(&http.Response{}), "AccessDenied", "Access Denied.", "minio-bucket", ""), + genErrResponse(setCommonHeaders(&http.Response{}), "Conflict", "Bucket not empty.", "minio-bucket", ""), + genErrResponse(setCommonHeaders(&http.Response{}), "Bad Request", "Bad Request", "minio-bucket", ""), + } + + // List of http response to be used as input. + inputResponses := []*http.Response{ + nil, + createAPIErrorResponse(APIErrors[0], "minio-bucket"), + genEmptyBodyResponse(http.StatusNotFound), + genEmptyBodyResponse(http.StatusNotFound), + genEmptyBodyResponse(http.StatusForbidden), + genEmptyBodyResponse(http.StatusConflict), + genEmptyBodyResponse(http.StatusBadRequest), + } + + testCases := []struct { + bucketName string + objectName string + inputHTTPResp *http.Response + // expected results. + expectedResult error + // flag indicating whether tests should pass. + + }{ + {"minio-bucket", "", inputResponses[0], expectedErrResponse[0]}, + {"minio-bucket", "", inputResponses[1], expectedErrResponse[1]}, + {"minio-bucket", "", inputResponses[2], expectedErrResponse[2]}, + {"minio-bucket", "Asia/", inputResponses[3], expectedErrResponse[3]}, + {"minio-bucket", "", inputResponses[4], expectedErrResponse[4]}, + {"minio-bucket", "", inputResponses[5], expectedErrResponse[5]}, + } + + for i, testCase := range testCases { + actualResult := httpRespToErrorResponse(testCase.inputHTTPResp, testCase.bucketName, testCase.objectName) + if !reflect.DeepEqual(testCase.expectedResult, actualResult) { + t.Errorf("Test %d: Expected result to be '%+v', but instead got '%+v'", i+1, testCase.expectedResult, actualResult) + } + } +} + +// Test validates 'ErrEntityTooLarge' error response. +func TestErrEntityTooLarge(t *testing.T) { + msg := fmt.Sprintf("Your proposed upload size ‘%d’ exceeds the maximum allowed object size ‘%d’ for single PUT operation.", 1000000, 99999) + expectedResult := ErrorResponse{ + Code: "EntityTooLarge", + Message: msg, + BucketName: "minio-bucket", + Key: "Asia/", + } + actualResult := ErrEntityTooLarge(1000000, 99999, "minio-bucket", "Asia/") + if !reflect.DeepEqual(expectedResult, actualResult) { + t.Errorf("Expected result to be '%+v', but instead got '%+v'", expectedResult, actualResult) + } +} + +// Test validates 'ErrEntityTooSmall' error response. +func TestErrEntityTooSmall(t *testing.T) { + msg := fmt.Sprintf("Your proposed upload size ‘%d’ is below the minimum allowed object size '0B' for single PUT operation.", -1) + expectedResult := ErrorResponse{ + Code: "EntityTooLarge", + Message: msg, + BucketName: "minio-bucket", + Key: "Asia/", + } + actualResult := ErrEntityTooSmall(-1, "minio-bucket", "Asia/") + if !reflect.DeepEqual(expectedResult, actualResult) { + t.Errorf("Expected result to be '%+v', but instead got '%+v'", expectedResult, actualResult) + } +} + +// Test validates 'ErrUnexpectedEOF' error response. +func TestErrUnexpectedEOF(t *testing.T) { + msg := fmt.Sprintf("Data read ‘%s’ is not equal to the size ‘%s’ of the input Reader.", + strconv.FormatInt(100, 10), strconv.FormatInt(101, 10)) + expectedResult := ErrorResponse{ + Code: "UnexpectedEOF", + Message: msg, + BucketName: "minio-bucket", + Key: "Asia/", + } + actualResult := ErrUnexpectedEOF(100, 101, "minio-bucket", "Asia/") + if !reflect.DeepEqual(expectedResult, actualResult) { + t.Errorf("Expected result to be '%+v', but instead got '%+v'", expectedResult, actualResult) + } +} + +// Test validates 'ErrInvalidBucketName' error response. +func TestErrInvalidBucketName(t *testing.T) { + expectedResult := ErrorResponse{ + Code: "InvalidBucketName", + Message: "Invalid Bucket name", + RequestID: "minio", + } + actualResult := ErrInvalidBucketName("Invalid Bucket name") + if !reflect.DeepEqual(expectedResult, actualResult) { + t.Errorf("Expected result to be '%+v', but instead got '%+v'", expectedResult, actualResult) + } +} + +// Test validates 'ErrInvalidObjectName' error response. +func TestErrInvalidObjectName(t *testing.T) { + expectedResult := ErrorResponse{ + Code: "NoSuchKey", + Message: "Invalid Object Key", + RequestID: "minio", + } + actualResult := ErrInvalidObjectName("Invalid Object Key") + if !reflect.DeepEqual(expectedResult, actualResult) { + t.Errorf("Expected result to be '%+v', but instead got '%+v'", expectedResult, actualResult) + } +} + +// Test validates 'ErrInvalidParts' error response. +func TestErrInvalidParts(t *testing.T) { + msg := fmt.Sprintf("Unexpected number of parts found Want %d, Got %d", 10, 9) + expectedResult := ErrorResponse{ + Code: "InvalidParts", + Message: msg, + RequestID: "minio", + } + actualResult := ErrInvalidParts(10, 9) + if !reflect.DeepEqual(expectedResult, actualResult) { + t.Errorf("Expected result to be '%+v', but instead got '%+v'", expectedResult, actualResult) + } +} + +// Test validates 'ErrInvalidArgument' response. +func TestErrInvalidArgument(t *testing.T) { + expectedResult := ErrorResponse{ + Code: "InvalidArgument", + Message: "Invalid Argument", + RequestID: "minio", + } + actualResult := ErrInvalidArgument("Invalid Argument") + if !reflect.DeepEqual(expectedResult, actualResult) { + t.Errorf("Expected result to be '%+v', but instead got '%+v'", expectedResult, actualResult) + } +} diff --git a/vendor/src/github.com/minio/minio-go/api-get.go b/vendor/src/github.com/minio/minio-go/api-get-object.go similarity index 69% rename from vendor/src/github.com/minio/minio-go/api-get.go rename to vendor/src/github.com/minio/minio-go/api-get-object.go index 56d44c9f5..b5b74ff72 100644 --- a/vendor/src/github.com/minio/minio-go/api-get.go +++ b/vendor/src/github.com/minio/minio-go/api-get-object.go @@ -1,5 +1,5 @@ /* - * Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015 Minio, Inc. + * Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015, 2016 Minio, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,124 +20,12 @@ import ( "errors" "fmt" "io" - "math" "net/http" - "net/url" "strings" "sync" "time" ) -// GetBucketACL - Get the permissions on an existing bucket. -// -// Returned values are: -// -// private - Owner gets full access. -// public-read - Owner gets full access, others get read access. -// public-read-write - Owner gets full access, others get full access -// too. -// authenticated-read - Owner gets full access, authenticated users -// get read access. -func (c Client) GetBucketACL(bucketName string) (BucketACL, error) { - // Input validation. - if err := isValidBucketName(bucketName); err != nil { - return "", err - } - - // Set acl query. - urlValues := make(url.Values) - urlValues.Set("acl", "") - - // Instantiate a new request. - req, err := c.newRequest("GET", requestMetadata{ - bucketName: bucketName, - queryValues: urlValues, - }) - if err != nil { - return "", err - } - - // Initiate the request. - resp, err := c.do(req) - defer closeResponse(resp) - if err != nil { - return "", err - } - if resp != nil { - if resp.StatusCode != http.StatusOK { - return "", httpRespToErrorResponse(resp, bucketName, "") - } - } - - // Decode access control policy. - policy := accessControlPolicy{} - err = xmlDecoder(resp.Body, &policy) - if err != nil { - return "", err - } - - // We need to avoid following de-serialization check for Google - // Cloud Storage. On Google Cloud Storage "private" canned ACL's - // policy do not have grant list. Treat it as a valid case, check - // for all other vendors. - if !isGoogleEndpoint(c.endpointURL) { - if policy.AccessControlList.Grant == nil { - errorResponse := ErrorResponse{ - Code: "InternalError", - Message: "Access control Grant list is empty. " + reportIssue, - BucketName: bucketName, - RequestID: resp.Header.Get("x-amz-request-id"), - HostID: resp.Header.Get("x-amz-id-2"), - AmzBucketRegion: resp.Header.Get("x-amz-bucket-region"), - } - return "", errorResponse - } - } - - // Boolean cues to indentify right canned acls. - var publicRead, publicWrite, authenticatedRead bool - - // Handle grants. - grants := policy.AccessControlList.Grant - for _, g := range grants { - if g.Grantee.URI == "" && g.Permission == "FULL_CONTROL" { - continue - } - if g.Grantee.URI == "http://acs.amazonaws.com/groups/global/AuthenticatedUsers" && g.Permission == "READ" { - authenticatedRead = true - break - } else if g.Grantee.URI == "http://acs.amazonaws.com/groups/global/AllUsers" && g.Permission == "WRITE" { - publicWrite = true - } else if g.Grantee.URI == "http://acs.amazonaws.com/groups/global/AllUsers" && g.Permission == "READ" { - publicRead = true - } - } - - // Verify if acl is authenticated read. - if authenticatedRead { - return BucketACL("authenticated-read"), nil - } - // Verify if acl is private. - if !publicWrite && !publicRead { - return BucketACL("private"), nil - } - // Verify if acl is public-read. - if !publicWrite && publicRead { - return BucketACL("public-read"), nil - } - // Verify if acl is public-read-write. - if publicRead && publicWrite { - return BucketACL("public-read-write"), nil - } - - return "", ErrorResponse{ - Code: "NoSuchBucketPolicy", - Message: "The specified bucket does not have a bucket policy.", - BucketName: bucketName, - RequestID: "minio", - } -} - // GetObject - returns an seekable, readable object. func (c Client) GetObject(bucketName, objectName string) (*Object, error) { // Input validation. @@ -147,8 +35,9 @@ func (c Client) GetObject(bucketName, objectName string) (*Object, error) { if err := isValidObjectName(objectName); err != nil { return nil, err } - // Send an explicit info to get the actual object size. - objectInfo, err := c.StatObject(bucketName, objectName) + + // Start the request as soon Get is initiated. + httpReader, objectInfo, err := c.getObject(bucketName, objectName, 0, 0) if err != nil { return nil, err } @@ -160,8 +49,7 @@ func (c Client) GetObject(bucketName, objectName string) (*Object, error) { // Create done channel. doneCh := make(chan struct{}) - // This routine feeds partial object data as and when the caller - // reads. + // This routine feeds partial object data as and when the caller reads. go func() { defer close(reqCh) defer close(resCh) @@ -174,23 +62,27 @@ func (c Client) GetObject(bucketName, objectName string) (*Object, error) { return // Request message. case req := <-reqCh: - // Get shortest length. - // NOTE: Last remaining bytes are usually smaller than - // req.Buffer size. Use that as the final length. - length := math.Min(float64(len(req.Buffer)), float64(objectInfo.Size-req.Offset)) - httpReader, _, err := c.getObject(bucketName, objectName, req.Offset, int64(length)) - if err != nil { - resCh <- readResponse{ - Error: err, + // Offset changes fetch the new object at an Offset. + if req.DidOffsetChange { + // Read from offset. + httpReader, _, err = c.getObject(bucketName, objectName, req.Offset, 0) + if err != nil { + resCh <- readResponse{ + Error: err, + } + return } - return } + + // Read at least req.Buffer bytes, if not we have + // reached our EOF. size, err := io.ReadFull(httpReader, req.Buffer) if err == io.ErrUnexpectedEOF { // If an EOF happens after reading some but not // all the bytes ReadFull returns ErrUnexpectedEOF err = io.EOF } + // Reply back how much was read. resCh <- readResponse{ Size: int(size), Error: err, @@ -211,8 +103,9 @@ type readResponse struct { // Read request message container to communicate with internal // go-routine. type readRequest struct { - Buffer []byte - Offset int64 // readAt offset. + Buffer []byte + Offset int64 // readAt offset. + DidOffsetChange bool } // Object represents an open object. It implements Read, ReadAt, @@ -225,6 +118,7 @@ type Object struct { reqCh chan<- readRequest resCh <-chan readResponse doneCh chan<- struct{} + prevOffset int64 currOffset int64 objectInfo ObjectInfo @@ -247,7 +141,7 @@ func (o *Object) Read(b []byte) (n int, err error) { o.mutex.Lock() defer o.mutex.Unlock() - // Previous prevErr is which was saved in previous operation. + // prevErr is previous error saved from previous operation. if o.prevErr != nil || o.isClosed { return 0, o.prevErr } @@ -257,13 +151,27 @@ func (o *Object) Read(b []byte) (n int, err error) { return 0, io.EOF } - // Send current information over control channel to indicate we - // are ready. + // Send current information over control channel to indicate we are ready. reqMsg := readRequest{} - - // Send the offset and pointer to the buffer over the channel. + // Send the pointer to the buffer over the channel. reqMsg.Buffer = b - reqMsg.Offset = o.currOffset + + // Verify if offset has changed and currOffset is greater than + // previous offset. Perhaps due to Seek(). + offsetChange := o.prevOffset - o.currOffset + if offsetChange < 0 { + offsetChange = -offsetChange + } + if offsetChange > 0 { + // Fetch the new reader at the current offset again. + reqMsg.Offset = o.currOffset + reqMsg.DidOffsetChange = true + } else { + // No offset changes no need to fetch new reader, continue + // reading. + reqMsg.DidOffsetChange = false + reqMsg.Offset = 0 + } // Send read request over the control channel. o.reqCh <- reqMsg @@ -277,6 +185,9 @@ func (o *Object) Read(b []byte) (n int, err error) { // Update current offset. o.currOffset += bytesRead + // Save the current offset as previous offset. + o.prevOffset = o.currOffset + if dataMsg.Error == nil { // If currOffset read is equal to objectSize // We have reached end of file, we return io.EOF. @@ -320,7 +231,7 @@ func (o *Object) ReadAt(b []byte, offset int64) (n int, err error) { o.mutex.Lock() defer o.mutex.Unlock() - // prevErr is which was saved in previous operation. + // prevErr is error which was saved in previous operation. if o.prevErr != nil || o.isClosed { return 0, o.prevErr } @@ -337,7 +248,16 @@ func (o *Object) ReadAt(b []byte, offset int64) (n int, err error) { // Send the offset and pointer to the buffer over the channel. reqMsg.Buffer = b - reqMsg.Offset = offset + + // For ReadAt offset always changes, minor optimization where + // offset same as currOffset we don't change the offset. + reqMsg.DidOffsetChange = offset != o.currOffset + if reqMsg.DidOffsetChange { + // Set new offset. + reqMsg.Offset = offset + // Save new offset as current offset. + o.currOffset = offset + } // Send read request over the control channel. o.reqCh <- reqMsg @@ -348,10 +268,16 @@ func (o *Object) ReadAt(b []byte, offset int64) (n int, err error) { // Bytes read. bytesRead := int64(dataMsg.Size) + // Update current offset. + o.currOffset += bytesRead + + // Save current offset as previous offset before returning. + o.prevOffset = o.currOffset + if dataMsg.Error == nil { - // If offset+bytes read is equal to objectSize + // If currentOffset is equal to objectSize // we have reached end of file, we return io.EOF. - if offset+bytesRead == o.objectInfo.Size { + if o.currOffset >= o.objectInfo.Size { return dataMsg.Size, io.EOF } return dataMsg.Size, nil @@ -381,7 +307,7 @@ func (o *Object) Seek(offset int64, whence int) (n int64, err error) { defer o.mutex.Unlock() if o.prevErr != nil { - // At EOF seeking is legal, for any other errors we return. + // At EOF seeking is legal allow only io.EOF, for any other errors we return. if o.prevErr != io.EOF { return 0, o.prevErr } @@ -391,6 +317,11 @@ func (o *Object) Seek(offset int64, whence int) (n int64, err error) { if offset < 0 && whence != 2 { return 0, ErrInvalidArgument(fmt.Sprintf("Negative position not allowed for %d.", whence)) } + + // Save current offset as previous offset. + o.prevOffset = o.currOffset + + // Switch through whence. switch whence { default: return 0, ErrInvalidArgument(fmt.Sprintf("Invalid whence %d", whence)) @@ -484,8 +415,8 @@ func (c Client) getObject(bucketName, objectName string, offset, length int64) ( customHeader.Set("Range", fmt.Sprintf("bytes=%d", length)) } - // Instantiate a new request. - req, err := c.newRequest("GET", requestMetadata{ + // Execute GET on objectName. + resp, err := c.executeMethod("GET", requestMetadata{ bucketName: bucketName, objectName: objectName, customHeader: customHeader, @@ -493,11 +424,6 @@ func (c Client) getObject(bucketName, objectName string, offset, length int64) ( if err != nil { return nil, ObjectInfo{}, err } - // Execute the request. - resp, err := c.do(req) - if err != nil { - return nil, ObjectInfo{}, err - } if resp != nil { if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusPartialContent { return nil, ObjectInfo{}, httpRespToErrorResponse(resp, bucketName, objectName) @@ -513,11 +439,11 @@ func (c Client) getObject(bucketName, objectName string, offset, length int64) ( if err != nil { msg := "Last-Modified time format not recognized. " + reportIssue return nil, ObjectInfo{}, ErrorResponse{ - Code: "InternalError", - Message: msg, - RequestID: resp.Header.Get("x-amz-request-id"), - HostID: resp.Header.Get("x-amz-id-2"), - AmzBucketRegion: resp.Header.Get("x-amz-bucket-region"), + Code: "InternalError", + Message: msg, + RequestID: resp.Header.Get("x-amz-request-id"), + HostID: resp.Header.Get("x-amz-id-2"), + Region: resp.Header.Get("x-amz-bucket-region"), } } // Get content-type. diff --git a/vendor/src/github.com/minio/minio-go/api-get-policy.go b/vendor/src/github.com/minio/minio-go/api-get-policy.go new file mode 100644 index 000000000..1004461b2 --- /dev/null +++ b/vendor/src/github.com/minio/minio-go/api-get-policy.go @@ -0,0 +1,92 @@ +/* + * Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015, 2016 Minio, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package minio + +import ( + "io" + "io/ioutil" + "net/http" + "net/url" + "sort" +) + +// GetBucketPolicy - get bucket policy at a given path. +func (c Client) GetBucketPolicy(bucketName, objectPrefix string) (bucketPolicy BucketPolicy, err error) { + // Input validation. + if err := isValidBucketName(bucketName); err != nil { + return BucketPolicyNone, err + } + if err := isValidObjectPrefix(objectPrefix); err != nil { + return BucketPolicyNone, err + } + policy, err := c.getBucketPolicy(bucketName, objectPrefix) + if err != nil { + return BucketPolicyNone, err + } + return identifyPolicyType(policy, bucketName, objectPrefix), nil +} + +// Request server for policy. +func (c Client) getBucketPolicy(bucketName string, objectPrefix string) (BucketAccessPolicy, error) { + // Get resources properly escaped and lined up before + // using them in http request. + urlValues := make(url.Values) + urlValues.Set("policy", "") + + // Execute GET on bucket to list objects. + resp, err := c.executeMethod("GET", requestMetadata{ + bucketName: bucketName, + queryValues: urlValues, + }) + + defer closeResponse(resp) + if err != nil { + return BucketAccessPolicy{}, err + } + return processBucketPolicyResponse(bucketName, resp) + +} + +// processes the GetPolicy http response from the server. +func processBucketPolicyResponse(bucketName string, resp *http.Response) (BucketAccessPolicy, error) { + if resp != nil { + if resp.StatusCode != http.StatusOK { + errResponse := httpRespToErrorResponse(resp, bucketName, "") + if ToErrorResponse(errResponse).Code == "NoSuchBucketPolicy" { + return BucketAccessPolicy{Version: "2012-10-17"}, nil + } + return BucketAccessPolicy{}, errResponse + } + } + // Read access policy up to maxAccessPolicySize. + // http://docs.aws.amazon.com/AmazonS3/latest/dev/access-policy-language-overview.html + // bucket policies are limited to 20KB in size, using a limit reader. + bucketPolicyBuf, err := ioutil.ReadAll(io.LimitReader(resp.Body, maxAccessPolicySize)) + if err != nil { + return BucketAccessPolicy{}, err + } + policy, err := unMarshalBucketPolicy(bucketPolicyBuf) + if err != nil { + return BucketAccessPolicy{}, err + } + // Sort the policy actions and resources for convenience. + for _, statement := range policy.Statements { + sort.Strings(statement.Actions) + sort.Strings(statement.Resources) + } + return policy, nil +} diff --git a/vendor/src/github.com/minio/minio-go/api-get-policy_test.go b/vendor/src/github.com/minio/minio-go/api-get-policy_test.go new file mode 100644 index 000000000..a15f53577 --- /dev/null +++ b/vendor/src/github.com/minio/minio-go/api-get-policy_test.go @@ -0,0 +1,102 @@ +/* + * Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015, 2016 Minio, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package minio + +import ( + "bytes" + "encoding/json" + "io/ioutil" + "net/http" + "reflect" + "testing" +) + +// Mocks valid http response containing bucket policy from server. +func generatePolicyResponse(resp *http.Response, policy BucketAccessPolicy) (*http.Response, error) { + policyBytes, err := json.Marshal(policy) + if err != nil { + return nil, err + } + resp.StatusCode = http.StatusOK + resp.Body = ioutil.NopCloser(bytes.NewBuffer(policyBytes)) + return resp, nil +} + +// Tests the processing of GetPolicy response from server. +func TestProcessBucketPolicyResopnse(t *testing.T) { + bucketAccesPolicies := []BucketAccessPolicy{ + {Version: "1.0"}, + {Version: "1.0", Statements: setReadOnlyStatement("minio-bucket", "")}, + {Version: "1.0", Statements: setReadWriteStatement("minio-bucket", "Asia/")}, + {Version: "1.0", Statements: setWriteOnlyStatement("minio-bucket", "Asia/India/")}, + } + + APIErrors := []APIError{ + { + Code: "NoSuchBucketPolicy", + Description: "The specified bucket does not have a bucket policy.", + HTTPStatusCode: http.StatusNotFound, + }, + } + testCases := []struct { + bucketName string + isAPIError bool + apiErr APIError + // expected results. + expectedResult BucketAccessPolicy + err error + // flag indicating whether tests should pass. + shouldPass bool + }{ + {"my-bucket", true, APIErrors[0], BucketAccessPolicy{Version: "2012-10-17"}, nil, true}, + {"my-bucket", false, APIError{}, bucketAccesPolicies[0], nil, true}, + {"my-bucket", false, APIError{}, bucketAccesPolicies[1], nil, true}, + {"my-bucket", false, APIError{}, bucketAccesPolicies[2], nil, true}, + {"my-bucket", false, APIError{}, bucketAccesPolicies[3], nil, true}, + } + + for i, testCase := range testCases { + inputResponse := &http.Response{} + var err error + if testCase.isAPIError { + inputResponse = generateErrorResponse(inputResponse, testCase.apiErr, testCase.bucketName) + } else { + inputResponse, err = generatePolicyResponse(inputResponse, testCase.expectedResult) + if err != nil { + t.Fatalf("Test %d: Creation of valid response failed", i+1) + } + } + actualResult, err := processBucketPolicyResponse("my-bucket", inputResponse) + if err != nil && testCase.shouldPass { + t.Errorf("Test %d: Expected to pass, but failed with: %s", i+1, err.Error()) + } + if err == nil && !testCase.shouldPass { + t.Errorf("Test %d: Expected to fail with \"%s\", but passed instead", i+1, testCase.err.Error()) + } + // Failed as expected, but does it fail for the expected reason. + if err != nil && !testCase.shouldPass { + if err.Error() != testCase.err.Error() { + t.Errorf("Test %d: Expected to fail with error \"%s\", but instead failed with error \"%s\" instead", i+1, testCase.err.Error(), err.Error()) + } + } + if err == nil && testCase.shouldPass { + if !reflect.DeepEqual(testCase.expectedResult, actualResult) { + t.Errorf("Test %d: The expected BucketPolicy doesnt match the actual BucketPolicy", i+1) + } + } + } +} diff --git a/vendor/src/github.com/minio/minio-go/api-list.go b/vendor/src/github.com/minio/minio-go/api-list.go index 534ac4eb4..e61763db9 100644 --- a/vendor/src/github.com/minio/minio-go/api-list.go +++ b/vendor/src/github.com/minio/minio-go/api-list.go @@ -1,5 +1,5 @@ /* - * Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015 Minio, Inc. + * Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015, 2016 Minio, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,13 +34,8 @@ import ( // } // func (c Client) ListBuckets() ([]BucketInfo, error) { - // Instantiate a new request. - req, err := c.newRequest("GET", requestMetadata{}) - if err != nil { - return nil, err - } - // Initiate the request. - resp, err := c.do(req) + // Execute GET on service. + resp, err := c.executeMethod("GET", requestMetadata{}) defer closeResponse(resp) if err != nil { return nil, err @@ -82,7 +77,7 @@ func (c Client) ListBuckets() ([]BucketInfo, error) { // func (c Client) ListObjects(bucketName, objectPrefix string, recursive bool, doneCh <-chan struct{}) <-chan ObjectInfo { // Allocate new list objects channel. - objectStatCh := make(chan ObjectInfo, 1000) + objectStatCh := make(chan ObjectInfo) // Default listing is delimited at "/" delimiter := "/" if recursive { @@ -188,11 +183,11 @@ func (c Client) listObjectsQuery(bucketName, objectPrefix, objectMarker, delimit urlValues := make(url.Values) // Set object prefix. if objectPrefix != "" { - urlValues.Set("prefix", urlEncodePath(objectPrefix)) + urlValues.Set("prefix", objectPrefix) } // Set object marker. if objectMarker != "" { - urlValues.Set("marker", urlEncodePath(objectMarker)) + urlValues.Set("marker", objectMarker) } // Set delimiter. if delimiter != "" { @@ -206,16 +201,11 @@ func (c Client) listObjectsQuery(bucketName, objectPrefix, objectMarker, delimit // Set max keys. urlValues.Set("max-keys", fmt.Sprintf("%d", maxkeys)) - // Initialize a new request. - req, err := c.newRequest("GET", requestMetadata{ + // Execute GET on bucket to list objects. + resp, err := c.executeMethod("GET", requestMetadata{ bucketName: bucketName, queryValues: urlValues, }) - if err != nil { - return listBucketResult{}, err - } - // Execute list buckets. - resp, err := c.do(req) defer closeResponse(resp) if err != nil { return listBucketResult{}, err @@ -264,7 +254,7 @@ func (c Client) ListIncompleteUploads(bucketName, objectPrefix string, recursive // listIncompleteUploads lists all incomplete uploads. func (c Client) listIncompleteUploads(bucketName, objectPrefix string, recursive, aggregateSize bool, doneCh <-chan struct{}) <-chan ObjectMultipartInfo { // Allocate channel for multipart uploads. - objectMultipartStatCh := make(chan ObjectMultipartInfo, 1000) + objectMultipartStatCh := make(chan ObjectMultipartInfo) // Delimiter is set to "/" by default. delimiter := "/" if recursive { @@ -366,7 +356,7 @@ func (c Client) listMultipartUploadsQuery(bucketName, keyMarker, uploadIDMarker, urlValues.Set("uploads", "") // Set object key marker. if keyMarker != "" { - urlValues.Set("key-marker", urlEncodePath(keyMarker)) + urlValues.Set("key-marker", keyMarker) } // Set upload id marker. if uploadIDMarker != "" { @@ -374,7 +364,7 @@ func (c Client) listMultipartUploadsQuery(bucketName, keyMarker, uploadIDMarker, } // Set prefix marker. if prefix != "" { - urlValues.Set("prefix", urlEncodePath(prefix)) + urlValues.Set("prefix", prefix) } // Set delimiter. if delimiter != "" { @@ -388,16 +378,11 @@ func (c Client) listMultipartUploadsQuery(bucketName, keyMarker, uploadIDMarker, // Set max-uploads. urlValues.Set("max-uploads", fmt.Sprintf("%d", maxUploads)) - // Instantiate a new request. - req, err := c.newRequest("GET", requestMetadata{ + // Execute GET on bucketName to list multipart uploads. + resp, err := c.executeMethod("GET", requestMetadata{ bucketName: bucketName, queryValues: urlValues, }) - if err != nil { - return listMultipartUploadsResult{}, err - } - // Execute list multipart uploads request. - resp, err := c.do(req) defer closeResponse(resp) if err != nil { return listMultipartUploadsResult{}, err @@ -510,16 +495,12 @@ func (c Client) listObjectPartsQuery(bucketName, objectName, uploadID string, pa // Set max parts. urlValues.Set("max-parts", fmt.Sprintf("%d", maxParts)) - req, err := c.newRequest("GET", requestMetadata{ + // Execute GET on objectName to get list of parts. + resp, err := c.executeMethod("GET", requestMetadata{ bucketName: bucketName, objectName: objectName, queryValues: urlValues, }) - if err != nil { - return listObjectPartsResult{}, err - } - // Exectue list object parts. - resp, err := c.do(req) defer closeResponse(resp) if err != nil { return listObjectPartsResult{}, err diff --git a/vendor/src/github.com/minio/minio-go/api-presigned.go b/vendor/src/github.com/minio/minio-go/api-presigned.go index 0f350d22e..5d1ab7bb0 100644 --- a/vendor/src/github.com/minio/minio-go/api-presigned.go +++ b/vendor/src/github.com/minio/minio-go/api-presigned.go @@ -1,5 +1,5 @@ /* - * Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015 Minio, Inc. + * Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015, 2016 Minio, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,13 +18,26 @@ package minio import ( "errors" + "net/url" "time" ) +// supportedGetReqParams - supported request parameters for GET +// presigned request. +var supportedGetReqParams = map[string]struct{}{ + "response-expires": {}, + "response-content-type": {}, + "response-cache-control": {}, + "response-content-disposition": {}, +} + // presignURL - Returns a presigned URL for an input 'method'. // Expires maximum is 7days - ie. 604800 and minimum is 1. -func (c Client) presignURL(method string, bucketName string, objectName string, expires time.Duration) (url string, err error) { +func (c Client) presignURL(method string, bucketName string, objectName string, expires time.Duration, reqParams url.Values) (urlStr string, err error) { // Input validation. + if method == "" { + return "", ErrInvalidArgument("method cannot be empty.") + } if err := isValidBucketName(bucketName); err != nil { return "", err } @@ -35,35 +48,50 @@ func (c Client) presignURL(method string, bucketName string, objectName string, return "", err } - if method == "" { - return "", ErrInvalidArgument("method cannot be empty.") - } - + // Convert expires into seconds. expireSeconds := int64(expires / time.Second) - // Instantiate a new request. - // Since expires is set newRequest will presign the request. - req, err := c.newRequest(method, requestMetadata{ + reqMetadata := requestMetadata{ presignURL: true, bucketName: bucketName, objectName: objectName, expires: expireSeconds, - }) + } + + // For "GET" we are handling additional request parameters to + // override its response headers. + if method == "GET" { + // Verify if input map has unsupported params, if yes exit. + for k := range reqParams { + if _, ok := supportedGetReqParams[k]; !ok { + return "", ErrInvalidArgument(k + " unsupported request parameter for presigned GET.") + } + } + // Save the request parameters to be used in presigning for + // GET request. + reqMetadata.queryValues = reqParams + } + + // Instantiate a new request. + // Since expires is set newRequest will presign the request. + req, err := c.newRequest(method, reqMetadata) if err != nil { return "", err } return req.URL.String(), nil } -// PresignedGetObject - Returns a presigned URL to access an object without credentials. -// Expires maximum is 7days - ie. 604800 and minimum is 1. -func (c Client) PresignedGetObject(bucketName string, objectName string, expires time.Duration) (url string, err error) { - return c.presignURL("GET", bucketName, objectName, expires) +// PresignedGetObject - Returns a presigned URL to access an object +// without credentials. Expires maximum is 7days - ie. 604800 and +// minimum is 1. Additionally you can override a set of response +// headers using the query parameters. +func (c Client) PresignedGetObject(bucketName string, objectName string, expires time.Duration, reqParams url.Values) (url string, err error) { + return c.presignURL("GET", bucketName, objectName, expires, reqParams) } // PresignedPutObject - Returns a presigned URL to upload an object without credentials. // Expires maximum is 7days - ie. 604800 and minimum is 1. func (c Client) PresignedPutObject(bucketName string, objectName string, expires time.Duration) (url string, err error) { - return c.presignURL("PUT", bucketName, objectName, expires) + return c.presignURL("PUT", bucketName, objectName, expires, nil) } // PresignedPostPolicy - Returns POST form data to upload an object at a location. diff --git a/vendor/src/github.com/minio/minio-go/api-put-bucket.go b/vendor/src/github.com/minio/minio-go/api-put-bucket.go index 986099e34..357f8b992 100644 --- a/vendor/src/github.com/minio/minio-go/api-put-bucket.go +++ b/vendor/src/github.com/minio/minio-go/api-put-bucket.go @@ -1,5 +1,5 @@ /* - * Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015 Minio, Inc. + * Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015, 2016 Minio, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,8 +18,11 @@ package minio import ( "bytes" + "encoding/base64" "encoding/hex" + "encoding/json" "encoding/xml" + "fmt" "io/ioutil" "net/http" "net/url" @@ -27,28 +30,18 @@ import ( /// Bucket operations -// MakeBucket makes a new bucket. +// MakeBucket creates a new bucket with bucketName. // -// Optional arguments are acl and location - by default all buckets are created -// with ``private`` acl and in US Standard region. -// -// ACL valid values - http://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html -// -// private - owner gets full access [default]. -// public-read - owner gets full access, all others get read access. -// public-read-write - owner gets full access, all others get full access too. -// authenticated-read - owner gets full access, authenticated users get read access. +// Location is an optional argument, by default all buckets are +// created in US Standard Region. // // For Amazon S3 for more supported regions - http://docs.aws.amazon.com/general/latest/gr/rande.html // For Google Cloud Storage for more supported regions - https://cloud.google.com/storage/docs/bucket-locations -func (c Client) MakeBucket(bucketName string, acl BucketACL, location string) error { +func (c Client) MakeBucket(bucketName string, location string) error { // Validate the input arguments. if err := isValidBucketName(bucketName); err != nil { return err } - if !acl.isValidBucketACL() { - return ErrInvalidArgument("Unrecognized ACL " + acl.String()) - } // If location is empty, treat is a default region 'us-east-1'. if location == "" { @@ -56,7 +49,7 @@ func (c Client) MakeBucket(bucketName string, acl BucketACL, location string) er } // Instantiate the request. - req, err := c.makeBucketRequest(bucketName, acl, location) + req, err := c.makeBucketRequest(bucketName, location) if err != nil { return err } @@ -74,7 +67,7 @@ func (c Client) MakeBucket(bucketName string, acl BucketACL, location string) er } } - // Save the location into cache on a successfull makeBucket response. + // Save the location into cache on a successful makeBucket response. c.bucketLocCache.Set(bucketName, location) // Return. @@ -82,14 +75,11 @@ func (c Client) MakeBucket(bucketName string, acl BucketACL, location string) er } // makeBucketRequest constructs request for makeBucket. -func (c Client) makeBucketRequest(bucketName string, acl BucketACL, location string) (*http.Request, error) { +func (c Client) makeBucketRequest(bucketName string, location string) (*http.Request, error) { // Validate input arguments. if err := isValidBucketName(bucketName); err != nil { return nil, err } - if !acl.isValidBucketACL() { - return nil, ErrInvalidArgument("Unrecognized ACL " + acl.String()) - } // In case of Amazon S3. The make bucket issued on already // existing bucket would fail with 'AuthorizationMalformed' error @@ -106,12 +96,6 @@ func (c Client) makeBucketRequest(bucketName string, acl BucketACL, location str return nil, err } - // by default bucket acl is set to private. - req.Header.Set("x-amz-acl", "private") - if acl != "" { - req.Header.Set("x-amz-acl", string(acl)) - } - // set UserAgent for the request. c.setUserAgent(req) @@ -131,9 +115,12 @@ func (c Client) makeBucketRequest(bucketName string, acl BucketACL, location str } createBucketConfigBuffer := bytes.NewBuffer(createBucketConfigBytes) req.Body = ioutil.NopCloser(createBucketConfigBuffer) - req.ContentLength = int64(createBucketConfigBuffer.Len()) + req.ContentLength = int64(len(createBucketConfigBytes)) + // Set content-md5. + req.Header.Set("Content-Md5", base64.StdEncoding.EncodeToString(sumMD5(createBucketConfigBytes))) if c.signature.isV4() { - req.Header.Set("X-Amz-Content-Sha256", hex.EncodeToString(sum256(createBucketConfigBuffer.Bytes()))) + // Set sha256. + req.Header.Set("X-Amz-Content-Sha256", hex.EncodeToString(sum256(createBucketConfigBytes))) } } @@ -150,60 +137,89 @@ func (c Client) makeBucketRequest(bucketName string, acl BucketACL, location str return req, nil } -// SetBucketACL set the permissions on an existing bucket using access control lists (ACL). +// SetBucketPolicy set the access permissions on an existing bucket. // // For example // -// private - owner gets full access [default]. -// public-read - owner gets full access, all others get read access. -// public-read-write - owner gets full access, all others get full access too. -// authenticated-read - owner gets full access, authenticated users get read access. -func (c Client) SetBucketACL(bucketName string, acl BucketACL) error { +// none - owner gets full access [default]. +// readonly - anonymous get access for everyone at a given object prefix. +// readwrite - anonymous list/put/delete access to a given object prefix. +// writeonly - anonymous put/delete access to a given object prefix. +func (c Client) SetBucketPolicy(bucketName string, objectPrefix string, bucketPolicy BucketPolicy) error { // Input validation. if err := isValidBucketName(bucketName); err != nil { return err } - if !acl.isValidBucketACL() { - return ErrInvalidArgument("Unrecognized ACL " + acl.String()) + if err := isValidObjectPrefix(objectPrefix); err != nil { + return err + } + if !bucketPolicy.isValidBucketPolicy() { + return ErrInvalidArgument(fmt.Sprintf("Invalid bucket policy provided. %s", bucketPolicy)) + } + policy, err := c.getBucketPolicy(bucketName, objectPrefix) + if err != nil { + return err + } + // For bucket policy set to 'none' we need to remove the policy. + if bucketPolicy == BucketPolicyNone && policy.Statements == nil { + // No policies to set, return success. + return nil + } + // Remove any previous policies at this path. + policy.Statements = removeBucketPolicyStatement(policy.Statements, bucketName, objectPrefix) + + // generating []Statement for the given bucketPolicy. + statements, err := generatePolicyStatement(bucketPolicy, bucketName, objectPrefix) + if err != nil { + return err + } + policy.Statements = append(policy.Statements, statements...) + // Save the updated policies. + return c.putBucketPolicy(bucketName, policy) +} + +// Saves a new bucket policy. +func (c Client) putBucketPolicy(bucketName string, policy BucketAccessPolicy) error { + // Input validation. + if err := isValidBucketName(bucketName); err != nil { + return err } - // Set acl query. + // If there are no policy statements, we should remove entire policy. + if len(policy.Statements) == 0 { + return c.removeBucketPolicy(bucketName) + } + + // Get resources properly escaped and lined up before + // using them in http request. urlValues := make(url.Values) - urlValues.Set("acl", "") + urlValues.Set("policy", "") - // Add misc headers. - customHeader := make(http.Header) - - if acl != "" { - customHeader.Set("x-amz-acl", acl.String()) - } else { - customHeader.Set("x-amz-acl", "private") - } - - // Instantiate a new request. - req, err := c.newRequest("PUT", requestMetadata{ - bucketName: bucketName, - queryValues: urlValues, - customHeader: customHeader, - }) + policyBytes, err := json.Marshal(&policy) if err != nil { return err } - // Initiate the request. - resp, err := c.do(req) + policyBuffer := bytes.NewReader(policyBytes) + reqMetadata := requestMetadata{ + bucketName: bucketName, + queryValues: urlValues, + contentBody: policyBuffer, + contentLength: int64(len(policyBytes)), + contentMD5Bytes: sumMD5(policyBytes), + contentSHA256Bytes: sum256(policyBytes), + } + + // Execute PUT to upload a new bucket policy. + resp, err := c.executeMethod("PUT", reqMetadata) defer closeResponse(resp) if err != nil { return err } - if resp != nil { - // if error return. - if resp.StatusCode != http.StatusOK { + if resp.StatusCode != http.StatusNoContent { return httpRespToErrorResponse(resp, bucketName, "") } } - - // return return nil } diff --git a/vendor/src/github.com/minio/minio-go/api-put-bucket_test.go b/vendor/src/github.com/minio/minio-go/api-put-bucket_test.go new file mode 100644 index 000000000..050feed0e --- /dev/null +++ b/vendor/src/github.com/minio/minio-go/api-put-bucket_test.go @@ -0,0 +1,270 @@ +/* + * Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015, 2016 Minio, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package minio + +import ( + "bytes" + "encoding/base64" + "encoding/hex" + "encoding/xml" + "io" + "io/ioutil" + "net/http" + "testing" +) + +// Tests validate http request formulated for creation of bucket. +func TestMakeBucketRequest(t *testing.T) { + // Generates expected http request for bucket creation. + // Used for asserting with the actual request generated. + createExpectedRequest := func(c *Client, bucketName string, location string, req *http.Request) (*http.Request, error) { + + targetURL := *c.endpointURL + targetURL.Path = "/" + bucketName + "/" + + // get a new HTTP request for the method. + req, err := http.NewRequest("PUT", targetURL.String(), nil) + if err != nil { + return nil, err + } + + // set UserAgent for the request. + c.setUserAgent(req) + + // set sha256 sum for signature calculation only with signature version '4'. + if c.signature.isV4() { + req.Header.Set("X-Amz-Content-Sha256", hex.EncodeToString(sum256([]byte{}))) + } + + // If location is not 'us-east-1' create bucket location config. + if location != "us-east-1" && location != "" { + createBucketConfig := createBucketConfiguration{} + createBucketConfig.Location = location + var createBucketConfigBytes []byte + createBucketConfigBytes, err = xml.Marshal(createBucketConfig) + if err != nil { + return nil, err + } + createBucketConfigBuffer := bytes.NewBuffer(createBucketConfigBytes) + req.Body = ioutil.NopCloser(createBucketConfigBuffer) + req.ContentLength = int64(len(createBucketConfigBytes)) + // Set content-md5. + req.Header.Set("Content-Md5", base64.StdEncoding.EncodeToString(sumMD5(createBucketConfigBytes))) + if c.signature.isV4() { + // Set sha256. + req.Header.Set("X-Amz-Content-Sha256", hex.EncodeToString(sum256(createBucketConfigBytes))) + } + } + + // Sign the request. + if c.signature.isV4() { + // Signature calculated for MakeBucket request should be for 'us-east-1', + // regardless of the bucket's location constraint. + req = signV4(*req, c.accessKeyID, c.secretAccessKey, "us-east-1") + } else if c.signature.isV2() { + req = signV2(*req, c.accessKeyID, c.secretAccessKey) + } + + // Return signed request. + return req, nil + } + + // Get Request body. + getReqBody := func(reqBody io.ReadCloser) (string, error) { + contents, err := ioutil.ReadAll(reqBody) + if err != nil { + return "", err + } + return string(contents), nil + } + + // Info for 'Client' creation. + // Will be used as arguments for 'NewClient'. + type infoForClient struct { + endPoint string + accessKey string + secretKey string + enableInsecure bool + } + // dataset for 'NewClient' call. + info := []infoForClient{ + // endpoint localhost. + // both access-key and secret-key are empty. + {"localhost:9000", "", "", false}, + // both access-key are secret-key exists. + {"localhost:9000", "my-access-key", "my-secret-key", false}, + // one of acess-key and secret-key are empty. + {"localhost:9000", "", "my-secret-key", false}, + + // endpoint amazon s3. + {"s3.amazonaws.com", "", "", false}, + {"s3.amazonaws.com", "my-access-key", "my-secret-key", false}, + {"s3.amazonaws.com", "my-acess-key", "", false}, + + // endpoint google cloud storage. + {"storage.googleapis.com", "", "", false}, + {"storage.googleapis.com", "my-access-key", "my-secret-key", false}, + {"storage.googleapis.com", "", "my-secret-key", false}, + + // endpoint custom domain running Minio server. + {"play.minio.io", "", "", false}, + {"play.minio.io", "my-access-key", "my-secret-key", false}, + {"play.minio.io", "my-acess-key", "", false}, + } + + testCases := []struct { + bucketName string + location string + // data for new client creation. + info infoForClient + // error in the output. + err error + // flag indicating whether tests should pass. + shouldPass bool + }{ + // Test cases with Invalid bucket name. + {".mybucket", "", infoForClient{}, ErrInvalidBucketName("Bucket name cannot start or end with a '.' dot."), false}, + {"mybucket.", "", infoForClient{}, ErrInvalidBucketName("Bucket name cannot start or end with a '.' dot."), false}, + {"mybucket-", "", infoForClient{}, ErrInvalidBucketName("Bucket name contains invalid characters."), false}, + {"my", "", infoForClient{}, ErrInvalidBucketName("Bucket name cannot be smaller than 3 characters."), false}, + {"", "", infoForClient{}, ErrInvalidBucketName("Bucket name cannot be empty."), false}, + {"my..bucket", "", infoForClient{}, ErrInvalidBucketName("Bucket name cannot have successive periods."), false}, + + // Test case with all valid values for S3 bucket location. + // Client is constructed using the info struct. + // case with empty location. + {"my-bucket", "", info[0], nil, true}, + // case with location set to standard 'us-east-1'. + {"my-bucket", "us-east-1", info[0], nil, true}, + // case with location set to a value different from 'us-east-1'. + {"my-bucket", "eu-central-1", info[0], nil, true}, + + {"my-bucket", "", info[1], nil, true}, + {"my-bucket", "us-east-1", info[1], nil, true}, + {"my-bucket", "eu-central-1", info[1], nil, true}, + + {"my-bucket", "", info[2], nil, true}, + {"my-bucket", "us-east-1", info[2], nil, true}, + {"my-bucket", "eu-central-1", info[2], nil, true}, + + {"my-bucket", "", info[3], nil, true}, + {"my-bucket", "us-east-1", info[3], nil, true}, + {"my-bucket", "eu-central-1", info[3], nil, true}, + + {"my-bucket", "", info[4], nil, true}, + {"my-bucket", "us-east-1", info[4], nil, true}, + {"my-bucket", "eu-central-1", info[4], nil, true}, + + {"my-bucket", "", info[5], nil, true}, + {"my-bucket", "us-east-1", info[5], nil, true}, + {"my-bucket", "eu-central-1", info[5], nil, true}, + + {"my-bucket", "", info[6], nil, true}, + {"my-bucket", "us-east-1", info[6], nil, true}, + {"my-bucket", "eu-central-1", info[6], nil, true}, + + {"my-bucket", "", info[7], nil, true}, + {"my-bucket", "us-east-1", info[7], nil, true}, + {"my-bucket", "eu-central-1", info[7], nil, true}, + + {"my-bucket", "", info[8], nil, true}, + {"my-bucket", "us-east-1", info[8], nil, true}, + {"my-bucket", "eu-central-1", info[8], nil, true}, + + {"my-bucket", "", info[9], nil, true}, + {"my-bucket", "us-east-1", info[9], nil, true}, + {"my-bucket", "eu-central-1", info[9], nil, true}, + + {"my-bucket", "", info[10], nil, true}, + {"my-bucket", "us-east-1", info[10], nil, true}, + {"my-bucket", "eu-central-1", info[10], nil, true}, + + {"my-bucket", "", info[11], nil, true}, + {"my-bucket", "us-east-1", info[11], nil, true}, + {"my-bucket", "eu-central-1", info[11], nil, true}, + } + + for i, testCase := range testCases { + // cannot create a newclient with empty endPoint value. + // validates and creates a new client only if the endPoint value is not empty. + client := &Client{} + var err error + if testCase.info.endPoint != "" { + + client, err = New(testCase.info.endPoint, testCase.info.accessKey, testCase.info.secretKey, testCase.info.enableInsecure) + if err != nil { + t.Fatalf("Test %d: Failed to create new Client: %s", i+1, err.Error()) + } + } + + actualReq, err := client.makeBucketRequest(testCase.bucketName, testCase.location) + if err != nil && testCase.shouldPass { + t.Errorf("Test %d: Expected to pass, but failed with: %s", i+1, err.Error()) + } + if err == nil && !testCase.shouldPass { + t.Errorf("Test %d: Expected to fail with \"%s\", but passed instead", i+1, testCase.err.Error()) + } + // Failed as expected, but does it fail for the expected reason. + if err != nil && !testCase.shouldPass { + if err.Error() != testCase.err.Error() { + t.Errorf("Test %d: Expected to fail with error \"%s\", but instead failed with error \"%s\" instead", i+1, testCase.err.Error(), err.Error()) + } + } + + // Test passes as expected, but the output values are verified for correctness here. + if err == nil && testCase.shouldPass { + expectedReq := &http.Request{} + expectedReq, err = createExpectedRequest(client, testCase.bucketName, testCase.location, expectedReq) + if err != nil { + t.Fatalf("Test %d: Expected request Creation failed", i+1) + } + if expectedReq.Method != actualReq.Method { + t.Errorf("Test %d: The expected Request method doesn't match with the actual one", i+1) + } + if expectedReq.URL.String() != actualReq.URL.String() { + t.Errorf("Test %d: Expected the request URL to be '%s', but instead found '%s'", i+1, expectedReq.URL.String(), actualReq.URL.String()) + } + if expectedReq.ContentLength != actualReq.ContentLength { + t.Errorf("Test %d: Expected the request body Content-Length to be '%d', but found '%d' instead", i+1, expectedReq.ContentLength, actualReq.ContentLength) + } + + if expectedReq.Header.Get("X-Amz-Content-Sha256") != actualReq.Header.Get("X-Amz-Content-Sha256") { + t.Errorf("Test %d: 'X-Amz-Content-Sha256' header of the expected request doesn't match with that of the actual request", i+1) + } + if expectedReq.Header.Get("User-Agent") != actualReq.Header.Get("User-Agent") { + t.Errorf("Test %d: Expected 'User-Agent' header to be \"%s\",but found \"%s\" instead", i+1, expectedReq.Header.Get("User-Agent"), actualReq.Header.Get("User-Agent")) + } + + if testCase.location != "us-east-1" && testCase.location != "" { + expectedContent, err := getReqBody(expectedReq.Body) + if err != nil { + t.Fatalf("Test %d: Coudln't parse request body", i+1) + } + actualContent, err := getReqBody(actualReq.Body) + if err != nil { + t.Fatalf("Test %d: Coudln't parse request body", i+1) + } + if expectedContent != actualContent { + t.Errorf("Test %d: Expected request body doesn't match actual content body", i+1) + } + if expectedReq.Header.Get("Content-Md5") != actualReq.Header.Get("Content-Md5") { + t.Errorf("Test %d: Request body Md5 differs from the expected result", i+1) + } + } + } + } +} diff --git a/vendor/src/github.com/minio/minio-go/api-put-object-common.go b/vendor/src/github.com/minio/minio-go/api-put-object-common.go index 1584497bb..937c74d46 100644 --- a/vendor/src/github.com/minio/minio-go/api-put-object-common.go +++ b/vendor/src/github.com/minio/minio-go/api-put-object-common.go @@ -19,6 +19,7 @@ package minio import ( "crypto/md5" "crypto/sha256" + "fmt" "hash" "io" "math" @@ -55,7 +56,7 @@ func shouldUploadPart(objPart objectPart, objectParts map[int]objectPart) bool { return true } // if md5sum mismatches should upload the part. - if objPart.ETag == uploadedPart.ETag { + if objPart.ETag != uploadedPart.ETag { return true } return false @@ -94,62 +95,13 @@ func optimalPartInfo(objectSize int64) (totalPartsCount int, partSize int64, las return totalPartsCount, partSize, lastPartSize, nil } -// Compatibility code for Golang < 1.5.x. -// copyBuffer is identical to io.CopyBuffer, since such a function is -// not available/implemented in Golang version < 1.5.x, we use a -// custom call exactly implementng io.CopyBuffer from Golang > 1.5.x -// version does. +// hashCopyBuffer is identical to hashCopyN except that it doesn't take +// any size argument but takes a buffer argument and reader should be +// of io.ReaderAt interface. // -// copyBuffer stages through the provided buffer (if one is required) -// rather than allocating a temporary one. If buf is nil, one is -// allocated; otherwise if it has zero length, copyBuffer panics. -// -// FIXME: Remove this code when distributions move to newer Golang versions. -func copyBuffer(writer io.Writer, reader io.Reader, buf []byte) (written int64, err error) { - // If the reader has a WriteTo method, use it to do the copy. - // Avoids an allocation and a copy. - if wt, ok := reader.(io.WriterTo); ok { - return wt.WriteTo(writer) - } - // Similarly, if the writer has a ReadFrom method, use it to do - // the copy. - if rt, ok := writer.(io.ReaderFrom); ok { - return rt.ReadFrom(reader) - } - if buf == nil { - buf = make([]byte, 32*1024) - } - for { - nr, er := reader.Read(buf) - if nr > 0 { - nw, ew := writer.Write(buf[0:nr]) - if nw > 0 { - written += int64(nw) - } - if ew != nil { - err = ew - break - } - if nr != nw { - err = io.ErrShortWrite - break - } - } - if er == io.EOF { - break - } - if er != nil { - err = er - break - } - } - return written, err -} - -// hashCopyBuffer is identical to hashCopyN except that it stages -// through the provided buffer (if one is required) rather than -// allocating a temporary one. If buf is nil, one is allocated for 5MiB. -func (c Client) hashCopyBuffer(writer io.Writer, reader io.Reader, buf []byte) (md5Sum, sha256Sum []byte, size int64, err error) { +// Stages reads from offsets into the buffer, if buffer is nil it is +// initialized to optimalBufferSize. +func (c Client) hashCopyBuffer(writer io.Writer, reader io.ReaderAt, buf []byte) (md5Sum, sha256Sum []byte, size int64, err error) { // MD5 and SHA256 hasher. var hashMD5, hashSHA256 hash.Hash // MD5 and SHA256 hasher. @@ -160,14 +112,61 @@ func (c Client) hashCopyBuffer(writer io.Writer, reader io.Reader, buf []byte) ( hashWriter = io.MultiWriter(writer, hashMD5, hashSHA256) } - // Allocate buf if not initialized. + // Buffer is nil, initialize. if buf == nil { buf = make([]byte, optimalReadBufferSize) } + // Offset to start reading from. + var readAtOffset int64 + + // Following block reads data at an offset from the input + // reader and copies data to into local temporary file. + for { + readAtSize, rerr := reader.ReadAt(buf, readAtOffset) + if rerr != nil { + if rerr != io.EOF { + return nil, nil, 0, rerr + } + } + writeSize, werr := hashWriter.Write(buf[:readAtSize]) + if werr != nil { + return nil, nil, 0, werr + } + if readAtSize != writeSize { + return nil, nil, 0, fmt.Errorf("Read size was not completely written to writer. wanted %d, got %d - %s", readAtSize, writeSize, reportIssue) + } + readAtOffset += int64(writeSize) + size += int64(writeSize) + if rerr == io.EOF { + break + } + } + + // Finalize md5 sum and sha256 sum. + md5Sum = hashMD5.Sum(nil) + if c.signature.isV4() { + sha256Sum = hashSHA256.Sum(nil) + } + return md5Sum, sha256Sum, size, err +} + +// hashCopy is identical to hashCopyN except that it doesn't take +// any size argument. +func (c Client) hashCopy(writer io.Writer, reader io.Reader) (md5Sum, sha256Sum []byte, size int64, err error) { + // MD5 and SHA256 hasher. + var hashMD5, hashSHA256 hash.Hash + // MD5 and SHA256 hasher. + hashMD5 = md5.New() + hashWriter := io.MultiWriter(writer, hashMD5) + if c.signature.isV4() { + hashSHA256 = sha256.New() + hashWriter = io.MultiWriter(writer, hashMD5, hashSHA256) + } + // Using copyBuffer to copy in large buffers, default buffer // for io.Copy of 32KiB is too small. - size, err = copyBuffer(hashWriter, reader, buf) + size, err = io.Copy(hashWriter, reader) if err != nil { return nil, nil, 0, err } @@ -244,12 +243,8 @@ func (c Client) getUploadID(bucketName, objectName, contentType string) (uploadI return uploadID, isNew, nil } -// computeHashBuffer - Calculates MD5 and SHA256 for an input read -// Seeker is identical to computeHash except that it stages -// through the provided buffer (if one is required) rather than -// allocating a temporary one. If buf is nil, it uses a temporary -// buffer. -func (c Client) computeHashBuffer(reader io.ReadSeeker, buf []byte) (md5Sum, sha256Sum []byte, size int64, err error) { +// computeHash - Calculates MD5 and SHA256 for an input read Seeker. +func (c Client) computeHash(reader io.ReadSeeker) (md5Sum, sha256Sum []byte, size int64, err error) { // MD5 and SHA256 hasher. var hashMD5, hashSHA256 hash.Hash // MD5 and SHA256 hasher. @@ -261,16 +256,9 @@ func (c Client) computeHashBuffer(reader io.ReadSeeker, buf []byte) (md5Sum, sha } // If no buffer is provided, no need to allocate just use io.Copy. - if buf == nil { - size, err = io.Copy(hashWriter, reader) - if err != nil { - return nil, nil, 0, err - } - } else { - size, err = copyBuffer(hashWriter, reader, buf) - if err != nil { - return nil, nil, 0, err - } + size, err = io.Copy(hashWriter, reader) + if err != nil { + return nil, nil, 0, err } // Seek back reader to the beginning location. @@ -285,8 +273,3 @@ func (c Client) computeHashBuffer(reader io.ReadSeeker, buf []byte) (md5Sum, sha } return md5Sum, sha256Sum, size, nil } - -// computeHash - Calculates MD5 and SHA256 for an input read Seeker. -func (c Client) computeHash(reader io.ReadSeeker) (md5Sum, sha256Sum []byte, size int64, err error) { - return c.computeHashBuffer(reader, nil) -} diff --git a/vendor/src/github.com/minio/minio-go/api-put-object-copy.go b/vendor/src/github.com/minio/minio-go/api-put-object-copy.go new file mode 100644 index 000000000..45d5693fc --- /dev/null +++ b/vendor/src/github.com/minio/minio-go/api-put-object-copy.go @@ -0,0 +1,68 @@ +/* + * Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2016 Minio, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package minio + +import "net/http" + +// CopyObject - copy a source object into a new object with the provided name in the provided bucket +func (c Client) CopyObject(bucketName string, objectName string, objectSource string, cpCond CopyConditions) error { + // Input validation. + if err := isValidBucketName(bucketName); err != nil { + return err + } + if err := isValidObjectName(objectName); err != nil { + return err + } + if objectSource == "" { + return ErrInvalidArgument("Object source cannot be empty.") + } + + // customHeaders apply headers. + customHeaders := make(http.Header) + for _, cond := range cpCond.conditions { + customHeaders.Set(cond.key, cond.value) + } + + // Set copy source. + customHeaders.Set("x-amz-copy-source", objectSource) + + // Execute PUT on objectName. + resp, err := c.executeMethod("PUT", requestMetadata{ + bucketName: bucketName, + objectName: objectName, + customHeader: customHeaders, + }) + defer closeResponse(resp) + if err != nil { + return err + } + if resp != nil { + if resp.StatusCode != http.StatusOK { + return httpRespToErrorResponse(resp, bucketName, objectName) + } + } + + // Decode copy response on success. + cpObjRes := copyObjectResult{} + err = xmlDecoder(resp.Body, &cpObjRes) + if err != nil { + return err + } + + // Return nil on success. + return nil +} diff --git a/vendor/src/github.com/minio/minio-go/api-put-object-file.go b/vendor/src/github.com/minio/minio-go/api-put-object-file.go index d212dfb87..e7a1a9685 100644 --- a/vendor/src/github.com/minio/minio-go/api-put-object-file.go +++ b/vendor/src/github.com/minio/minio-go/api-put-object-file.go @@ -1,5 +1,5 @@ /* - * Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015 Minio, Inc. + * Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015, 2016 Minio, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,7 +21,9 @@ import ( "fmt" "io" "io/ioutil" + "mime" "os" + "path/filepath" "sort" ) @@ -57,6 +59,14 @@ func (c Client) FPutObject(bucketName, objectName, filePath, contentType string) return 0, ErrEntityTooLarge(fileSize, maxMultipartPutObjectSize, bucketName, objectName) } + // Set contentType based on filepath extension if not given or default + // value of "binary/octet-stream" if the extension has no associated type. + if contentType == "" { + if contentType = mime.TypeByExtension(filepath.Ext(filePath)); contentType == "" { + contentType = "application/octet-stream" + } + } + // NOTE: Google Cloud Storage multipart Put is not compatible with Amazon S3 APIs. // Current implementation will only upload a maximum of 5GiB to Google Cloud Storage servers. if isGoogleEndpoint(c.endpointURL) { @@ -187,7 +197,7 @@ func (c Client) putObjectMultipartFromFile(bucketName, objectName string, fileRe }, partsInfo) { // Proceed to upload the part. var objPart objectPart - objPart, err = c.uploadPart(bucketName, objectName, uploadID, ioutil.NopCloser(reader), partNumber, + objPart, err = c.uploadPart(bucketName, objectName, uploadID, reader, partNumber, md5Sum, sha256Sum, prtSize) if err != nil { return totalUploadedSize, err diff --git a/vendor/src/github.com/minio/minio-go/api-put-object-multipart.go b/vendor/src/github.com/minio/minio-go/api-put-object-multipart.go index ee0019165..be96ad232 100644 --- a/vendor/src/github.com/minio/minio-go/api-put-object-multipart.go +++ b/vendor/src/github.com/minio/minio-go/api-put-object-multipart.go @@ -1,5 +1,5 @@ /* - * Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015 Minio, Inc. + * Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015, 2016 Minio, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -134,8 +134,7 @@ func (c Client) putObjectMultipartStream(bucketName, objectName string, reader i }, partsInfo) { // Proceed to upload the part. var objPart objectPart - objPart, err = c.uploadPart(bucketName, objectName, uploadID, ioutil.NopCloser(reader), partNumber, - md5Sum, sha256Sum, prtSize) + objPart, err = c.uploadPart(bucketName, objectName, uploadID, reader, partNumber, md5Sum, sha256Sum, prtSize) if err != nil { // Reset the temporary buffer upon any error. tmpBuffer.Reset() @@ -230,14 +229,8 @@ func (c Client) initiateMultipartUpload(bucketName, objectName, contentType stri customHeader: customHeader, } - // Instantiate the request. - req, err := c.newRequest("POST", reqMetadata) - if err != nil { - return initiateMultipartUploadResult{}, err - } - - // Execute the request. - resp, err := c.do(req) + // Execute POST on an objectName to initiate multipart upload. + resp, err := c.executeMethod("POST", reqMetadata) defer closeResponse(resp) if err != nil { return initiateMultipartUploadResult{}, err @@ -257,7 +250,7 @@ func (c Client) initiateMultipartUpload(bucketName, objectName, contentType stri } // uploadPart - Uploads a part in a multipart upload. -func (c Client) uploadPart(bucketName, objectName, uploadID string, reader io.ReadCloser, partNumber int, md5Sum, sha256Sum []byte, size int64) (objectPart, error) { +func (c Client) uploadPart(bucketName, objectName, uploadID string, reader io.Reader, partNumber int, md5Sum, sha256Sum []byte, size int64) (objectPart, error) { // Input validation. if err := isValidBucketName(bucketName); err != nil { return objectPart{}, err @@ -295,13 +288,8 @@ func (c Client) uploadPart(bucketName, objectName, uploadID string, reader io.Re contentSHA256Bytes: sha256Sum, } - // Instantiate a request. - req, err := c.newRequest("PUT", reqMetadata) - if err != nil { - return objectPart{}, err - } - // Execute the request. - resp, err := c.do(req) + // Execute PUT on each part. + resp, err := c.executeMethod("PUT", reqMetadata) defer closeResponse(resp) if err != nil { return objectPart{}, err @@ -342,24 +330,18 @@ func (c Client) completeMultipartUpload(bucketName, objectName, uploadID string, } // Instantiate all the complete multipart buffer. - completeMultipartUploadBuffer := bytes.NewBuffer(completeMultipartUploadBytes) + completeMultipartUploadBuffer := bytes.NewReader(completeMultipartUploadBytes) reqMetadata := requestMetadata{ bucketName: bucketName, objectName: objectName, queryValues: urlValues, - contentBody: ioutil.NopCloser(completeMultipartUploadBuffer), - contentLength: int64(completeMultipartUploadBuffer.Len()), - contentSHA256Bytes: sum256(completeMultipartUploadBuffer.Bytes()), + contentBody: completeMultipartUploadBuffer, + contentLength: int64(len(completeMultipartUploadBytes)), + contentSHA256Bytes: sum256(completeMultipartUploadBytes), } - // Instantiate the request. - req, err := c.newRequest("POST", reqMetadata) - if err != nil { - return completeMultipartUploadResult{}, err - } - - // Execute the request. - resp, err := c.do(req) + // Execute POST to complete multipart upload for an objectName. + resp, err := c.executeMethod("POST", reqMetadata) defer closeResponse(resp) if err != nil { return completeMultipartUploadResult{}, err diff --git a/vendor/src/github.com/minio/minio-go/api-put-object-progress.go b/vendor/src/github.com/minio/minio-go/api-put-object-progress.go index ae4425d49..ebbc380c3 100644 --- a/vendor/src/github.com/minio/minio-go/api-put-object-progress.go +++ b/vendor/src/github.com/minio/minio-go/api-put-object-progress.go @@ -91,7 +91,7 @@ func (c Client) PutObjectWithProgress(bucketName, objectName string, reader io.R errResp := ToErrorResponse(err) // Verify if multipart functionality is not available, if not // fall back to single PutObject operation. - if errResp.Code == "NotImplemented" { + if errResp.Code == "AccessDenied" && errResp.Message == "Access Denied." { // Verify if size of reader is greater than '5GiB'. if size > maxSinglePutObjectSize { return 0, ErrEntityTooLarge(size, maxSinglePutObjectSize, bucketName, objectName) diff --git a/vendor/src/github.com/minio/minio-go/api-put-object-readat.go b/vendor/src/github.com/minio/minio-go/api-put-object-readat.go index ddb1ab3dc..957e3380e 100644 --- a/vendor/src/github.com/minio/minio-go/api-put-object-readat.go +++ b/vendor/src/github.com/minio/minio-go/api-put-object-readat.go @@ -1,5 +1,5 @@ /* - * Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015 Minio, Inc. + * Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015, 2016 Minio, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -97,7 +97,7 @@ func (c Client) putObjectMultipartFromReadAt(bucketName, objectName string, read tmpBuffer := new(bytes.Buffer) // Read defaults to reading at 5MiB buffer. - readBuffer := make([]byte, optimalReadBufferSize) + readAtBuffer := make([]byte, optimalReadBufferSize) // Upload all the missing parts. for partNumber <= lastPartNumber { @@ -147,7 +147,7 @@ func (c Client) putObjectMultipartFromReadAt(bucketName, objectName string, read // Calculates MD5 and SHA256 sum for a section reader. var md5Sum, sha256Sum []byte var prtSize int64 - md5Sum, sha256Sum, prtSize, err = c.hashCopyBuffer(tmpBuffer, sectionReader, readBuffer) + md5Sum, sha256Sum, prtSize, err = c.hashCopyBuffer(tmpBuffer, sectionReader, readAtBuffer) if err != nil { return 0, err } @@ -159,8 +159,7 @@ func (c Client) putObjectMultipartFromReadAt(bucketName, objectName string, read // Proceed to upload the part. var objPart objectPart - objPart, err = c.uploadPart(bucketName, objectName, uploadID, ioutil.NopCloser(reader), - partNumber, md5Sum, sha256Sum, prtSize) + objPart, err = c.uploadPart(bucketName, objectName, uploadID, reader, partNumber, md5Sum, sha256Sum, prtSize) if err != nil { // Reset the buffer upon any error. tmpBuffer.Reset() diff --git a/vendor/src/github.com/minio/minio-go/api-put-object.go b/vendor/src/github.com/minio/minio-go/api-put-object.go index a09e658f4..10390c6c5 100644 --- a/vendor/src/github.com/minio/minio-go/api-put-object.go +++ b/vendor/src/github.com/minio/minio-go/api-put-object.go @@ -1,5 +1,5 @@ /* - * Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015 Minio, Inc. + * Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015, 2016 Minio, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -38,7 +38,7 @@ func getReaderSize(reader io.Reader) (size int64, err error) { if lenFn.Kind() == reflect.Func { // Call the 'Size' function and save its return value. result = lenFn.Call([]reflect.Value{}) - if result != nil && len(result) == 1 { + if len(result) == 1 { lenValue := result[0] if lenValue.IsValid() { switch lenValue.Kind() { @@ -146,11 +146,11 @@ func (c Client) putObjectNoChecksum(bucketName, objectName string, reader io.Rea // Update progress reader appropriately to the latest offset as we // read from the source. - reader = newHook(reader, progress) + readSeeker := newHook(reader, progress) // This function does not calculate sha256 and md5sum for payload. // Execute put object. - st, err := c.putObjectDo(bucketName, objectName, ioutil.NopCloser(reader), nil, nil, size, contentType) + st, err := c.putObjectDo(bucketName, objectName, readSeeker, nil, nil, size, contentType) if err != nil { return 0, err } @@ -178,12 +178,12 @@ func (c Client) putObjectSingle(bucketName, objectName string, reader io.Reader, size = maxSinglePutObjectSize } var md5Sum, sha256Sum []byte - var readCloser io.ReadCloser if size <= minPartSize { // Initialize a new temporary buffer. tmpBuffer := new(bytes.Buffer) md5Sum, sha256Sum, size, err = c.hashCopyN(tmpBuffer, reader, size) - readCloser = ioutil.NopCloser(tmpBuffer) + reader = bytes.NewReader(tmpBuffer.Bytes()) + tmpBuffer.Reset() } else { // Initialize a new temporary file. var tmpFile *tempFile @@ -191,12 +191,13 @@ func (c Client) putObjectSingle(bucketName, objectName string, reader io.Reader, if err != nil { return 0, err } + defer tmpFile.Close() md5Sum, sha256Sum, size, err = c.hashCopyN(tmpFile, reader, size) // Seek back to beginning of the temporary file. if _, err = tmpFile.Seek(0, 0); err != nil { return 0, err } - readCloser = tmpFile + reader = tmpFile } // Return error if its not io.EOF. if err != nil { @@ -204,26 +205,26 @@ func (c Client) putObjectSingle(bucketName, objectName string, reader io.Reader, return 0, err } } - // Progress the reader to the size. - if progress != nil { - if _, err = io.CopyN(ioutil.Discard, progress, size); err != nil { - return size, err - } - } // Execute put object. - st, err := c.putObjectDo(bucketName, objectName, readCloser, md5Sum, sha256Sum, size, contentType) + st, err := c.putObjectDo(bucketName, objectName, reader, md5Sum, sha256Sum, size, contentType) if err != nil { return 0, err } if st.Size != size { return 0, ErrUnexpectedEOF(st.Size, size, bucketName, objectName) } + // Progress the reader to the size if putObjectDo is successful. + if progress != nil { + if _, err = io.CopyN(ioutil.Discard, progress, size); err != nil { + return size, err + } + } return size, nil } // putObjectDo - executes the put object http operation. // NOTE: You must have WRITE permissions on a bucket to add an object to it. -func (c Client) putObjectDo(bucketName, objectName string, reader io.ReadCloser, md5Sum []byte, sha256Sum []byte, size int64, contentType string) (ObjectInfo, error) { +func (c Client) putObjectDo(bucketName, objectName string, reader io.Reader, md5Sum []byte, sha256Sum []byte, size int64, contentType string) (ObjectInfo, error) { // Input validation. if err := isValidBucketName(bucketName); err != nil { return ObjectInfo{}, err @@ -258,13 +259,9 @@ func (c Client) putObjectDo(bucketName, objectName string, reader io.ReadCloser, contentMD5Bytes: md5Sum, contentSHA256Bytes: sha256Sum, } - // Initiate new request. - req, err := c.newRequest("PUT", reqMetadata) - if err != nil { - return ObjectInfo{}, err - } - // Execute the request. - resp, err := c.do(req) + + // Execute PUT an objectName. + resp, err := c.executeMethod("PUT", reqMetadata) defer closeResponse(resp) if err != nil { return ObjectInfo{}, err diff --git a/vendor/src/github.com/minio/minio-go/api-remove.go b/vendor/src/github.com/minio/minio-go/api-remove.go index 8f59c15e6..bd5842828 100644 --- a/vendor/src/github.com/minio/minio-go/api-remove.go +++ b/vendor/src/github.com/minio/minio-go/api-remove.go @@ -1,5 +1,5 @@ /* - * Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015 Minio, Inc. + * Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015, 2016 Minio, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,15 +30,10 @@ func (c Client) RemoveBucket(bucketName string) error { if err := isValidBucketName(bucketName); err != nil { return err } - // Instantiate a new request. - req, err := c.newRequest("DELETE", requestMetadata{ + // Execute DELETE on bucket. + resp, err := c.executeMethod("DELETE", requestMetadata{ bucketName: bucketName, }) - if err != nil { - return err - } - // Initiate the request. - resp, err := c.do(req) defer closeResponse(resp) if err != nil { return err @@ -55,6 +50,54 @@ func (c Client) RemoveBucket(bucketName string) error { return nil } +// RemoveBucketPolicy remove a bucket policy on given path. +func (c Client) RemoveBucketPolicy(bucketName, objectPrefix string) error { + // Input validation. + if err := isValidBucketName(bucketName); err != nil { + return err + } + if err := isValidObjectPrefix(objectPrefix); err != nil { + return err + } + policy, err := c.getBucketPolicy(bucketName, objectPrefix) + if err != nil { + return err + } + // No bucket policy found, nothing to remove return success. + if policy.Statements == nil { + return nil + } + + // Save new statements after removing requested bucket policy. + policy.Statements = removeBucketPolicyStatement(policy.Statements, bucketName, objectPrefix) + + // Commit the update policy. + return c.putBucketPolicy(bucketName, policy) +} + +// Removes all policies on a bucket. +func (c Client) removeBucketPolicy(bucketName string) error { + // Input validation. + if err := isValidBucketName(bucketName); err != nil { + return err + } + // Get resources properly escaped and lined up before + // using them in http request. + urlValues := make(url.Values) + urlValues.Set("policy", "") + + // Execute DELETE on objectName. + resp, err := c.executeMethod("DELETE", requestMetadata{ + bucketName: bucketName, + queryValues: urlValues, + }) + defer closeResponse(resp) + if err != nil { + return err + } + return nil +} + // RemoveObject remove an object from a bucket. func (c Client) RemoveObject(bucketName, objectName string) error { // Input validation. @@ -64,16 +107,11 @@ func (c Client) RemoveObject(bucketName, objectName string) error { if err := isValidObjectName(objectName); err != nil { return err } - // Instantiate the request. - req, err := c.newRequest("DELETE", requestMetadata{ + // Execute DELETE on objectName. + resp, err := c.executeMethod("DELETE", requestMetadata{ bucketName: bucketName, objectName: objectName, }) - if err != nil { - return err - } - // Initiate the request. - resp, err := c.do(req) defer closeResponse(resp) if err != nil { return err @@ -124,18 +162,12 @@ func (c Client) abortMultipartUpload(bucketName, objectName, uploadID string) er urlValues := make(url.Values) urlValues.Set("uploadId", uploadID) - // Instantiate a new DELETE request. - req, err := c.newRequest("DELETE", requestMetadata{ + // Execute DELETE on multipart upload. + resp, err := c.executeMethod("DELETE", requestMetadata{ bucketName: bucketName, objectName: objectName, queryValues: urlValues, }) - if err != nil { - return err - } - - // Initiate the request. - resp, err := c.do(req) defer closeResponse(resp) if err != nil { return err @@ -149,13 +181,13 @@ func (c Client) abortMultipartUpload(bucketName, objectName, uploadID string) er // This is needed specifically for abort and it cannot // be converged into default case. errorResponse = ErrorResponse{ - Code: "NoSuchUpload", - Message: "The specified multipart upload does not exist.", - BucketName: bucketName, - Key: objectName, - RequestID: resp.Header.Get("x-amz-request-id"), - HostID: resp.Header.Get("x-amz-id-2"), - AmzBucketRegion: resp.Header.Get("x-amz-bucket-region"), + Code: "NoSuchUpload", + Message: "The specified multipart upload does not exist.", + BucketName: bucketName, + Key: objectName, + RequestID: resp.Header.Get("x-amz-request-id"), + HostID: resp.Header.Get("x-amz-id-2"), + Region: resp.Header.Get("x-amz-bucket-region"), } default: return httpRespToErrorResponse(resp, bucketName, objectName) diff --git a/vendor/src/github.com/minio/minio-go/api-s3-definitions.go b/vendor/src/github.com/minio/minio-go/api-s3-datatypes.go similarity index 89% rename from vendor/src/github.com/minio/minio-go/api-s3-definitions.go rename to vendor/src/github.com/minio/minio-go/api-s3-datatypes.go index de562e475..ca81e302d 100644 --- a/vendor/src/github.com/minio/minio-go/api-s3-definitions.go +++ b/vendor/src/github.com/minio/minio-go/api-s3-datatypes.go @@ -96,6 +96,12 @@ type initiator struct { DisplayName string } +// copyObjectResult container for copy object response. +type copyObjectResult struct { + ETag string + LastModified string // time string format "2006-01-02T15:04:05.000Z" +} + // objectPart container for particular part of an object. type objectPart struct { // Part number identifies the part. @@ -171,27 +177,3 @@ type createBucketConfiguration struct { XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ CreateBucketConfiguration" json:"-"` Location string `xml:"LocationConstraint"` } - -// grant container for the grantee and his or her permissions. -type grant struct { - // grantee container for DisplayName and ID of the person being - // granted permissions. - Grantee struct { - ID string - DisplayName string - EmailAddress string - Type string - URI string - } - Permission string -} - -// accessControlPolicy contains the elements providing ACL permissions -// for a bucket. -type accessControlPolicy struct { - // accessControlList container for ACL information. - AccessControlList struct { - Grant []grant - } - Owner owner -} diff --git a/vendor/src/github.com/minio/minio-go/api-stat.go b/vendor/src/github.com/minio/minio-go/api-stat.go index 20f66e8fc..b5db7fedc 100644 --- a/vendor/src/github.com/minio/minio-go/api-stat.go +++ b/vendor/src/github.com/minio/minio-go/api-stat.go @@ -1,5 +1,5 @@ /* - * Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015 Minio, Inc. + * Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015, 2016 Minio, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,15 +29,11 @@ func (c Client) BucketExists(bucketName string) error { if err := isValidBucketName(bucketName); err != nil { return err } - // Instantiate a new request. - req, err := c.newRequest("HEAD", requestMetadata{ + + // Execute HEAD on bucketName. + resp, err := c.executeMethod("HEAD", requestMetadata{ bucketName: bucketName, }) - if err != nil { - return err - } - // Initiate the request. - resp, err := c.do(req) defer closeResponse(resp) if err != nil { return err @@ -59,16 +55,12 @@ func (c Client) StatObject(bucketName, objectName string) (ObjectInfo, error) { if err := isValidObjectName(objectName); err != nil { return ObjectInfo{}, err } - // Instantiate a new request. - req, err := c.newRequest("HEAD", requestMetadata{ + + // Execute HEAD on objectName. + resp, err := c.executeMethod("HEAD", requestMetadata{ bucketName: bucketName, objectName: objectName, }) - if err != nil { - return ObjectInfo{}, err - } - // Initiate the request. - resp, err := c.do(req) defer closeResponse(resp) if err != nil { return ObjectInfo{}, err @@ -87,26 +79,26 @@ func (c Client) StatObject(bucketName, objectName string) (ObjectInfo, error) { size, err := strconv.ParseInt(resp.Header.Get("Content-Length"), 10, 64) if err != nil { return ObjectInfo{}, ErrorResponse{ - Code: "InternalError", - Message: "Content-Length is invalid. " + reportIssue, - BucketName: bucketName, - Key: objectName, - RequestID: resp.Header.Get("x-amz-request-id"), - HostID: resp.Header.Get("x-amz-id-2"), - AmzBucketRegion: resp.Header.Get("x-amz-bucket-region"), + Code: "InternalError", + Message: "Content-Length is invalid. " + reportIssue, + BucketName: bucketName, + Key: objectName, + RequestID: resp.Header.Get("x-amz-request-id"), + HostID: resp.Header.Get("x-amz-id-2"), + Region: resp.Header.Get("x-amz-bucket-region"), } } // Parse Last-Modified has http time format. date, err := time.Parse(http.TimeFormat, resp.Header.Get("Last-Modified")) if err != nil { return ObjectInfo{}, ErrorResponse{ - Code: "InternalError", - Message: "Last-Modified time format is invalid. " + reportIssue, - BucketName: bucketName, - Key: objectName, - RequestID: resp.Header.Get("x-amz-request-id"), - HostID: resp.Header.Get("x-amz-id-2"), - AmzBucketRegion: resp.Header.Get("x-amz-bucket-region"), + Code: "InternalError", + Message: "Last-Modified time format is invalid. " + reportIssue, + BucketName: bucketName, + Key: objectName, + RequestID: resp.Header.Get("x-amz-request-id"), + HostID: resp.Header.Get("x-amz-id-2"), + Region: resp.Header.Get("x-amz-bucket-region"), } } // Fetch content type if any present. diff --git a/vendor/src/github.com/minio/minio-go/api.go b/vendor/src/github.com/minio/minio-go/api.go index 96cab8c02..93c0a6d23 100644 --- a/vendor/src/github.com/minio/minio-go/api.go +++ b/vendor/src/github.com/minio/minio-go/api.go @@ -1,5 +1,5 @@ /* - * Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015 Minio, Inc. + * Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015, 2016 Minio, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,6 +22,8 @@ import ( "encoding/hex" "fmt" "io" + "io/ioutil" + "math/rand" "net/http" "net/http/httputil" "net/url" @@ -29,6 +31,7 @@ import ( "regexp" "runtime" "strings" + "sync" "time" ) @@ -56,15 +59,18 @@ type Client struct { httpClient *http.Client bucketLocCache *bucketLocationCache - // Advanced functionality + // Advanced functionality. isTraceEnabled bool traceOutput io.Writer + + // Random seed. + random *rand.Rand } // Global constants. const ( libraryName = "minio-go" - libraryVersion = "0.2.5" + libraryVersion = "1.0.1" ) // User Agent should always following the below style. @@ -78,7 +84,7 @@ const ( // NewV2 - instantiate minio client with Amazon S3 signature version // '2' compatibility. -func NewV2(endpoint string, accessKeyID, secretAccessKey string, insecure bool) (CloudStorageClient, error) { +func NewV2(endpoint string, accessKeyID, secretAccessKey string, insecure bool) (*Client, error) { clnt, err := privateNew(endpoint, accessKeyID, secretAccessKey, insecure) if err != nil { return nil, err @@ -90,7 +96,7 @@ func NewV2(endpoint string, accessKeyID, secretAccessKey string, insecure bool) // NewV4 - instantiate minio client with Amazon S3 signature version // '4' compatibility. -func NewV4(endpoint string, accessKeyID, secretAccessKey string, insecure bool) (CloudStorageClient, error) { +func NewV4(endpoint string, accessKeyID, secretAccessKey string, insecure bool) (*Client, error) { clnt, err := privateNew(endpoint, accessKeyID, secretAccessKey, insecure) if err != nil { return nil, err @@ -102,7 +108,7 @@ func NewV4(endpoint string, accessKeyID, secretAccessKey string, insecure bool) // New - instantiate minio client Client, adds automatic verification // of signature. -func New(endpoint string, accessKeyID, secretAccessKey string, insecure bool) (CloudStorageClient, error) { +func New(endpoint string, accessKeyID, secretAccessKey string, insecure bool) (*Client, error) { clnt, err := privateNew(endpoint, accessKeyID, secretAccessKey, insecure) if err != nil { return nil, err @@ -112,13 +118,36 @@ func New(endpoint string, accessKeyID, secretAccessKey string, insecure bool) (C if isGoogleEndpoint(clnt.endpointURL) { clnt.signature = SignatureV2 } - // If Amazon S3 set to signature v2. + // If Amazon S3 set to signature v2.n if isAmazonEndpoint(clnt.endpointURL) { clnt.signature = SignatureV4 } return clnt, nil } +// lockedRandSource provides protected rand source, implements rand.Source interface. +type lockedRandSource struct { + lk sync.Mutex + src rand.Source +} + +// Int63 returns a non-negative pseudo-random 63-bit integer as an +// int64. +func (r *lockedRandSource) Int63() (n int64) { + r.lk.Lock() + n = r.src.Int63() + r.lk.Unlock() + return +} + +// Seed uses the provided seed value to initialize the generator to a +// deterministic state. +func (r *lockedRandSource) Seed(seed int64) { + r.lk.Lock() + r.src.Seed(seed) + r.lk.Unlock() +} + func privateNew(endpoint, accessKeyID, secretAccessKey string, insecure bool) (*Client, error) { // construct endpoint. endpointURL, err := getEndpointURL(endpoint, insecure) @@ -138,9 +167,20 @@ func privateNew(endpoint, accessKeyID, secretAccessKey string, insecure bool) (* clnt.endpointURL = endpointURL // Instantiate http client and bucket location cache. - clnt.httpClient = &http.Client{} + clnt.httpClient = &http.Client{ + // Setting a sensible time out of 2minutes to wait for response + // headers. Request is pro-actively cancelled after 2minutes + // if no response was received from server. + Timeout: 2 * time.Minute, + Transport: http.DefaultTransport, + } + + // Instantiae bucket location cache. clnt.bucketLocCache = newBucketLocationCache() + // Introduce a new locked random seed. + clnt.random = rand.New(&lockedRandSource{src: rand.NewSource(time.Now().UTC().UnixNano())}) + // Return. return clnt, nil } @@ -180,6 +220,13 @@ func (c *Client) SetCustomTransport(customHTTPTransport http.RoundTripper) { } } +// SetClientTimeout - set http client timeout. +func (c *Client) SetClientTimeout(timeout time.Duration) { + if c.httpClient != nil { + c.httpClient.Timeout = timeout + } +} + // TraceOn - enable HTTP tracing. func (c *Client) TraceOn(outputStream io.Writer) { // if outputStream is nil then default to os.Stdout. @@ -214,7 +261,7 @@ type requestMetadata struct { // Generated by our internal code. bucketLocation string - contentBody io.ReadCloser + contentBody io.Reader contentLength int64 contentSHA256Bytes []byte contentMD5Bytes []byte @@ -292,7 +339,7 @@ func (c Client) dumpHTTP(req *http.Request, resp *http.Response) error { // to zero. Keep this workaround until the above bug is fixed. if resp.ContentLength == 0 { var buffer bytes.Buffer - if err := resp.Header.Write(&buffer); err != nil { + if err = resp.Header.Write(&buffer); err != nil { return err } respTrace = buffer.Bytes() @@ -322,11 +369,28 @@ func (c Client) dumpHTTP(req *http.Request, resp *http.Response) error { // do - execute http request. func (c Client) do(req *http.Request) (*http.Response, error) { - // execute the request. + // do the request. resp, err := c.httpClient.Do(req) if err != nil { - return resp, err + // Handle this specifically for now until future Golang + // versions fix this issue properly. + urlErr, ok := err.(*url.Error) + if ok && strings.Contains(urlErr.Err.Error(), "EOF") { + return nil, &url.Error{ + Op: urlErr.Op, + URL: urlErr.URL, + Err: fmt.Errorf("Connection closed by foreign host %s. Retry again.", urlErr.URL), + } + } + return nil, err } + + // Response cannot be non-nil, report if its the case. + if resp == nil { + msg := "Response is empty. " + reportIssue + return nil, ErrInvalidArgument(msg) + } + // If trace is enabled, dump http request and response. if c.isTraceEnabled { err = c.dumpHTTP(req, resp) @@ -337,6 +401,113 @@ func (c Client) do(req *http.Request) (*http.Response, error) { return resp, nil } +// List of success status. +var successStatus = []int{ + http.StatusOK, + http.StatusNoContent, + http.StatusPartialContent, +} + +// executeMethod - instantiates a given method, and retries the +// request upon any error up to maxRetries attempts in a binomially +// delayed manner using a standard back off algorithm. +func (c Client) executeMethod(method string, metadata requestMetadata) (res *http.Response, err error) { + var isRetryable bool // Indicates if request can be retried. + var bodySeeker io.Seeker // Extracted seeker from io.Reader. + if metadata.contentBody != nil { + // Check if body is seekable then it is retryable. + bodySeeker, isRetryable = metadata.contentBody.(io.Seeker) + } + + // Create a done channel to control 'ListObjects' go routine. + doneCh := make(chan struct{}, 1) + + // Indicate to our routine to exit cleanly upon return. + defer close(doneCh) + + // Blank indentifier is kept here on purpose since 'range' without + // blank identifiers is only supported since go1.4 + // https://golang.org/doc/go1.4#forrange. + for _ = range c.newRetryTimer(MaxRetry, time.Second, time.Second*30, MaxJitter, doneCh) { + // Retry executes the following function body if request has an + // error until maxRetries have been exhausted, retry attempts are + // performed after waiting for a given period of time in a + // binomial fashion. + if isRetryable { + // Seek back to beginning for each attempt. + if _, err = bodySeeker.Seek(0, 0); err != nil { + // If seek failed, no need to retry. + return nil, err + } + } + + // Instantiate a new request. + var req *http.Request + req, err = c.newRequest(method, metadata) + if err != nil { + errResponse := ToErrorResponse(err) + if isS3CodeRetryable(errResponse.Code) { + continue // Retry. + } + return nil, err + } + + // Initiate the request. + res, err = c.do(req) + if err != nil { + // For supported network errors verify. + if isNetErrorRetryable(err) { + continue // Retry. + } + // For other errors, return here no need to retry. + return nil, err + } + + // For any known successful http status, return quickly. + for _, httpStatus := range successStatus { + if httpStatus == res.StatusCode { + return res, nil + } + } + + // Read the body to be saved later. + errBodyBytes, err := ioutil.ReadAll(res.Body) + if err != nil { + return nil, err + } + // Save the body. + errBodySeeker := bytes.NewReader(errBodyBytes) + res.Body = ioutil.NopCloser(errBodySeeker) + + // For errors verify if its retryable otherwise fail quickly. + errResponse := ToErrorResponse(httpRespToErrorResponse(res, metadata.bucketName, metadata.objectName)) + // Bucket region if set in error response, we can retry the + // request with the new region. + if errResponse.Region != "" { + c.bucketLocCache.Set(metadata.bucketName, errResponse.Region) + continue // Retry. + } + + // Verify if error response code is retryable. + if isS3CodeRetryable(errResponse.Code) { + continue // Retry. + } + + // Verify if http status code is retryable. + if isHTTPStatusRetryable(res.StatusCode) { + continue // Retry. + } + + // Save the body back again. + errBodySeeker.Seek(0, 0) // Seek back to starting point. + res.Body = ioutil.NopCloser(errBodySeeker) + + // For all other cases break out of the retry loop. + break + } + return res, err +} + // newRequest - instantiate a new HTTP request for a given method. func (c Client) newRequest(method string, metadata requestMetadata) (req *http.Request, err error) { // If no method is supplied default to 'POST'. @@ -344,8 +515,17 @@ func (c Client) newRequest(method string, metadata requestMetadata) (req *http.R method = "POST" } + // Default all requests to "us-east-1" or "cn-north-1" (china region) + location := "us-east-1" + if isAmazonChinaEndpoint(c.endpointURL) { + // For china specifically we need to set everything to + // cn-north-1 for now, there is no easier way until AWS S3 + // provides a cleaner compatible API across "us-east-1" and + // China region. + location = "cn-north-1" + } + // Gather location only if bucketName is present. - location := "us-east-1" // Default all other requests to "us-east-1". if metadata.bucketName != "" { location, err = c.getBucketLocation(metadata.bucketName) if err != nil { @@ -385,10 +565,13 @@ func (c Client) newRequest(method string, metadata requestMetadata) (req *http.R // Set content body if available. if metadata.contentBody != nil { - req.Body = metadata.contentBody + req.Body = ioutil.NopCloser(metadata.contentBody) } - // set UserAgent for the request. + // set 'Expect' header for the request. + req.Header.Set("Expect", "100-continue") + + // set 'User-Agent' header for the request. c.setUserAgent(req) // Set all headers. @@ -415,7 +598,7 @@ func (c Client) newRequest(method string, metadata requestMetadata) (req *http.R // set md5Sum for content protection. if metadata.contentMD5Bytes != nil { - req.Header.Set("Content-MD5", base64.StdEncoding.EncodeToString(metadata.contentMD5Bytes)) + req.Header.Set("Content-Md5", base64.StdEncoding.EncodeToString(metadata.contentMD5Bytes)) } // Sign the request for all authenticated requests. @@ -478,55 +661,11 @@ func (c Client) makeTargetURL(bucketName, objectName, bucketLocation string, que } // If there are any query values, add them to the end. if len(queryValues) > 0 { - urlStr = urlStr + "?" + queryValues.Encode() + urlStr = urlStr + "?" + queryEncode(queryValues) } u, err := url.Parse(urlStr) if err != nil { return nil, err } - return u, nil } - -// CloudStorageClient - Cloud Storage Client interface. -type CloudStorageClient interface { - // Bucket Read/Write/Stat operations. - MakeBucket(bucketName string, cannedACL BucketACL, location string) error - BucketExists(bucketName string) error - RemoveBucket(bucketName string) error - SetBucketACL(bucketName string, cannedACL BucketACL) error - GetBucketACL(bucketName string) (BucketACL, error) - - ListBuckets() ([]BucketInfo, error) - ListObjects(bucket, prefix string, recursive bool, doneCh <-chan struct{}) <-chan ObjectInfo - ListIncompleteUploads(bucket, prefix string, recursive bool, doneCh <-chan struct{}) <-chan ObjectMultipartInfo - - // Object Read/Write/Stat operations. - GetObject(bucketName, objectName string) (reader *Object, err error) - PutObject(bucketName, objectName string, reader io.Reader, contentType string) (n int64, err error) - StatObject(bucketName, objectName string) (ObjectInfo, error) - RemoveObject(bucketName, objectName string) error - RemoveIncompleteUpload(bucketName, objectName string) error - - // File to Object API. - FPutObject(bucketName, objectName, filePath, contentType string) (n int64, err error) - FGetObject(bucketName, objectName, filePath string) error - - // PutObjectWithProgress for progress. - PutObjectWithProgress(bucketName, objectName string, reader io.Reader, contentType string, progress io.Reader) (n int64, err error) - - // Presigned operations. - PresignedGetObject(bucketName, objectName string, expires time.Duration) (presignedURL string, err error) - PresignedPutObject(bucketName, objectName string, expires time.Duration) (presignedURL string, err error) - PresignedPostPolicy(*PostPolicy) (formData map[string]string, err error) - - // Application info. - SetAppInfo(appName, appVersion string) - - // Set custom transport. - SetCustomTransport(customTransport http.RoundTripper) - - // HTTP tracing methods. - TraceOn(traceOutput io.Writer) - TraceOff() -} diff --git a/vendor/src/github.com/minio/minio-go/api_functional_v2_test.go b/vendor/src/github.com/minio/minio-go/api_functional_v2_test.go index 990e02810..7fbd97c31 100644 --- a/vendor/src/github.com/minio/minio-go/api_functional_v2_test.go +++ b/vendor/src/github.com/minio/minio-go/api_functional_v2_test.go @@ -24,6 +24,7 @@ import ( "io/ioutil" "math/rand" "net/http" + "net/url" "os" "testing" "time" @@ -61,10 +62,10 @@ func TestMakeBucketErrorV2(t *testing.T) { bucketName := randString(60, rand.NewSource(time.Now().UnixNano())) // Make a new bucket in 'eu-west-1'. - if err = c.MakeBucket(bucketName, "private", "eu-west-1"); err != nil { + if err = c.MakeBucket(bucketName, "eu-west-1"); err != nil { t.Fatal("Error:", err, bucketName) } - if err = c.MakeBucket(bucketName, "private", "eu-west-1"); err == nil { + if err = c.MakeBucket(bucketName, "eu-west-1"); err == nil { t.Fatal("Error: make bucket should should fail for", bucketName) } // Verify valid error response from server. @@ -107,7 +108,7 @@ func TestGetObjectClosedTwiceV2(t *testing.T) { bucketName := randString(60, rand.NewSource(time.Now().UnixNano())) // Make a new bucket. - err = c.MakeBucket(bucketName, "private", "us-east-1") + err = c.MakeBucket(bucketName, "us-east-1") if err != nil { t.Fatal("Error:", err, bucketName) } @@ -192,7 +193,7 @@ func TestRemovePartiallyUploadedV2(t *testing.T) { bucketName := randString(60, rand.NewSource(time.Now().UnixNano())) // make a new bucket. - err = c.MakeBucket(bucketName, "private", "us-east-1") + err = c.MakeBucket(bucketName, "us-east-1") if err != nil { t.Fatal("Error:", err, bucketName) } @@ -229,7 +230,7 @@ func TestRemovePartiallyUploadedV2(t *testing.T) { } // Tests resumable put object cloud to cloud. -func TestResumbalePutObjectV2(t *testing.T) { +func TestResumablePutObjectV2(t *testing.T) { // By passing 'go test -short' skips these tests. if testing.Short() { t.Skip("skipping functional tests for the short runs") @@ -259,7 +260,7 @@ func TestResumbalePutObjectV2(t *testing.T) { bucketName := randString(60, rand.NewSource(time.Now().UnixNano())) // Make a new bucket. - err = c.MakeBucket(bucketName, "private", "us-east-1") + err = c.MakeBucket(bucketName, "us-east-1") if err != nil { t.Fatal("Error:", err, bucketName) } @@ -340,6 +341,154 @@ func TestResumbalePutObjectV2(t *testing.T) { } +// Tests FPutObject hidden contentType setting +func TestFPutObjectV2(t *testing.T) { + if testing.Short() { + t.Skip("skipping functional tests for short runs") + } + + // Seed random based on current time. + rand.Seed(time.Now().Unix()) + + // Instantiate new minio client object. + c, err := minio.NewV2( + "s3.amazonaws.com", + os.Getenv("ACCESS_KEY"), + os.Getenv("SECRET_KEY"), + false, + ) + if err != nil { + t.Fatal("Error:", err) + } + + // Enable tracing, write to stderr. + // c.TraceOn(os.Stderr) + + // Set user agent. + c.SetAppInfo("Minio-go-FunctionalTest", "0.1.0") + + // Generate a new random bucket name. + bucketName := randString(60, rand.NewSource(time.Now().UnixNano())) + + // Make a new bucket. + err = c.MakeBucket(bucketName, "us-east-1") + if err != nil { + t.Fatal("Error:", err, bucketName) + } + + // Make a temp file with 11*1024*1024 bytes of data. + file, err := ioutil.TempFile(os.TempDir(), "FPutObjectTest") + if err != nil { + t.Fatal("Error:", err) + } + + n, err := io.CopyN(file, crand.Reader, 11*1024*1024) + if err != nil { + t.Fatal("Error:", err) + } + if n != int64(11*1024*1024) { + t.Fatalf("Error: number of bytes does not match, want %v, got %v\n", 11*1024*1024, n) + } + + // Close the file pro-actively for windows. + err = file.Close() + if err != nil { + t.Fatal("Error:", err) + } + + // Set base object name + objectName := bucketName + "FPutObject" + + // Perform standard FPutObject with contentType provided (Expecting application/octet-stream) + n, err = c.FPutObject(bucketName, objectName+"-standard", file.Name(), "application/octet-stream") + if err != nil { + t.Fatal("Error:", err) + } + if n != int64(11*1024*1024) { + t.Fatalf("Error: number of bytes does not match, want %v, got %v\n", 11*1024*1024, n) + } + + // Perform FPutObject with no contentType provided (Expecting application/octet-stream) + n, err = c.FPutObject(bucketName, objectName+"-Octet", file.Name(), "") + if err != nil { + t.Fatal("Error:", err) + } + if n != int64(11*1024*1024) { + t.Fatalf("Error: number of bytes does not match, want %v, got %v\n", 11*1024*1024, n) + } + + // Add extension to temp file name + fileName := file.Name() + err = os.Rename(file.Name(), fileName+".gtar") + if err != nil { + t.Fatal("Error:", err) + } + + // Perform FPutObject with no contentType provided (Expecting application/x-gtar) + n, err = c.FPutObject(bucketName, objectName+"-GTar", fileName+".gtar", "") + if err != nil { + t.Fatal("Error:", err) + } + if n != int64(11*1024*1024) { + t.Fatalf("Error: number of bytes does not match, want %v, got %v\n", 11*1024*1024, n) + } + + // Check headers + rStandard, err := c.StatObject(bucketName, objectName+"-standard") + if err != nil { + t.Fatal("Error:", err, bucketName, objectName+"-standard") + } + if rStandard.ContentType != "application/octet-stream" { + t.Fatalf("Error: Content-Type headers mismatched, want %v, got %v\n", + "application/octet-stream", rStandard.ContentType) + } + + rOctet, err := c.StatObject(bucketName, objectName+"-Octet") + if err != nil { + t.Fatal("Error:", err, bucketName, objectName+"-Octet") + } + if rOctet.ContentType != "application/octet-stream" { + t.Fatalf("Error: Content-Type headers mismatched, want %v, got %v\n", + "application/octet-stream", rStandard.ContentType) + } + + rGTar, err := c.StatObject(bucketName, objectName+"-GTar") + if err != nil { + t.Fatal("Error:", err, bucketName, objectName+"-GTar") + } + if rGTar.ContentType != "application/x-gtar" { + t.Fatalf("Error: Content-Type headers mismatched, want %v, got %v\n", + "application/x-gtar", rStandard.ContentType) + } + + // Remove all objects and bucket and temp file + err = c.RemoveObject(bucketName, objectName+"-standard") + if err != nil { + t.Fatal("Error: ", err) + } + + err = c.RemoveObject(bucketName, objectName+"-Octet") + if err != nil { + t.Fatal("Error: ", err) + } + + err = c.RemoveObject(bucketName, objectName+"-GTar") + if err != nil { + t.Fatal("Error: ", err) + } + + err = c.RemoveBucket(bucketName) + if err != nil { + t.Fatal("Error:", err) + } + + err = os.Remove(fileName + ".gtar") + if err != nil { + t.Fatal("Error:", err) + } + +} + // Tests resumable file based put object multipart upload. func TestResumableFPutObjectV2(t *testing.T) { if testing.Short() { @@ -370,7 +519,7 @@ func TestResumableFPutObjectV2(t *testing.T) { bucketName := randString(60, rand.NewSource(time.Now().UnixNano())) // make a new bucket. - err = c.MakeBucket(bucketName, "private", "us-east-1") + err = c.MakeBucket(bucketName, "us-east-1") if err != nil { t.Fatal("Error:", err, bucketName) } @@ -447,7 +596,7 @@ func TestMakeBucketRegionsV2(t *testing.T) { bucketName := randString(60, rand.NewSource(time.Now().UnixNano())) // Make a new bucket in 'eu-central-1'. - if err = c.MakeBucket(bucketName, "private", "eu-west-1"); err != nil { + if err = c.MakeBucket(bucketName, "eu-west-1"); err != nil { t.Fatal("Error:", err, bucketName) } @@ -458,7 +607,7 @@ func TestMakeBucketRegionsV2(t *testing.T) { // Make a new bucket with '.' in its name, in 'us-west-2'. This // request is internally staged into a path style instead of // virtual host style. - if err = c.MakeBucket(bucketName+".withperiod", "private", "us-west-2"); err != nil { + if err = c.MakeBucket(bucketName+".withperiod", "us-west-2"); err != nil { t.Fatal("Error:", err, bucketName+".withperiod") } @@ -468,70 +617,6 @@ func TestMakeBucketRegionsV2(t *testing.T) { } } -// Tests resumable put object multipart upload. -func TestResumablePutObjectV2(t *testing.T) { - if testing.Short() { - t.Skip("skipping functional tests for the short runs") - } - - // Seed random based on current time. - rand.Seed(time.Now().Unix()) - - // Instantiate new minio client object. - c, err := minio.NewV2( - "s3.amazonaws.com", - os.Getenv("ACCESS_KEY"), - os.Getenv("SECRET_KEY"), - false, - ) - if err != nil { - t.Fatal("Error:", err) - } - - // Enable tracing, write to stderr. - // c.TraceOn(os.Stderr) - - // Set user agent. - c.SetAppInfo("Minio-go-FunctionalTest", "0.1.0") - - // Generate a new random bucket name. - bucketName := randString(60, rand.NewSource(time.Now().UnixNano())) - - // make a new bucket. - err = c.MakeBucket(bucketName, "private", "us-east-1") - if err != nil { - t.Fatal("Error:", err, bucketName) - } - - // generate 11MB - buf := make([]byte, 11*1024*1024) - - _, err = io.ReadFull(crand.Reader, buf) - if err != nil { - t.Fatal("Error:", err) - } - - objectName := bucketName + "-resumable" - reader := bytes.NewReader(buf) - n, err := c.PutObject(bucketName, objectName, reader, "application/octet-stream") - if err != nil { - t.Fatal("Error:", err, bucketName, objectName) - } - if n != int64(len(buf)) { - t.Fatalf("Error: number of bytes does not match, want %v, got %v\n", len(buf), n) - } - - err = c.RemoveObject(bucketName, objectName) - if err != nil { - t.Fatal("Error: ", err) - } - - err = c.RemoveBucket(bucketName) - if err != nil { - t.Fatal("Error:", err) - } -} - // Tests get object ReaderSeeker interface methods. func TestGetObjectReadSeekFunctionalV2(t *testing.T) { if testing.Short() { @@ -562,7 +647,7 @@ func TestGetObjectReadSeekFunctionalV2(t *testing.T) { bucketName := randString(60, rand.NewSource(time.Now().UnixNano())) // Make a new bucket. - err = c.MakeBucket(bucketName, "private", "us-east-1") + err = c.MakeBucket(bucketName, "us-east-1") if err != nil { t.Fatal("Error:", err, bucketName) } @@ -629,13 +714,37 @@ func TestGetObjectReadSeekFunctionalV2(t *testing.T) { if n != 0 { t.Fatalf("Error: number of bytes seeked back does not match, want 0, got %v\n", n) } - var buffer bytes.Buffer - if _, err = io.CopyN(&buffer, r, st.Size); err != nil { - t.Fatal("Error:", err) + + var buffer1 bytes.Buffer + if n, err = io.CopyN(&buffer1, r, st.Size); err != nil { + if err != io.EOF { + t.Fatal("Error:", err) + } } - if !bytes.Equal(buf, buffer.Bytes()) { + if !bytes.Equal(buf, buffer1.Bytes()) { t.Fatal("Error: Incorrect read bytes v/s original buffer.") } + + // Seek again and read again. + n, err = r.Seek(offset-1, 0) + if err != nil { + t.Fatal("Error:", err) + } + if n != (offset - 1) { + t.Fatalf("Error: number of bytes seeked back does not match, want %v, got %v\n", offset-1, n) + } + + var buffer2 bytes.Buffer + if _, err = io.CopyN(&buffer2, r, st.Size); err != nil { + if err != io.EOF { + t.Fatal("Error:", err) + } + } + // Verify now lesser bytes. + if !bytes.Equal(buf[2047:], buffer2.Bytes()) { + t.Fatal("Error: Incorrect read bytes v/s original buffer.") + } + err = c.RemoveObject(bucketName, objectName) if err != nil { t.Fatal("Error: ", err) @@ -676,7 +785,7 @@ func TestGetObjectReadAtFunctionalV2(t *testing.T) { bucketName := randString(60, rand.NewSource(time.Now().UnixNano())) // Make a new bucket. - err = c.MakeBucket(bucketName, "private", "us-east-1") + err = c.MakeBucket(bucketName, "us-east-1") if err != nil { t.Fatal("Error:", err, bucketName) } @@ -788,6 +897,132 @@ func TestGetObjectReadAtFunctionalV2(t *testing.T) { } } +// Tests copy object +func TestCopyObjectV2(t *testing.T) { + if testing.Short() { + t.Skip("Skipping functional tests for short runs") + } + // Seed random based on current time. + rand.Seed(time.Now().Unix()) + + // Instantiate new minio client object + c, err := minio.NewV2( + "s3.amazonaws.com", + os.Getenv("ACCESS_KEY"), + os.Getenv("SECRET_KEY"), + false, + ) + if err != nil { + t.Fatal("Error:", err) + } + + // Enable tracing, write to stderr. + // c.TraceOn(os.Stderr) + + // Set user agent. + c.SetAppInfo("Minio-go-FunctionalTest", "0.1.0") + + // Generate a new random bucket name. + bucketName := randString(60, rand.NewSource(time.Now().UnixNano())) + + // Make a new bucket in 'us-east-1' (source bucket). + err = c.MakeBucket(bucketName, "us-east-1") + if err != nil { + t.Fatal("Error:", err, bucketName) + } + + // Make a new bucket in 'us-east-1' (destination bucket). + err = c.MakeBucket(bucketName+"-copy", "us-east-1") + if err != nil { + t.Fatal("Error:", err, bucketName+"-copy") + } + + // Generate data more than 32K + buf := make([]byte, rand.Intn(1<<20)+32*1024) + + _, err = io.ReadFull(crand.Reader, buf) + if err != nil { + t.Fatal("Error:", err) + } + + // Save the data + objectName := randString(60, rand.NewSource(time.Now().UnixNano())) + n, err := c.PutObject(bucketName, objectName, bytes.NewReader(buf), "binary/octet-stream") + if err != nil { + t.Fatal("Error:", err, bucketName, objectName) + } + + if n != int64(len(buf)) { + t.Fatalf("Error: number of bytes does not match want %v, got %v", + len(buf), n) + } + + // Set copy conditions. + copyConds := minio.NewCopyConditions() + err = copyConds.SetModified(time.Date(2014, time.April, 0, 0, 0, 0, 0, time.UTC)) + if err != nil { + t.Fatal("Error:", err) + } + + // Copy source. + copySource := bucketName + "/" + objectName + + // Perform the Copy + err = c.CopyObject(bucketName+"-copy", objectName+"-copy", copySource, copyConds) + if err != nil { + t.Fatal("Error:", err, bucketName+"-copy", objectName+"-copy") + } + + // Source object + reader, err := c.GetObject(bucketName, objectName) + if err != nil { + t.Fatal("Error:", err) + } + // Destination object + readerCopy, err := c.GetObject(bucketName+"-copy", objectName+"-copy") + if err != nil { + t.Fatal("Error:", err) + } + // Check the various fields of source object against destination object. + objInfo, err := reader.Stat() + if err != nil { + t.Fatal("Error:", err) + } + objInfoCopy, err := readerCopy.Stat() + if err != nil { + t.Fatal("Error:", err) + } + if objInfo.Size != objInfoCopy.Size { + t.Fatalf("Error: number of bytes does not match, want %v, got %v\n", + objInfo.Size, objInfoCopy.Size) + } + if objInfo.ETag != objInfoCopy.ETag { + t.Fatalf("Error: ETags do not match, want %v, got %v\n", + objInfoCopy.ETag, objInfo.ETag) + } + + // Remove all objects and buckets + err = c.RemoveObject(bucketName, objectName) + if err != nil { + t.Fatal("Error:", err) + } + + err = c.RemoveObject(bucketName+"-copy", objectName+"-copy") + if err != nil { + t.Fatal("Error:", err) + } + + err = c.RemoveBucket(bucketName) + if err != nil { + t.Fatal("Error:", err) + } + + err = c.RemoveBucket(bucketName + "-copy") + if err != nil { + t.Fatal("Error:", err) + } +} + // Tests comprehensive list of all methods. func TestFunctionalV2(t *testing.T) { if testing.Short() { @@ -817,7 +1052,7 @@ func TestFunctionalV2(t *testing.T) { bucketName := randString(60, rand.NewSource(time.Now().UnixNano())) // Make a new bucket. - err = c.MakeBucket(bucketName, "private", "us-east-1") + err = c.MakeBucket(bucketName, "us-east-1") if err != nil { t.Fatal("Error:", err, bucketName) } @@ -844,22 +1079,11 @@ func TestFunctionalV2(t *testing.T) { } // Make the bucket 'public read/write'. - err = c.SetBucketACL(bucketName, "public-read-write") + err = c.SetBucketPolicy(bucketName, "", minio.BucketPolicyReadWrite) if err != nil { t.Fatal("Error:", err) } - // Get the previously set acl. - acl, err := c.GetBucketACL(bucketName) - if err != nil { - t.Fatal("Error:", err) - } - - // ACL must be 'public read/write'. - if acl != minio.BucketACL("public-read-write") { - t.Fatal("Error:", acl) - } - // List all buckets. buckets, err := c.ListBuckets() if len(buckets) == 0 { @@ -954,11 +1178,12 @@ func TestFunctionalV2(t *testing.T) { t.Fatal("Error: ", err) } - presignedGetURL, err := c.PresignedGetObject(bucketName, objectName, 3600*time.Second) + // Generate presigned GET object url. + presignedGetURL, err := c.PresignedGetObject(bucketName, objectName, 3600*time.Second, nil) if err != nil { t.Fatal("Error: ", err) } - + // Verify if presigned url works. resp, err := http.Get(presignedGetURL) if err != nil { t.Fatal("Error: ", err) @@ -974,6 +1199,34 @@ func TestFunctionalV2(t *testing.T) { t.Fatal("Error: bytes mismatch.") } + // Set request parameters. + reqParams := make(url.Values) + reqParams.Set("response-content-disposition", "attachment; filename=\"test.txt\"") + // Generate presigned GET object url. + presignedGetURL, err = c.PresignedGetObject(bucketName, objectName, 3600*time.Second, reqParams) + if err != nil { + t.Fatal("Error: ", err) + } + // Verify if presigned url works. + resp, err = http.Get(presignedGetURL) + if err != nil { + t.Fatal("Error: ", err) + } + if resp.StatusCode != http.StatusOK { + t.Fatal("Error: ", resp.Status) + } + newPresignedBytes, err = ioutil.ReadAll(resp.Body) + if err != nil { + t.Fatal("Error: ", err) + } + if !bytes.Equal(newPresignedBytes, buf) { + t.Fatal("Error: bytes mismatch for presigned GET url.") + } + // Verify content disposition. + if resp.Header.Get("Content-Disposition") != "attachment; filename=\"test.txt\"" { + t.Fatalf("Error: wrong Content-Disposition received %s", resp.Header.Get("Content-Disposition")) + } + presignedPutURL, err := c.PresignedPutObject(bucketName, objectName+"-presigned", 3600*time.Second) if err != nil { t.Fatal("Error: ", err) @@ -987,7 +1240,13 @@ func TestFunctionalV2(t *testing.T) { if err != nil { t.Fatal("Error: ", err) } - httpClient := &http.Client{} + httpClient := &http.Client{ + // Setting a sensible time out of 30secs to wait for response + // headers. Request is pro-actively cancelled after 30secs + // with no response. + Timeout: 30 * time.Second, + Transport: http.DefaultTransport, + } resp, err = httpClient.Do(req) if err != nil { t.Fatal("Error: ", err) diff --git a/vendor/src/github.com/minio/minio-go/api_functional_v4_test.go b/vendor/src/github.com/minio/minio-go/api_functional_v4_test.go index 5e88c6124..7126dd7a9 100644 --- a/vendor/src/github.com/minio/minio-go/api_functional_v4_test.go +++ b/vendor/src/github.com/minio/minio-go/api_functional_v4_test.go @@ -24,6 +24,7 @@ import ( "io/ioutil" "math/rand" "net/http" + "net/url" "os" "testing" "time" @@ -85,10 +86,10 @@ func TestMakeBucketError(t *testing.T) { bucketName := randString(60, rand.NewSource(time.Now().UnixNano())) // Make a new bucket in 'eu-central-1'. - if err = c.MakeBucket(bucketName, "private", "eu-central-1"); err != nil { + if err = c.MakeBucket(bucketName, "eu-central-1"); err != nil { t.Fatal("Error:", err, bucketName) } - if err = c.MakeBucket(bucketName, "private", "eu-central-1"); err == nil { + if err = c.MakeBucket(bucketName, "eu-central-1"); err == nil { t.Fatal("Error: make bucket should should fail for", bucketName) } // Verify valid error response from server. @@ -131,7 +132,7 @@ func TestMakeBucketRegions(t *testing.T) { bucketName := randString(60, rand.NewSource(time.Now().UnixNano())) // Make a new bucket in 'eu-central-1'. - if err = c.MakeBucket(bucketName, "private", "eu-central-1"); err != nil { + if err = c.MakeBucket(bucketName, "eu-central-1"); err != nil { t.Fatal("Error:", err, bucketName) } @@ -142,7 +143,7 @@ func TestMakeBucketRegions(t *testing.T) { // Make a new bucket with '.' in its name, in 'us-west-2'. This // request is internally staged into a path style instead of // virtual host style. - if err = c.MakeBucket(bucketName+".withperiod", "private", "us-west-2"); err != nil { + if err = c.MakeBucket(bucketName+".withperiod", "us-west-2"); err != nil { t.Fatal("Error:", err, bucketName+".withperiod") } @@ -182,7 +183,7 @@ func TestGetObjectClosedTwice(t *testing.T) { bucketName := randString(60, rand.NewSource(time.Now().UnixNano())) // Make a new bucket. - err = c.MakeBucket(bucketName, "private", "us-east-1") + err = c.MakeBucket(bucketName, "us-east-1") if err != nil { t.Fatal("Error:", err, bucketName) } @@ -267,7 +268,7 @@ func TestRemovePartiallyUploaded(t *testing.T) { bucketName := randString(60, rand.NewSource(time.Now().UnixNano())) // Make a new bucket. - err = c.MakeBucket(bucketName, "private", "us-east-1") + err = c.MakeBucket(bucketName, "us-east-1") if err != nil { t.Fatal("Error:", err, bucketName) } @@ -307,7 +308,7 @@ func TestRemovePartiallyUploaded(t *testing.T) { } // Tests resumable put object cloud to cloud. -func TestResumbalePutObject(t *testing.T) { +func TestResumablePutObject(t *testing.T) { // By passing 'go test -short' skips these tests. if testing.Short() { t.Skip("skipping functional tests for the short runs") @@ -337,7 +338,7 @@ func TestResumbalePutObject(t *testing.T) { bucketName := randString(60, rand.NewSource(time.Now().UnixNano())) // Make a new bucket. - err = c.MakeBucket(bucketName, "private", "us-east-1") + err = c.MakeBucket(bucketName, "us-east-1") if err != nil { t.Fatal("Error:", err, bucketName) } @@ -447,7 +448,7 @@ func TestResumableFPutObject(t *testing.T) { bucketName := randString(60, rand.NewSource(time.Now().UnixNano())) // Make a new bucket. - err = c.MakeBucket(bucketName, "private", "us-east-1") + err = c.MakeBucket(bucketName, "us-east-1") if err != nil { t.Fatal("Error:", err, bucketName) } @@ -497,10 +498,10 @@ func TestResumableFPutObject(t *testing.T) { } } -// Tests resumable put object multipart upload. -func TestResumablePutObject(t *testing.T) { +// Tests FPutObject hidden contentType setting +func TestFPutObject(t *testing.T) { if testing.Short() { - t.Skip("skipping functional tests for the short runs") + t.Skip("skipping functional tests for short runs") } // Seed random based on current time. @@ -527,30 +528,108 @@ func TestResumablePutObject(t *testing.T) { bucketName := randString(60, rand.NewSource(time.Now().UnixNano())) // Make a new bucket. - err = c.MakeBucket(bucketName, "private", "us-east-1") + err = c.MakeBucket(bucketName, "us-east-1") if err != nil { t.Fatal("Error:", err, bucketName) } - // Generate 11MB - buf := make([]byte, 11*1024*1024) - - _, err = io.ReadFull(crand.Reader, buf) + // Make a temp file with 11*1024*1024 bytes of data. + file, err := ioutil.TempFile(os.TempDir(), "FPutObjectTest") if err != nil { t.Fatal("Error:", err) } - objectName := bucketName + "-resumable" - reader := bytes.NewReader(buf) - n, err := c.PutObject(bucketName, objectName, reader, "application/octet-stream") + n, err := io.CopyN(file, crand.Reader, 11*1024*1024) if err != nil { - t.Fatal("Error:", err, bucketName, objectName) + t.Fatal("Error:", err) } - if n != int64(len(buf)) { - t.Fatalf("Error: number of bytes does not match, want %v, got %v\n", len(buf), n) + if n != int64(11*1024*1024) { + t.Fatalf("Error: number of bytes does not match, want %v, got %v\n", 11*1024*1024, n) } - err = c.RemoveObject(bucketName, objectName) + // Close the file pro-actively for windows. + err = file.Close() + if err != nil { + t.Fatal("Error:", err) + } + + // Set base object name + objectName := bucketName + "FPutObject" + + // Perform standard FPutObject with contentType provided (Expecting application/octet-stream) + n, err = c.FPutObject(bucketName, objectName+"-standard", file.Name(), "application/octet-stream") + if err != nil { + t.Fatal("Error:", err) + } + if n != int64(11*1024*1024) { + t.Fatalf("Error: number of bytes does not match, want %v, got %v\n", 11*1024*1024, n) + } + + // Perform FPutObject with no contentType provided (Expecting application/octet-stream) + n, err = c.FPutObject(bucketName, objectName+"-Octet", file.Name(), "") + if err != nil { + t.Fatal("Error:", err) + } + if n != int64(11*1024*1024) { + t.Fatalf("Error: number of bytes does not match, want %v, got %v\n", 11*1024*1024, n) + } + + // Add extension to temp file name + fileName := file.Name() + err = os.Rename(file.Name(), fileName+".gtar") + if err != nil { + t.Fatal("Error:", err) + } + + // Perform FPutObject with no contentType provided (Expecting application/x-gtar) + n, err = c.FPutObject(bucketName, objectName+"-GTar", fileName+".gtar", "") + if err != nil { + t.Fatal("Error:", err) + } + if n != int64(11*1024*1024) { + t.Fatalf("Error: number of bytes does not match, want %v, got %v\n", 11*1024*1024, n) + } + + // Check headers + rStandard, err := c.StatObject(bucketName, objectName+"-standard") + if err != nil { + t.Fatal("Error:", err, bucketName, objectName+"-standard") + } + if rStandard.ContentType != "application/octet-stream" { + t.Fatalf("Error: Content-Type headers mismatched, want %v, got %v\n", + "application/octet-stream", rStandard.ContentType) + } + + rOctet, err := c.StatObject(bucketName, objectName+"-Octet") + if err != nil { + t.Fatal("Error:", err, bucketName, objectName+"-Octet") + } + if rOctet.ContentType != "application/octet-stream" { + t.Fatalf("Error: Content-Type headers mismatched, want %v, got %v\n", + "application/octet-stream", rStandard.ContentType) + } + + rGTar, err := c.StatObject(bucketName, objectName+"-GTar") + if err != nil { + t.Fatal("Error:", err, bucketName, objectName+"-GTar") + } + if rGTar.ContentType != "application/x-gtar" { + t.Fatalf("Error: Content-Type headers mismatched, want %v, got %v\n", + "application/x-gtar", rStandard.ContentType) + } + + // Remove all objects and bucket and temp file + err = c.RemoveObject(bucketName, objectName+"-standard") + if err != nil { + t.Fatal("Error: ", err) + } + + err = c.RemoveObject(bucketName, objectName+"-Octet") + if err != nil { + t.Fatal("Error: ", err) + } + + err = c.RemoveObject(bucketName, objectName+"-GTar") if err != nil { t.Fatal("Error: ", err) } @@ -559,6 +638,12 @@ func TestResumablePutObject(t *testing.T) { if err != nil { t.Fatal("Error:", err) } + + err = os.Remove(fileName + ".gtar") + if err != nil { + t.Fatal("Error:", err) + } + } // Tests get object ReaderSeeker interface methods. @@ -591,7 +676,7 @@ func TestGetObjectReadSeekFunctional(t *testing.T) { bucketName := randString(60, rand.NewSource(time.Now().UnixNano())) // Make a new bucket. - err = c.MakeBucket(bucketName, "private", "us-east-1") + err = c.MakeBucket(bucketName, "us-east-1") if err != nil { t.Fatal("Error:", err, bucketName) } @@ -658,13 +743,37 @@ func TestGetObjectReadSeekFunctional(t *testing.T) { if n != 0 { t.Fatalf("Error: number of bytes seeked back does not match, want 0, got %v\n", n) } - var buffer bytes.Buffer - if _, err = io.CopyN(&buffer, r, st.Size); err != nil { - t.Fatal("Error:", err) + + var buffer1 bytes.Buffer + if n, err = io.CopyN(&buffer1, r, st.Size); err != nil { + if err != io.EOF { + t.Fatal("Error:", err) + } } - if !bytes.Equal(buf, buffer.Bytes()) { + if !bytes.Equal(buf, buffer1.Bytes()) { t.Fatal("Error: Incorrect read bytes v/s original buffer.") } + + // Seek again and read again. + n, err = r.Seek(offset-1, 0) + if err != nil { + t.Fatal("Error:", err) + } + if n != (offset - 1) { + t.Fatalf("Error: number of bytes seeked back does not match, want %v, got %v\n", offset-1, n) + } + + var buffer2 bytes.Buffer + if _, err = io.CopyN(&buffer2, r, st.Size); err != nil { + if err != io.EOF { + t.Fatal("Error:", err) + } + } + // Verify now lesser bytes. + if !bytes.Equal(buf[2047:], buffer2.Bytes()) { + t.Fatal("Error: Incorrect read bytes v/s original buffer.") + } + err = c.RemoveObject(bucketName, objectName) if err != nil { t.Fatal("Error: ", err) @@ -705,7 +814,7 @@ func TestGetObjectReadAtFunctional(t *testing.T) { bucketName := randString(60, rand.NewSource(time.Now().UnixNano())) // Make a new bucket. - err = c.MakeBucket(bucketName, "private", "us-east-1") + err = c.MakeBucket(bucketName, "us-east-1") if err != nil { t.Fatal("Error:", err, bucketName) } @@ -817,6 +926,132 @@ func TestGetObjectReadAtFunctional(t *testing.T) { } } +// Tests copy object +func TestCopyObject(t *testing.T) { + if testing.Short() { + t.Skip("Skipping functional tests for short runs") + } + // Seed random based on current time. + rand.Seed(time.Now().Unix()) + + // Instantiate new minio client object + c, err := minio.NewV4( + "s3.amazonaws.com", + os.Getenv("ACCESS_KEY"), + os.Getenv("SECRET_KEY"), + false, + ) + if err != nil { + t.Fatal("Error:", err) + } + + // Enable tracing, write to stderr. + // c.TraceOn(os.Stderr) + + // Set user agent. + c.SetAppInfo("Minio-go-FunctionalTest", "0.1.0") + + // Generate a new random bucket name. + bucketName := randString(60, rand.NewSource(time.Now().UnixNano())) + + // Make a new bucket in 'us-east-1' (source bucket). + err = c.MakeBucket(bucketName, "us-east-1") + if err != nil { + t.Fatal("Error:", err, bucketName) + } + + // Make a new bucket in 'us-east-1' (destination bucket). + err = c.MakeBucket(bucketName+"-copy", "us-east-1") + if err != nil { + t.Fatal("Error:", err, bucketName+"-copy") + } + + // Generate data more than 32K + buf := make([]byte, rand.Intn(1<<20)+32*1024) + + _, err = io.ReadFull(crand.Reader, buf) + if err != nil { + t.Fatal("Error:", err) + } + + // Save the data + objectName := randString(60, rand.NewSource(time.Now().UnixNano())) + n, err := c.PutObject(bucketName, objectName, bytes.NewReader(buf), "binary/octet-stream") + if err != nil { + t.Fatal("Error:", err, bucketName, objectName) + } + + if n != int64(len(buf)) { + t.Fatalf("Error: number of bytes does not match want %v, got %v", + len(buf), n) + } + + // Set copy conditions. + copyConds := minio.NewCopyConditions() + err = copyConds.SetModified(time.Date(2014, time.April, 0, 0, 0, 0, 0, time.UTC)) + if err != nil { + t.Fatal("Error:", err) + } + + // Copy source. + copySource := bucketName + "/" + objectName + + // Perform the Copy + err = c.CopyObject(bucketName+"-copy", objectName+"-copy", copySource, copyConds) + if err != nil { + t.Fatal("Error:", err, bucketName+"-copy", objectName+"-copy") + } + + // Source object + reader, err := c.GetObject(bucketName, objectName) + if err != nil { + t.Fatal("Error:", err) + } + // Destination object + readerCopy, err := c.GetObject(bucketName+"-copy", objectName+"-copy") + if err != nil { + t.Fatal("Error:", err) + } + // Check the various fields of source object against destination object. + objInfo, err := reader.Stat() + if err != nil { + t.Fatal("Error:", err) + } + objInfoCopy, err := readerCopy.Stat() + if err != nil { + t.Fatal("Error:", err) + } + if objInfo.Size != objInfoCopy.Size { + t.Fatalf("Error: number of bytes does not match, want %v, got %v\n", + objInfo.Size, objInfoCopy.Size) + } + if objInfo.ETag != objInfoCopy.ETag { + t.Fatalf("Error: ETags do not match, want %v, got %v\n", + objInfoCopy.ETag, objInfo.ETag) + } + + // Remove all objects and buckets + err = c.RemoveObject(bucketName, objectName) + if err != nil { + t.Fatal("Error:", err) + } + + err = c.RemoveObject(bucketName+"-copy", objectName+"-copy") + if err != nil { + t.Fatal("Error:", err) + } + + err = c.RemoveBucket(bucketName) + if err != nil { + t.Fatal("Error:", err) + } + + err = c.RemoveBucket(bucketName + "-copy") + if err != nil { + t.Fatal("Error:", err) + } +} + // Tests comprehensive list of all methods. func TestFunctional(t *testing.T) { if testing.Short() { @@ -846,7 +1081,7 @@ func TestFunctional(t *testing.T) { bucketName := randString(60, rand.NewSource(time.Now().UnixNano())) // Make a new bucket. - err = c.MakeBucket(bucketName, "private", "us-east-1") + err = c.MakeBucket(bucketName, "us-east-1") if err != nil { t.Fatal("Error:", err, bucketName) } @@ -872,23 +1107,54 @@ func TestFunctional(t *testing.T) { t.Fatal("Error:", err, bucketName) } + // Asserting the default bucket policy. + policy, err := c.GetBucketPolicy(bucketName, "") + if err != nil { + t.Fatal("Error:", err) + } + if policy != "none" { + t.Fatalf("Default bucket policy incorrect") + } + // Set the bucket policy to 'public readonly'. + err = c.SetBucketPolicy(bucketName, "", minio.BucketPolicyReadOnly) + if err != nil { + t.Fatal("Error:", err) + } + // should return policy `readonly`. + policy, err = c.GetBucketPolicy(bucketName, "") + if err != nil { + t.Fatal("Error:", err) + } + if policy != "readonly" { + t.Fatalf("Expected bucket policy to be readonly") + } + + // Make the bucket 'public writeonly'. + err = c.SetBucketPolicy(bucketName, "", minio.BucketPolicyWriteOnly) + if err != nil { + t.Fatal("Error:", err) + } + // should return policy `writeonly`. + policy, err = c.GetBucketPolicy(bucketName, "") + if err != nil { + t.Fatal("Error:", err) + } + if policy != "writeonly" { + t.Fatalf("Expected bucket policy to be writeonly") + } // Make the bucket 'public read/write'. - err = c.SetBucketACL(bucketName, "public-read-write") + err = c.SetBucketPolicy(bucketName, "", minio.BucketPolicyReadWrite) if err != nil { t.Fatal("Error:", err) } - - // Get the previously set acl. - acl, err := c.GetBucketACL(bucketName) + // should return policy `readwrite`. + policy, err = c.GetBucketPolicy(bucketName, "") if err != nil { t.Fatal("Error:", err) } - - // ACL must be 'public read/write'. - if acl != minio.BucketACL("public-read-write") { - t.Fatal("Error:", acl) + if policy != "readwrite" { + t.Fatalf("Expected bucket policy to be readwrite") } - // List all buckets. buckets, err := c.ListBuckets() if len(buckets) == 0 { @@ -983,11 +1249,13 @@ func TestFunctional(t *testing.T) { t.Fatal("Error: ", err) } - presignedGetURL, err := c.PresignedGetObject(bucketName, objectName, 3600*time.Second) + // Generate presigned GET object url. + presignedGetURL, err := c.PresignedGetObject(bucketName, objectName, 3600*time.Second, nil) if err != nil { t.Fatal("Error: ", err) } + // Verify if presigned url works. resp, err := http.Get(presignedGetURL) if err != nil { t.Fatal("Error: ", err) @@ -1003,6 +1271,32 @@ func TestFunctional(t *testing.T) { t.Fatal("Error: bytes mismatch.") } + // Set request parameters. + reqParams := make(url.Values) + reqParams.Set("response-content-disposition", "attachment; filename=\"test.txt\"") + presignedGetURL, err = c.PresignedGetObject(bucketName, objectName, 3600*time.Second, reqParams) + if err != nil { + t.Fatal("Error: ", err) + } + // Verify if presigned url works. + resp, err = http.Get(presignedGetURL) + if err != nil { + t.Fatal("Error: ", err) + } + if resp.StatusCode != http.StatusOK { + t.Fatal("Error: ", resp.Status) + } + newPresignedBytes, err = ioutil.ReadAll(resp.Body) + if err != nil { + t.Fatal("Error: ", err) + } + if !bytes.Equal(newPresignedBytes, buf) { + t.Fatal("Error: bytes mismatch for presigned GET URL.") + } + if resp.Header.Get("Content-Disposition") != "attachment; filename=\"test.txt\"" { + t.Fatalf("Error: wrong Content-Disposition received %s", resp.Header.Get("Content-Disposition")) + } + presignedPutURL, err := c.PresignedPutObject(bucketName, objectName+"-presigned", 3600*time.Second) if err != nil { t.Fatal("Error: ", err) @@ -1016,7 +1310,13 @@ func TestFunctional(t *testing.T) { if err != nil { t.Fatal("Error: ", err) } - httpClient := &http.Client{} + httpClient := &http.Client{ + // Setting a sensible time out of 30secs to wait for response + // headers. Request is pro-actively cancelled after 30secs + // with no response. + Timeout: 30 * time.Second, + Transport: http.DefaultTransport, + } resp, err = httpClient.Do(req) if err != nil { t.Fatal("Error: ", err) diff --git a/vendor/src/github.com/minio/minio-go/api_unit_test.go b/vendor/src/github.com/minio/minio-go/api_unit_test.go index 2afc666d8..4fb1978d1 100644 --- a/vendor/src/github.com/minio/minio-go/api_unit_test.go +++ b/vendor/src/github.com/minio/minio-go/api_unit_test.go @@ -160,31 +160,6 @@ func TestValidBucketLocation(t *testing.T) { } } -// Tests valid bucket names. -func TestBucketNames(t *testing.T) { - buckets := []struct { - name string - valid error - }{ - {".mybucket", ErrInvalidBucketName("Bucket name cannot start or end with a '.' dot.")}, - {"mybucket.", ErrInvalidBucketName("Bucket name cannot start or end with a '.' dot.")}, - {"mybucket-", ErrInvalidBucketName("Bucket name contains invalid characters.")}, - {"my", ErrInvalidBucketName("Bucket name cannot be smaller than 3 characters.")}, - {"", ErrInvalidBucketName("Bucket name cannot be empty.")}, - {"my..bucket", ErrInvalidBucketName("Bucket name cannot have successive periods.")}, - {"my.bucket.com", nil}, - {"my-bucket", nil}, - {"123my-bucket", nil}, - } - - for _, b := range buckets { - err := isValidBucketName(b.name) - if err != b.valid { - t.Fatal("Error:", err) - } - } -} - // Tests temp file. func TestTempFile(t *testing.T) { tmpFile, err := newTempFile("testing") @@ -340,17 +315,17 @@ func TestSignatureType(t *testing.T) { } } -// Tests bucket acl types. -func TestBucketACLTypes(t *testing.T) { +// Tests bucket policy types. +func TestBucketPolicyTypes(t *testing.T) { want := map[string]bool{ - "private": true, - "public-read": true, - "public-read-write": true, - "authenticated-read": true, - "invalid": false, + "none": true, + "readonly": true, + "writeonly": true, + "readwrite": true, + "invalid": false, } - for acl, ok := range want { - if BucketACL(acl).isValidBucketACL() != ok { + for bucketPolicy, ok := range want { + if BucketPolicy(bucketPolicy).isValidBucketPolicy() != ok { t.Fatal("Error") } } @@ -396,188 +371,3 @@ func TestPartSize(t *testing.T) { t.Fatalf("Error: expecting last part size of 241172480: got %v instead", lastPartSize) } } - -// Tests url encoding. -func TestURLEncoding(t *testing.T) { - type urlStrings struct { - name string - encodedName string - } - - want := []urlStrings{ - { - name: "bigfile-1._%", - encodedName: "bigfile-1._%25", - }, - { - name: "本語", - encodedName: "%E6%9C%AC%E8%AA%9E", - }, - { - name: "本語.1", - encodedName: "%E6%9C%AC%E8%AA%9E.1", - }, - { - name: ">123>3123123", - encodedName: "%3E123%3E3123123", - }, - { - name: "test 1 2.txt", - encodedName: "test%201%202.txt", - }, - { - name: "test++ 1.txt", - encodedName: "test%2B%2B%201.txt", - }, - } - - for _, u := range want { - if u.encodedName != urlEncodePath(u.name) { - t.Fatal("Error") - } - } -} - -// Tests constructing valid endpoint url. -func TestGetEndpointURL(t *testing.T) { - if _, err := getEndpointURL("s3.amazonaws.com", false); err != nil { - t.Fatal("Error:", err) - } - if _, err := getEndpointURL("192.168.1.1", false); err != nil { - t.Fatal("Error:", err) - } - if _, err := getEndpointURL("13333.123123.-", false); err == nil { - t.Fatal("Error") - } - if _, err := getEndpointURL("s3.aamzza.-", false); err == nil { - t.Fatal("Error") - } - if _, err := getEndpointURL("s3.amazonaws.com:443", false); err == nil { - t.Fatal("Error") - } -} - -// Tests valid ip address. -func TestValidIPAddr(t *testing.T) { - type validIP struct { - ip string - valid bool - } - - want := []validIP{ - { - ip: "192.168.1.1", - valid: true, - }, - { - ip: "192.1.8", - valid: false, - }, - { - ip: "..192.", - valid: false, - }, - { - ip: "192.168.1.1.1", - valid: false, - }, - } - for _, w := range want { - valid := isValidIP(w.ip) - if valid != w.valid { - t.Fatal("Error") - } - } -} - -// Tests valid endpoint domain. -func TestValidEndpointDomain(t *testing.T) { - type validEndpoint struct { - endpointDomain string - valid bool - } - - want := []validEndpoint{ - { - endpointDomain: "s3.amazonaws.com", - valid: true, - }, - { - endpointDomain: "s3.amazonaws.com_", - valid: false, - }, - { - endpointDomain: "%$$$", - valid: false, - }, - { - endpointDomain: "s3.amz.test.com", - valid: true, - }, - { - endpointDomain: "s3.%%", - valid: false, - }, - { - endpointDomain: "localhost", - valid: true, - }, - { - endpointDomain: "-localhost", - valid: false, - }, - { - endpointDomain: "", - valid: false, - }, - { - endpointDomain: "\n \t", - valid: false, - }, - { - endpointDomain: " ", - valid: false, - }, - } - for _, w := range want { - valid := isValidDomain(w.endpointDomain) - if valid != w.valid { - t.Fatal("Error:", w.endpointDomain) - } - } -} - -// Tests valid endpoint url. -func TestValidEndpointURL(t *testing.T) { - type validURL struct { - url string - valid bool - } - want := []validURL{ - { - url: "https://s3.amazonaws.com", - valid: true, - }, - { - url: "https://s3.amazonaws.com/bucket/object", - valid: false, - }, - { - url: "192.168.1.1", - valid: false, - }, - } - for _, w := range want { - u, err := url.Parse(w.url) - if err != nil { - t.Fatal("Error:", err) - } - valid := false - if err := isValidEndpointURL(u); err == nil { - valid = true - } - if valid != w.valid { - t.Fatal("Error") - } - } -} diff --git a/vendor/src/github.com/minio/minio-go/appveyor.yml b/vendor/src/github.com/minio/minio-go/appveyor.yml index 5b8824d45..a5dc2b226 100644 --- a/vendor/src/github.com/minio/minio-go/appveyor.yml +++ b/vendor/src/github.com/minio/minio-go/appveyor.yml @@ -17,8 +17,8 @@ install: - go version - go env - go get -u github.com/golang/lint/golint - - go get -u golang.org/x/tools/cmd/vet - go get -u github.com/remyoudompheng/go-misc/deadcode + - go get -u github.com/gordonklaus/ineffassign # to run your custom scripts instead of automatic MSBuild build_script: @@ -26,6 +26,7 @@ build_script: - gofmt -s -l . - golint github.com/minio/minio-go... - deadcode + - ineffassign . - go test -short -v - go test -short -race -v diff --git a/vendor/src/github.com/minio/minio-go/bucket-acl.go b/vendor/src/github.com/minio/minio-go/bucket-acl.go deleted file mode 100644 index d8eda0f54..000000000 --- a/vendor/src/github.com/minio/minio-go/bucket-acl.go +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015 Minio, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package minio - -// BucketACL - Bucket level access control. -type BucketACL string - -// Different types of ACL's currently supported for buckets. -const ( - bucketPrivate = BucketACL("private") - bucketReadOnly = BucketACL("public-read") - bucketPublic = BucketACL("public-read-write") - bucketAuthenticated = BucketACL("authenticated-read") -) - -// Stringify acl. -func (b BucketACL) String() string { - if string(b) == "" { - return "private" - } - return string(b) -} - -// isValidBucketACL - Is provided acl string supported. -func (b BucketACL) isValidBucketACL() bool { - switch true { - case b.isPrivate(): - fallthrough - case b.isReadOnly(): - fallthrough - case b.isPublic(): - fallthrough - case b.isAuthenticated(): - return true - case b.String() == "private": - // By default its "private" - return true - default: - return false - } -} - -// isPrivate - Is acl Private. -func (b BucketACL) isPrivate() bool { - return b == bucketPrivate -} - -// isPublicRead - Is acl PublicRead. -func (b BucketACL) isReadOnly() bool { - return b == bucketReadOnly -} - -// isPublicReadWrite - Is acl PublicReadWrite. -func (b BucketACL) isPublic() bool { - return b == bucketPublic -} - -// isAuthenticated - Is acl AuthenticatedRead. -func (b BucketACL) isAuthenticated() bool { - return b == bucketAuthenticated -} diff --git a/vendor/src/github.com/minio/minio-go/bucket-cache.go b/vendor/src/github.com/minio/minio-go/bucket-cache.go index c0b4f0cb8..14ac17044 100644 --- a/vendor/src/github.com/minio/minio-go/bucket-cache.go +++ b/vendor/src/github.com/minio/minio-go/bucket-cache.go @@ -20,7 +20,8 @@ import ( "encoding/hex" "net/http" "net/url" - "path/filepath" + "path" + "strings" "sync" ) @@ -67,15 +68,18 @@ func (r *bucketLocationCache) Delete(bucketName string) { // getBucketLocation - Get location for the bucketName from location map cache. func (c Client) getBucketLocation(bucketName string) (string, error) { - // For anonymous requests, default to "us-east-1" and let other calls - // move forward. - if c.anonymous { - return "us-east-1", nil - } if location, ok := c.bucketLocCache.Get(bucketName); ok { return location, nil } + if isAmazonChinaEndpoint(c.endpointURL) { + // For china specifically we need to set everything to + // cn-north-1 for now, there is no easier way until AWS S3 + // provides a cleaner compatible API across "us-east-1" and + // China region. + return "cn-north-1", nil + } + // Initialize a new request. req, err := c.getBucketLocationRequest(bucketName) if err != nil { @@ -88,9 +92,27 @@ func (c Client) getBucketLocation(bucketName string) (string, error) { if err != nil { return "", err } + location, err := processBucketLocationResponse(resp, bucketName) + if err != nil { + return "", err + } + c.bucketLocCache.Set(bucketName, location) + return location, nil +} + +// processes the getBucketLocation http response from the server. +func processBucketLocationResponse(resp *http.Response, bucketName string) (bucketLocation string, err error) { if resp != nil { if resp.StatusCode != http.StatusOK { - return "", httpRespToErrorResponse(resp, bucketName, "") + err = httpRespToErrorResponse(resp, bucketName, "") + errResp := ToErrorResponse(err) + // For access denied error, it could be an anonymous + // request. Move forward and let the top level callers + // succeed if possible based on their policy. + if errResp.Code == "AccessDenied" && strings.Contains(errResp.Message, "Access Denied") { + return "us-east-1", nil + } + return "", err } } @@ -113,7 +135,6 @@ func (c Client) getBucketLocation(bucketName string) (string, error) { } // Save the location into cache. - c.bucketLocCache.Set(bucketName, location) // Return. return location, nil @@ -127,7 +148,7 @@ func (c Client) getBucketLocationRequest(bucketName string) (*http.Request, erro // Set get bucket location always as path style. targetURL := c.endpointURL - targetURL.Path = filepath.Join(bucketName, "") + "/" + targetURL.Path = path.Join(bucketName, "") + "/" targetURL.RawQuery = urlValues.Encode() // Get a new HTTP request for the method. diff --git a/vendor/src/github.com/minio/minio-go/bucket-cache_test.go b/vendor/src/github.com/minio/minio-go/bucket-cache_test.go new file mode 100644 index 000000000..57b0e9e34 --- /dev/null +++ b/vendor/src/github.com/minio/minio-go/bucket-cache_test.go @@ -0,0 +1,320 @@ +/* + * Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2016, 2016 Minio, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package minio + +import ( + "bytes" + "encoding/hex" + "encoding/xml" + "io/ioutil" + "net/http" + "net/url" + "path" + "reflect" + "testing" +) + +// Test validates `newBucketLocationCache`. +func TestNewBucketLocationCache(t *testing.T) { + expectedBucketLocationcache := &bucketLocationCache{ + items: make(map[string]string), + } + actualBucketLocationCache := newBucketLocationCache() + + if !reflect.DeepEqual(actualBucketLocationCache, expectedBucketLocationcache) { + t.Errorf("Unexpected return value") + } +} + +// Tests validate bucketLocationCache operations. +func TestBucketLocationCacheOps(t *testing.T) { + testBucketLocationCache := newBucketLocationCache() + expectedBucketName := "minio-bucket" + expectedLocation := "us-east-1" + testBucketLocationCache.Set(expectedBucketName, expectedLocation) + actualLocation, ok := testBucketLocationCache.Get(expectedBucketName) + if !ok { + t.Errorf("Bucket location cache not set") + } + if expectedLocation != actualLocation { + t.Errorf("Bucket location cache not set to expected value") + } + testBucketLocationCache.Delete(expectedBucketName) + _, ok = testBucketLocationCache.Get(expectedBucketName) + if ok { + t.Errorf("Bucket location cache not deleted as expected") + } +} + +// Tests validate http request generation for 'getBucketLocation'. +func TestGetBucketLocationRequest(t *testing.T) { + // Generates expected http request for getBucketLocation. + // Used for asserting with the actual request generated. + createExpectedRequest := func(c *Client, bucketName string, req *http.Request) (*http.Request, error) { + // Set location query. + urlValues := make(url.Values) + urlValues.Set("location", "") + + // Set get bucket location always as path style. + targetURL := c.endpointURL + targetURL.Path = path.Join(bucketName, "") + "/" + targetURL.RawQuery = urlValues.Encode() + + // Get a new HTTP request for the method. + req, err := http.NewRequest("GET", targetURL.String(), nil) + if err != nil { + return nil, err + } + + // Set UserAgent for the request. + c.setUserAgent(req) + + // Set sha256 sum for signature calculation only with signature version '4'. + if c.signature.isV4() { + req.Header.Set("X-Amz-Content-Sha256", hex.EncodeToString(sum256([]byte{}))) + } + + // Sign the request. + if c.signature.isV4() { + req = signV4(*req, c.accessKeyID, c.secretAccessKey, "us-east-1") + } else if c.signature.isV2() { + req = signV2(*req, c.accessKeyID, c.secretAccessKey) + } + return req, nil + + } + // Info for 'Client' creation. + // Will be used as arguments for 'NewClient'. + type infoForClient struct { + endPoint string + accessKey string + secretKey string + enableInsecure bool + } + // dataset for 'NewClient' call. + info := []infoForClient{ + // endpoint localhost. + // both access-key and secret-key are empty. + {"localhost:9000", "", "", false}, + // both access-key are secret-key exists. + {"localhost:9000", "my-access-key", "my-secret-key", false}, + // one of acess-key and secret-key are empty. + {"localhost:9000", "", "my-secret-key", false}, + + // endpoint amazon s3. + {"s3.amazonaws.com", "", "", false}, + {"s3.amazonaws.com", "my-access-key", "my-secret-key", false}, + {"s3.amazonaws.com", "my-acess-key", "", false}, + + // endpoint google cloud storage. + {"storage.googleapis.com", "", "", false}, + {"storage.googleapis.com", "my-access-key", "my-secret-key", false}, + {"storage.googleapis.com", "", "my-secret-key", false}, + + // endpoint custom domain running Minio server. + {"play.minio.io", "", "", false}, + {"play.minio.io", "my-access-key", "my-secret-key", false}, + {"play.minio.io", "my-acess-key", "", false}, + } + testCases := []struct { + bucketName string + // data for new client creation. + info infoForClient + // error in the output. + err error + // flag indicating whether tests should pass. + shouldPass bool + }{ + // Client is constructed using the info struct. + // case with empty location. + {"my-bucket", info[0], nil, true}, + // case with location set to standard 'us-east-1'. + {"my-bucket", info[0], nil, true}, + // case with location set to a value different from 'us-east-1'. + {"my-bucket", info[0], nil, true}, + + {"my-bucket", info[1], nil, true}, + {"my-bucket", info[1], nil, true}, + {"my-bucket", info[1], nil, true}, + + {"my-bucket", info[2], nil, true}, + {"my-bucket", info[2], nil, true}, + {"my-bucket", info[2], nil, true}, + + {"my-bucket", info[3], nil, true}, + {"my-bucket", info[3], nil, true}, + {"my-bucket", info[3], nil, true}, + + {"my-bucket", info[4], nil, true}, + {"my-bucket", info[4], nil, true}, + {"my-bucket", info[4], nil, true}, + + {"my-bucket", info[5], nil, true}, + {"my-bucket", info[5], nil, true}, + {"my-bucket", info[5], nil, true}, + + {"my-bucket", info[6], nil, true}, + {"my-bucket", info[6], nil, true}, + {"my-bucket", info[6], nil, true}, + + {"my-bucket", info[7], nil, true}, + {"my-bucket", info[7], nil, true}, + {"my-bucket", info[7], nil, true}, + + {"my-bucket", info[8], nil, true}, + {"my-bucket", info[8], nil, true}, + {"my-bucket", info[8], nil, true}, + + {"my-bucket", info[9], nil, true}, + {"my-bucket", info[9], nil, true}, + {"my-bucket", info[9], nil, true}, + + {"my-bucket", info[10], nil, true}, + {"my-bucket", info[10], nil, true}, + {"my-bucket", info[10], nil, true}, + + {"my-bucket", info[11], nil, true}, + {"my-bucket", info[11], nil, true}, + {"my-bucket", info[11], nil, true}, + } + for i, testCase := range testCases { + // cannot create a newclient with empty endPoint value. + // validates and creates a new client only if the endPoint value is not empty. + client := &Client{} + var err error + if testCase.info.endPoint != "" { + + client, err = New(testCase.info.endPoint, testCase.info.accessKey, testCase.info.secretKey, testCase.info.enableInsecure) + if err != nil { + t.Fatalf("Test %d: Failed to create new Client: %s", i+1, err.Error()) + } + } + + actualReq, err := client.getBucketLocationRequest(testCase.bucketName) + if err != nil && testCase.shouldPass { + t.Errorf("Test %d: Expected to pass, but failed with: %s", i+1, err.Error()) + } + if err == nil && !testCase.shouldPass { + t.Errorf("Test %d: Expected to fail with \"%s\", but passed instead", i+1, testCase.err.Error()) + } + // Failed as expected, but does it fail for the expected reason. + if err != nil && !testCase.shouldPass { + if err.Error() != testCase.err.Error() { + t.Errorf("Test %d: Expected to fail with error \"%s\", but instead failed with error \"%s\" instead", i+1, testCase.err.Error(), err.Error()) + } + } + + // Test passes as expected, but the output values are verified for correctness here. + if err == nil && testCase.shouldPass { + expectedReq := &http.Request{} + expectedReq, err = createExpectedRequest(client, testCase.bucketName, expectedReq) + if err != nil { + t.Fatalf("Test %d: Expected request Creation failed", i+1) + } + if expectedReq.Method != actualReq.Method { + t.Errorf("Test %d: The expected Request method doesn't match with the actual one", i+1) + } + if expectedReq.URL.String() != actualReq.URL.String() { + t.Errorf("Test %d: Expected the request URL to be '%s', but instead found '%s'", i+1, expectedReq.URL.String(), actualReq.URL.String()) + } + if expectedReq.ContentLength != actualReq.ContentLength { + t.Errorf("Test %d: Expected the request body Content-Length to be '%d', but found '%d' instead", i+1, expectedReq.ContentLength, actualReq.ContentLength) + } + + if expectedReq.Header.Get("X-Amz-Content-Sha256") != actualReq.Header.Get("X-Amz-Content-Sha256") { + t.Errorf("Test %d: 'X-Amz-Content-Sha256' header of the expected request doesn't match with that of the actual request", i+1) + } + if expectedReq.Header.Get("User-Agent") != actualReq.Header.Get("User-Agent") { + t.Errorf("Test %d: Expected 'User-Agent' header to be \"%s\",but found \"%s\" instead", i+1, expectedReq.Header.Get("User-Agent"), actualReq.Header.Get("User-Agent")) + } + } + } +} + +// generates http response with bucket location set in the body. +func generateLocationResponse(resp *http.Response, bodyContent []byte) (*http.Response, error) { + resp.StatusCode = http.StatusOK + resp.Body = ioutil.NopCloser(bytes.NewBuffer(bodyContent)) + return resp, nil +} + +// Tests the processing of GetPolicy response from server. +func TestProcessBucketLocationResponse(t *testing.T) { + // LocationResponse - format for location response. + type LocationResponse struct { + XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ LocationConstraint" json:"-"` + Location string `xml:",chardata"` + } + + APIErrors := []APIError{ + { + Code: "AccessDenied", + Description: "Access Denied", + HTTPStatusCode: http.StatusUnauthorized, + }, + } + testCases := []struct { + bucketName string + inputLocation string + isAPIError bool + apiErr APIError + // expected results. + expectedResult string + err error + // flag indicating whether tests should pass. + shouldPass bool + }{ + {"my-bucket", "", true, APIErrors[0], "us-east-1", nil, true}, + {"my-bucket", "", false, APIError{}, "us-east-1", nil, true}, + {"my-bucket", "EU", false, APIError{}, "eu-west-1", nil, true}, + {"my-bucket", "eu-central-1", false, APIError{}, "eu-central-1", nil, true}, + {"my-bucket", "us-east-1", false, APIError{}, "us-east-1", nil, true}, + } + + for i, testCase := range testCases { + inputResponse := &http.Response{} + var err error + if testCase.isAPIError { + inputResponse = generateErrorResponse(inputResponse, testCase.apiErr, testCase.bucketName) + } else { + inputResponse, err = generateLocationResponse(inputResponse, encodeResponse(LocationResponse{ + Location: testCase.inputLocation, + })) + if err != nil { + t.Fatalf("Test %d: Creation of valid response failed", i+1) + } + } + actualResult, err := processBucketLocationResponse(inputResponse, "my-bucket") + if err != nil && testCase.shouldPass { + t.Errorf("Test %d: Expected to pass, but failed with: %s", i+1, err.Error()) + } + if err == nil && !testCase.shouldPass { + t.Errorf("Test %d: Expected to fail with \"%s\", but passed instead", i+1, testCase.err.Error()) + } + // Failed as expected, but does it fail for the expected reason. + if err != nil && !testCase.shouldPass { + if err.Error() != testCase.err.Error() { + t.Errorf("Test %d: Expected to fail with error \"%s\", but instead failed with error \"%s\" instead", i+1, testCase.err.Error(), err.Error()) + } + } + if err == nil && testCase.shouldPass { + if !reflect.DeepEqual(testCase.expectedResult, actualResult) { + t.Errorf("Test %d: The expected BucketPolicy doesnt match the actual BucketPolicy", i+1) + } + } + } +} diff --git a/vendor/src/github.com/minio/minio-go/bucket-policy.go b/vendor/src/github.com/minio/minio-go/bucket-policy.go new file mode 100644 index 000000000..57e3f2d02 --- /dev/null +++ b/vendor/src/github.com/minio/minio-go/bucket-policy.go @@ -0,0 +1,488 @@ +/* + * Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015 Minio, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package minio + +import ( + "encoding/json" + "fmt" + "sort" +) + +// maximum supported access policy size. +const maxAccessPolicySize = 20 * 1024 * 1024 // 20KiB. + +// Resource prefix for all aws resources. +const awsResourcePrefix = "arn:aws:s3:::" + +// BucketPolicy - Bucket level policy. +type BucketPolicy string + +// Different types of Policies currently supported for buckets. +const ( + BucketPolicyNone BucketPolicy = "none" + BucketPolicyReadOnly = "readonly" + BucketPolicyReadWrite = "readwrite" + BucketPolicyWriteOnly = "writeonly" +) + +// isValidBucketPolicy - Is provided policy value supported. +func (p BucketPolicy) isValidBucketPolicy() bool { + switch p { + case BucketPolicyNone, BucketPolicyReadOnly, BucketPolicyReadWrite, BucketPolicyWriteOnly: + return true + } + return false +} + +// User - canonical users list. +type User struct { + AWS []string +} + +// Statement - minio policy statement +type Statement struct { + Sid string + Effect string + Principal User `json:"Principal"` + Actions []string `json:"Action"` + Resources []string `json:"Resource"` + Conditions map[string]map[string]string `json:"Condition,omitempty"` +} + +// BucketAccessPolicy - minio policy collection +type BucketAccessPolicy struct { + Version string // date in 0000-00-00 format + Statements []Statement `json:"Statement"` +} + +// Read write actions. +var ( + readWriteBucketActions = []string{ + "s3:GetBucketLocation", + "s3:ListBucket", + "s3:ListBucketMultipartUploads", + // Add more bucket level read-write actions here. + } + readWriteObjectActions = []string{ + "s3:AbortMultipartUpload", + "s3:DeleteObject", + "s3:GetObject", + "s3:ListMultipartUploadParts", + "s3:PutObject", + // Add more object level read-write actions here. + } +) + +// Write only actions. +var ( + writeOnlyBucketActions = []string{ + "s3:GetBucketLocation", + "s3:ListBucketMultipartUploads", + // Add more bucket level write actions here. + } + writeOnlyObjectActions = []string{ + "s3:AbortMultipartUpload", + "s3:DeleteObject", + "s3:ListMultipartUploadParts", + "s3:PutObject", + // Add more object level write actions here. + } +) + +// Read only actions. +var ( + readOnlyBucketActions = []string{ + "s3:GetBucketLocation", + "s3:ListBucket", + // Add more bucket level read actions here. + } + readOnlyObjectActions = []string{ + "s3:GetObject", + // Add more object level read actions here. + } +) + +// subsetActions returns true if the first array is completely +// contained in the second array. There must be at least +// the same number of duplicate values in second as there +// are in first. +func subsetActions(first, second []string) bool { + set := make(map[string]int) + for _, value := range second { + set[value]++ + } + for _, value := range first { + if count, found := set[value]; !found { + return false + } else if count < 1 { + return false + } else { + set[value] = count - 1 + } + } + return true +} + +// Verifies if we have read/write policy set at bucketName, objectPrefix. +func isBucketPolicyReadWrite(statements []Statement, bucketName string, objectPrefix string) bool { + var commonActions, readWrite bool + sort.Strings(readWriteBucketActions) + sort.Strings(readWriteObjectActions) + for _, statement := range statements { + for _, resource := range statement.Resources { + if resource == awsResourcePrefix+bucketName { + if subsetActions(readWriteBucketActions, statement.Actions) { + commonActions = true + continue + } + } else if resource == awsResourcePrefix+bucketName+"/"+objectPrefix+"*" { + if subsetActions(readWriteObjectActions, statement.Actions) { + readWrite = true + } + } + } + } + return commonActions && readWrite +} + +// Verifies if we have write only policy set at bucketName, objectPrefix. +func isBucketPolicyWriteOnly(statements []Statement, bucketName string, objectPrefix string) bool { + var commonActions, writeOnly bool + sort.Strings(writeOnlyBucketActions) + sort.Strings(writeOnlyObjectActions) + for _, statement := range statements { + for _, resource := range statement.Resources { + if resource == awsResourcePrefix+bucketName { + if subsetActions(writeOnlyBucketActions, statement.Actions) { + commonActions = true + continue + } + } else if resource == awsResourcePrefix+bucketName+"/"+objectPrefix+"*" { + if subsetActions(writeOnlyObjectActions, statement.Actions) { + writeOnly = true + } + } + } + } + return commonActions && writeOnly +} + +// Verifies if we have read only policy set at bucketName, objectPrefix. +func isBucketPolicyReadOnly(statements []Statement, bucketName string, objectPrefix string) bool { + var commonActions, readOnly bool + sort.Strings(readOnlyBucketActions) + sort.Strings(readOnlyObjectActions) + for _, statement := range statements { + for _, resource := range statement.Resources { + if resource == awsResourcePrefix+bucketName { + if subsetActions(readOnlyBucketActions, statement.Actions) { + commonActions = true + continue + } + } else if resource == awsResourcePrefix+bucketName+"/"+objectPrefix+"*" { + if subsetActions(readOnlyObjectActions, statement.Actions) { + readOnly = true + break + } + } + } + } + return commonActions && readOnly +} + +// Removes read write bucket policy if found. +func removeBucketPolicyStatementReadWrite(statements []Statement, bucketName string, objectPrefix string) []Statement { + var newStatements []Statement + for _, statement := range statements { + for _, resource := range statement.Resources { + if resource == awsResourcePrefix+bucketName { + var newActions []string + for _, action := range statement.Actions { + switch action { + case "s3:GetBucketLocation", "s3:ListBucket", "s3:ListBucketMultipartUploads": + continue + } + newActions = append(newActions, action) + } + statement.Actions = newActions + } else if resource == awsResourcePrefix+bucketName+"/"+objectPrefix+"*" { + var newActions []string + for _, action := range statement.Actions { + switch action { + case "s3:PutObject", "s3:AbortMultipartUpload", "s3:ListMultipartUploadParts", "s3:DeleteObject", "s3:GetObject": + continue + } + newActions = append(newActions, action) + } + statement.Actions = newActions + } + } + if len(statement.Actions) != 0 { + newStatements = append(newStatements, statement) + } + } + return newStatements +} + +// Removes write only bucket policy if found. +func removeBucketPolicyStatementWriteOnly(statements []Statement, bucketName string, objectPrefix string) []Statement { + var newStatements []Statement + for _, statement := range statements { + for _, resource := range statement.Resources { + if resource == awsResourcePrefix+bucketName { + var newActions []string + for _, action := range statement.Actions { + switch action { + case "s3:GetBucketLocation", "s3:ListBucketMultipartUploads": + continue + } + newActions = append(newActions, action) + } + statement.Actions = newActions + } else if resource == awsResourcePrefix+bucketName+"/"+objectPrefix+"*" { + var newActions []string + for _, action := range statement.Actions { + switch action { + case "s3:PutObject", "s3:AbortMultipartUpload", "s3:ListMultipartUploadParts", "s3:DeleteObject": + continue + } + newActions = append(newActions, action) + } + statement.Actions = newActions + } + } + if len(statement.Actions) != 0 { + newStatements = append(newStatements, statement) + } + } + return newStatements +} + +// Removes read only bucket policy if found. +func removeBucketPolicyStatementReadOnly(statements []Statement, bucketName string, objectPrefix string) []Statement { + var newStatements []Statement + for _, statement := range statements { + for _, resource := range statement.Resources { + if resource == awsResourcePrefix+bucketName { + var newActions []string + for _, action := range statement.Actions { + switch action { + case "s3:GetBucketLocation", "s3:ListBucket": + continue + } + newActions = append(newActions, action) + } + statement.Actions = newActions + } else if resource == awsResourcePrefix+bucketName+"/"+objectPrefix+"*" { + var newActions []string + for _, action := range statement.Actions { + if action == "s3:GetObject" { + continue + } + newActions = append(newActions, action) + } + statement.Actions = newActions + } + } + if len(statement.Actions) != 0 { + newStatements = append(newStatements, statement) + } + } + return newStatements +} + +// Remove bucket policies based on the type. +func removeBucketPolicyStatement(statements []Statement, bucketName string, objectPrefix string) []Statement { + // Verify type of policy to be removed. + if isBucketPolicyReadWrite(statements, bucketName, objectPrefix) { + statements = removeBucketPolicyStatementReadWrite(statements, bucketName, objectPrefix) + } else if isBucketPolicyWriteOnly(statements, bucketName, objectPrefix) { + statements = removeBucketPolicyStatementWriteOnly(statements, bucketName, objectPrefix) + } else if isBucketPolicyReadOnly(statements, bucketName, objectPrefix) { + statements = removeBucketPolicyStatementReadOnly(statements, bucketName, objectPrefix) + } + return statements +} + +// Unmarshals bucket policy byte array into a structured bucket access policy. +func unMarshalBucketPolicy(bucketPolicyBuf []byte) (BucketAccessPolicy, error) { + // Untyped lazy JSON struct. + type bucketAccessPolicyUntyped struct { + Version string + Statement []struct { + Sid string + Effect string + Principal struct { + AWS json.RawMessage + } + Action json.RawMessage + Resource json.RawMessage + Condition map[string]map[string]string + } + } + var policyUntyped = bucketAccessPolicyUntyped{} + // Unmarshal incoming policy into an untyped structure, to be + // evaluated lazily later. + err := json.Unmarshal(bucketPolicyBuf, &policyUntyped) + if err != nil { + return BucketAccessPolicy{}, err + } + var policy = BucketAccessPolicy{} + policy.Version = policyUntyped.Version + for _, stmtUntyped := range policyUntyped.Statement { + statement := Statement{} + // These are properly typed messages. + statement.Sid = stmtUntyped.Sid + statement.Effect = stmtUntyped.Effect + statement.Conditions = stmtUntyped.Condition + + // AWS user can have two different types, either as []string + // and either as regular 'string'. We fall back to doing this + // since there is no other easier way to fix this. + err = json.Unmarshal(stmtUntyped.Principal.AWS, &statement.Principal.AWS) + if err != nil { + var awsUser string + err = json.Unmarshal(stmtUntyped.Principal.AWS, &awsUser) + if err != nil { + return BucketAccessPolicy{}, err + } + statement.Principal.AWS = []string{awsUser} + } + // Actions can have two different types, either as []string + // and either as regular 'string'. We fall back to doing this + // since there is no other easier way to fix this. + err = json.Unmarshal(stmtUntyped.Action, &statement.Actions) + if err != nil { + var action string + err = json.Unmarshal(stmtUntyped.Action, &action) + if err != nil { + return BucketAccessPolicy{}, err + } + statement.Actions = []string{action} + } + // Resources can have two different types, either as []string + // and either as regular 'string'. We fall back to doing this + // since there is no other easier way to fix this. + err = json.Unmarshal(stmtUntyped.Resource, &statement.Resources) + if err != nil { + var resource string + err = json.Unmarshal(stmtUntyped.Resource, &resource) + if err != nil { + return BucketAccessPolicy{}, err + } + statement.Resources = []string{resource} + } + // Append the typed policy. + policy.Statements = append(policy.Statements, statement) + } + return policy, nil +} + +// Identifies the policy type from policy Statements. +func identifyPolicyType(policy BucketAccessPolicy, bucketName, objectPrefix string) (bucketPolicy BucketPolicy) { + if policy.Statements == nil { + return BucketPolicyNone + } + if isBucketPolicyReadWrite(policy.Statements, bucketName, objectPrefix) { + return BucketPolicyReadWrite + } else if isBucketPolicyWriteOnly(policy.Statements, bucketName, objectPrefix) { + return BucketPolicyWriteOnly + } else if isBucketPolicyReadOnly(policy.Statements, bucketName, objectPrefix) { + return BucketPolicyReadOnly + } + return BucketPolicyNone +} + +// Generate policy statements for various bucket policies. +// refer to http://docs.aws.amazon.com/AmazonS3/latest/dev/access-policy-language-overview.html +// for more details about statement fields. +func generatePolicyStatement(bucketPolicy BucketPolicy, bucketName, objectPrefix string) ([]Statement, error) { + if !bucketPolicy.isValidBucketPolicy() { + return []Statement{}, ErrInvalidArgument(fmt.Sprintf("Invalid bucket policy provided. %s", bucketPolicy)) + } + var statements []Statement + if bucketPolicy == BucketPolicyNone { + return []Statement{}, nil + } else if bucketPolicy == BucketPolicyReadWrite { + // Get read-write policy. + statements = setReadWriteStatement(bucketName, objectPrefix) + } else if bucketPolicy == BucketPolicyReadOnly { + // Get read only policy. + statements = setReadOnlyStatement(bucketName, objectPrefix) + } else if bucketPolicy == BucketPolicyWriteOnly { + // Return Write only policy. + statements = setWriteOnlyStatement(bucketName, objectPrefix) + } + return statements, nil +} + +// Obtain statements for read-write BucketPolicy. +func setReadWriteStatement(bucketName, objectPrefix string) []Statement { + bucketResourceStatement := Statement{} + objectResourceStatement := Statement{} + statements := []Statement{} + + bucketResourceStatement.Effect = "Allow" + bucketResourceStatement.Principal.AWS = []string{"*"} + bucketResourceStatement.Resources = []string{fmt.Sprintf("%s%s", awsResourcePrefix, bucketName)} + bucketResourceStatement.Actions = readWriteBucketActions + objectResourceStatement.Effect = "Allow" + objectResourceStatement.Principal.AWS = []string{"*"} + objectResourceStatement.Resources = []string{fmt.Sprintf("%s%s", awsResourcePrefix, bucketName+"/"+objectPrefix+"*")} + objectResourceStatement.Actions = readWriteObjectActions + // Save the read write policy. + statements = append(statements, bucketResourceStatement, objectResourceStatement) + return statements +} + +// Obtain statements for read only BucketPolicy. +func setReadOnlyStatement(bucketName, objectPrefix string) []Statement { + bucketResourceStatement := Statement{} + objectResourceStatement := Statement{} + statements := []Statement{} + + bucketResourceStatement.Effect = "Allow" + bucketResourceStatement.Principal.AWS = []string{"*"} + bucketResourceStatement.Resources = []string{fmt.Sprintf("%s%s", awsResourcePrefix, bucketName)} + bucketResourceStatement.Actions = readOnlyBucketActions + objectResourceStatement.Effect = "Allow" + objectResourceStatement.Principal.AWS = []string{"*"} + objectResourceStatement.Resources = []string{fmt.Sprintf("%s%s", awsResourcePrefix, bucketName+"/"+objectPrefix+"*")} + objectResourceStatement.Actions = readOnlyObjectActions + // Save the read only policy. + statements = append(statements, bucketResourceStatement, objectResourceStatement) + return statements +} + +// Obtain statements for write only BucketPolicy. +func setWriteOnlyStatement(bucketName, objectPrefix string) []Statement { + bucketResourceStatement := Statement{} + objectResourceStatement := Statement{} + statements := []Statement{} + // Write only policy. + bucketResourceStatement.Effect = "Allow" + bucketResourceStatement.Principal.AWS = []string{"*"} + bucketResourceStatement.Resources = []string{fmt.Sprintf("%s%s", awsResourcePrefix, bucketName)} + bucketResourceStatement.Actions = writeOnlyBucketActions + objectResourceStatement.Effect = "Allow" + objectResourceStatement.Principal.AWS = []string{"*"} + objectResourceStatement.Resources = []string{fmt.Sprintf("%s%s", awsResourcePrefix, bucketName+"/"+objectPrefix+"*")} + objectResourceStatement.Actions = writeOnlyObjectActions + // Save the write only policy. + statements = append(statements, bucketResourceStatement, objectResourceStatement) + return statements +} diff --git a/vendor/src/github.com/minio/minio-go/bucket-policy_test.go b/vendor/src/github.com/minio/minio-go/bucket-policy_test.go new file mode 100644 index 000000000..483da597a --- /dev/null +++ b/vendor/src/github.com/minio/minio-go/bucket-policy_test.go @@ -0,0 +1,515 @@ +/* + * Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015 Minio, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package minio + +import ( + "encoding/json" + "fmt" + "reflect" + "testing" +) + +// Validates bucket policy string. +func TestIsValidBucketPolicy(t *testing.T) { + testCases := []struct { + inputPolicy BucketPolicy + expectedResult bool + }{ + // valid inputs. + {BucketPolicy("none"), true}, + {BucketPolicy("readonly"), true}, + {BucketPolicy("readwrite"), true}, + {BucketPolicy("writeonly"), true}, + // invalid input. + {BucketPolicy("readwriteonly"), false}, + {BucketPolicy("writeread"), false}, + } + + for i, testCase := range testCases { + actualResult := testCase.inputPolicy.isValidBucketPolicy() + if testCase.expectedResult != actualResult { + t.Errorf("Test %d: Expected IsValidBucket policy to be '%v' for policy \"%s\", but instead found it to be '%v'", i+1, testCase.expectedResult, testCase.inputPolicy, actualResult) + } + } +} + +// Tests whether first array is completly contained in second array. +func TestSubsetActions(t *testing.T) { + testCases := []struct { + firstArray []string + secondArray []string + + expectedResult bool + }{ + {[]string{"aaa", "bbb"}, []string{"ccc", "bbb"}, false}, + {[]string{"aaa", "bbb"}, []string{"aaa", "ccc"}, false}, + {[]string{"aaa", "bbb"}, []string{"aaa", "bbb"}, true}, + {[]string{"aaa", "bbb"}, []string{"aaa", "bbb", "ccc"}, true}, + {[]string{"aaa", "bbb", "aaa"}, []string{"aaa", "bbb", "ccc"}, false}, + {[]string{"aaa", "bbb", "aaa"}, []string{"aaa", "bbb", "bbb", "aaa"}, true}, + {[]string{"aaa", "bbb", "aaa"}, []string{"aaa", "bbb"}, false}, + {[]string{"aaa", "bbb", "aaa"}, []string{"aaa", "bbb", "aaa", "bbb", "ccc"}, true}, + } + for i, testCase := range testCases { + actualResult := subsetActions(testCase.firstArray, testCase.secondArray) + if testCase.expectedResult != actualResult { + t.Errorf("Test %d: First array '%v' is not contained in second array '%v'", i+1, testCase.firstArray, testCase.secondArray) + } + } + +} + +// Tests validate Bucket Policy type identifier. +func TestIdentifyPolicyType(t *testing.T) { + testCases := []struct { + inputPolicy BucketAccessPolicy + bucketName string + objName string + + expectedPolicy BucketPolicy + }{ + {BucketAccessPolicy{Version: "2012-10-17"}, "my-bucket", "", BucketPolicyNone}, + } + for i, testCase := range testCases { + actualBucketPolicy := identifyPolicyType(testCase.inputPolicy, testCase.bucketName, testCase.objName) + if testCase.expectedPolicy != actualBucketPolicy { + t.Errorf("Test %d: Expected bucket policy to be '%v', but instead got '%v'", i+1, testCase.expectedPolicy, actualBucketPolicy) + } + } +} + +// Test validate Resource Statement Generator. +func TestGeneratePolicyStatement(t *testing.T) { + + testCases := []struct { + bucketPolicy BucketPolicy + bucketName string + objectPrefix string + expectedStatements []Statement + + shouldPass bool + err error + }{ + {BucketPolicy("my-policy"), "my-bucket", "", []Statement{}, false, ErrInvalidArgument(fmt.Sprintf("Invalid bucket policy provided. %s", BucketPolicy("my-policy")))}, + {BucketPolicyNone, "my-bucket", "", []Statement{}, true, nil}, + {BucketPolicyReadOnly, "read-only-bucket", "", setReadOnlyStatement("read-only-bucket", ""), true, nil}, + {BucketPolicyWriteOnly, "write-only-bucket", "", setWriteOnlyStatement("write-only-bucket", ""), true, nil}, + {BucketPolicyReadWrite, "read-write-bucket", "", setReadWriteStatement("read-write-bucket", ""), true, nil}, + } + for i, testCase := range testCases { + actualStatements, err := generatePolicyStatement(testCase.bucketPolicy, testCase.bucketName, testCase.objectPrefix) + + if err != nil && testCase.shouldPass { + t.Errorf("Test %d: Expected to pass, but failed with: %s", i+1, err.Error()) + } + + if err == nil && !testCase.shouldPass { + t.Errorf("Test %d: Expected to fail with \"%s\", but passed instead", i+1, testCase.err.Error()) + } + // Failed as expected, but does it fail for the expected reason. + if err != nil && !testCase.shouldPass { + if err.Error() != testCase.err.Error() { + t.Errorf("Test %d: Expected to fail with error \"%s\", but instead failed with error \"%s\" instead", i+1, testCase.err.Error(), err.Error()) + } + } + // Test passes as expected, but the output values are verified for correctness here. + if err == nil && testCase.shouldPass { + if !reflect.DeepEqual(testCase.expectedStatements, actualStatements) { + t.Errorf("Test %d: The expected statements from resource statement generator doesn't match the actual statements", i+1) + } + } + } +} + +// Tests validating read only statement generator. +func TestsetReadOnlyStatement(t *testing.T) { + + expectedReadOnlyStatement := func(bucketName, objectPrefix string) []Statement { + bucketResourceStatement := &Statement{} + objectResourceStatement := &Statement{} + statements := []Statement{} + + bucketResourceStatement.Effect = "Allow" + bucketResourceStatement.Principal.AWS = []string{"*"} + bucketResourceStatement.Resources = []string{fmt.Sprintf("%s%s", awsResourcePrefix, bucketName)} + bucketResourceStatement.Actions = readOnlyBucketActions + objectResourceStatement.Effect = "Allow" + objectResourceStatement.Principal.AWS = []string{"*"} + objectResourceStatement.Resources = []string{fmt.Sprintf("%s%s", awsResourcePrefix, bucketName+"/"+objectPrefix+"*")} + objectResourceStatement.Actions = readOnlyObjectActions + // Save the read only policy. + statements = append(statements, *bucketResourceStatement, *objectResourceStatement) + return statements + } + + testCases := []struct { + // inputs. + bucketName string + objectPrefix string + // expected result. + expectedStatements []Statement + }{ + {"my-bucket", "", expectedReadOnlyStatement("my-bucket", "")}, + {"my-bucket", "Asia/", expectedReadOnlyStatement("my-bucket", "Asia/")}, + {"my-bucket", "Asia/India", expectedReadOnlyStatement("my-bucket", "Asia/India")}, + } + + for i, testCase := range testCases { + actualStaments := setReadOnlyStatement(testCase.bucketName, testCase.objectPrefix) + if !reflect.DeepEqual(testCase.expectedStatements, actualStaments) { + t.Errorf("Test %d: The expected statements from resource statement generator doesn't match the actual statements", i+1) + } + } +} + +// Tests validating write only statement generator. +func TestsetWriteOnlyStatement(t *testing.T) { + + expectedWriteOnlyStatement := func(bucketName, objectPrefix string) []Statement { + bucketResourceStatement := &Statement{} + objectResourceStatement := &Statement{} + statements := []Statement{} + // Write only policy. + bucketResourceStatement.Effect = "Allow" + bucketResourceStatement.Principal.AWS = []string{"*"} + bucketResourceStatement.Resources = []string{fmt.Sprintf("%s%s", awsResourcePrefix, bucketName)} + bucketResourceStatement.Actions = writeOnlyBucketActions + objectResourceStatement.Effect = "Allow" + objectResourceStatement.Principal.AWS = []string{"*"} + objectResourceStatement.Resources = []string{fmt.Sprintf("%s%s", awsResourcePrefix, bucketName+"/"+objectPrefix+"*")} + objectResourceStatement.Actions = writeOnlyObjectActions + // Save the write only policy. + statements = append(statements, *bucketResourceStatement, *objectResourceStatement) + return statements + } + testCases := []struct { + // inputs. + bucketName string + objectPrefix string + // expected result. + expectedStatements []Statement + }{ + {"my-bucket", "", expectedWriteOnlyStatement("my-bucket", "")}, + {"my-bucket", "Asia/", expectedWriteOnlyStatement("my-bucket", "Asia/")}, + {"my-bucket", "Asia/India", expectedWriteOnlyStatement("my-bucket", "Asia/India")}, + } + + for i, testCase := range testCases { + actualStaments := setWriteOnlyStatement(testCase.bucketName, testCase.objectPrefix) + if !reflect.DeepEqual(testCase.expectedStatements, actualStaments) { + t.Errorf("Test %d: The expected statements from resource statement generator doesn't match the actual statements", i+1) + } + } +} + +// Tests validating read-write statement generator. +func TestsetReadWriteStatement(t *testing.T) { + // Obtain statements for read-write BucketPolicy. + expectedReadWriteStatement := func(bucketName, objectPrefix string) []Statement { + bucketResourceStatement := &Statement{} + objectResourceStatement := &Statement{} + statements := []Statement{} + + bucketResourceStatement.Effect = "Allow" + bucketResourceStatement.Principal.AWS = []string{"*"} + bucketResourceStatement.Resources = []string{fmt.Sprintf("%s%s", awsResourcePrefix, bucketName)} + bucketResourceStatement.Actions = readWriteBucketActions + objectResourceStatement.Effect = "Allow" + objectResourceStatement.Principal.AWS = []string{"*"} + objectResourceStatement.Resources = []string{fmt.Sprintf("%s%s", awsResourcePrefix, bucketName+"/"+objectPrefix+"*")} + objectResourceStatement.Actions = readWriteObjectActions + // Save the read write policy. + statements = append(statements, *bucketResourceStatement, *objectResourceStatement) + return statements + } + + testCases := []struct { + // inputs. + bucketName string + objectPrefix string + // expected result. + expectedStatements []Statement + }{ + {"my-bucket", "", expectedReadWriteStatement("my-bucket", "")}, + {"my-bucket", "Asia/", expectedReadWriteStatement("my-bucket", "Asia/")}, + {"my-bucket", "Asia/India", expectedReadWriteStatement("my-bucket", "Asia/India")}, + } + + for i, testCase := range testCases { + actualStaments := setReadWriteStatement(testCase.bucketName, testCase.objectPrefix) + if !reflect.DeepEqual(testCase.expectedStatements, actualStaments) { + t.Errorf("Test %d: The expected statements from resource statement generator doesn't match the actual statements", i+1) + } + } +} + +// Tests validate Unmarshalling of BucketAccessPolicy. +func TestUnMarshalBucketPolicy(t *testing.T) { + + bucketAccesPolicies := []BucketAccessPolicy{ + {Version: "1.0"}, + {Version: "1.0", Statements: setReadOnlyStatement("minio-bucket", "")}, + {Version: "1.0", Statements: setReadWriteStatement("minio-bucket", "Asia/")}, + {Version: "1.0", Statements: setWriteOnlyStatement("minio-bucket", "Asia/India/")}, + } + + testCases := []struct { + inputPolicy BucketAccessPolicy + // expected results. + expectedPolicy BucketAccessPolicy + err error + // Flag indicating whether the test should pass. + shouldPass bool + }{ + {bucketAccesPolicies[0], bucketAccesPolicies[0], nil, true}, + {bucketAccesPolicies[1], bucketAccesPolicies[1], nil, true}, + {bucketAccesPolicies[2], bucketAccesPolicies[2], nil, true}, + {bucketAccesPolicies[3], bucketAccesPolicies[3], nil, true}, + } + for i, testCase := range testCases { + inputPolicyBytes, e := json.Marshal(testCase.inputPolicy) + if e != nil { + t.Fatalf("Test %d: Couldn't Marshal bucket policy", i+1) + } + actualAccessPolicy, err := unMarshalBucketPolicy(inputPolicyBytes) + if err != nil && testCase.shouldPass { + t.Errorf("Test %d: Expected to pass, but failed with: %s", i+1, err.Error()) + } + + if err == nil && !testCase.shouldPass { + t.Errorf("Test %d: Expected to fail with \"%s\", but passed instead", i+1, testCase.err.Error()) + } + // Failed as expected, but does it fail for the expected reason. + if err != nil && !testCase.shouldPass { + if err.Error() != testCase.err.Error() { + t.Errorf("Test %d: Expected to fail with error \"%s\", but instead failed with error \"%s\" instead", i+1, testCase.err.Error(), err.Error()) + } + } + // Test passes as expected, but the output values are verified for correctness here. + if err == nil && testCase.shouldPass { + if !reflect.DeepEqual(testCase.expectedPolicy, actualAccessPolicy) { + t.Errorf("Test %d: The expected statements from resource statement generator doesn't match the actual statements", i+1) + } + } + } +} + +// Statement.Action, Statement.Resource, Statement.Principal.AWS fields could be just string also. +// Setting these values to just a string and testing the unMarshalBucketPolicy +func TestUnMarshalBucketPolicyUntyped(t *testing.T) { + obtainRaw := func(v interface{}, t *testing.T) []byte { + rawData, e := json.Marshal(v) + if e != nil { + t.Fatal(e.Error()) + } + return rawData + } + + type untypedStatement struct { + Sid string + Effect string + Principal struct { + AWS json.RawMessage + } + Action json.RawMessage + Resource json.RawMessage + Condition map[string]map[string]string + } + + type bucketAccessPolicyUntyped struct { + Version string + Statement []untypedStatement + } + + statements := setReadOnlyStatement("my-bucket", "Asia/") + expectedBucketPolicy := BucketAccessPolicy{Statements: statements} + accessPolicyUntyped := bucketAccessPolicyUntyped{} + accessPolicyUntyped.Statement = make([]untypedStatement, 2) + + accessPolicyUntyped.Statement[0].Effect = statements[0].Effect + accessPolicyUntyped.Statement[0].Principal.AWS = obtainRaw(statements[0].Principal.AWS, t) + accessPolicyUntyped.Statement[0].Action = obtainRaw(statements[0].Actions, t) + accessPolicyUntyped.Statement[0].Resource = obtainRaw(statements[0].Resources, t) + + // Setting the values are strings. + accessPolicyUntyped.Statement[1].Effect = statements[1].Effect + accessPolicyUntyped.Statement[1].Principal.AWS = obtainRaw(statements[1].Principal.AWS[0], t) + accessPolicyUntyped.Statement[1].Action = obtainRaw(statements[1].Actions[0], t) + accessPolicyUntyped.Statement[1].Resource = obtainRaw(statements[1].Resources[0], t) + + inputPolicyBytes := obtainRaw(accessPolicyUntyped, t) + actualAccessPolicy, err := unMarshalBucketPolicy(inputPolicyBytes) + if err != nil { + t.Fatal("Unmarshalling bucket policy from untyped statements failed") + } + if !reflect.DeepEqual(expectedBucketPolicy, actualAccessPolicy) { + t.Errorf("Expected BucketPolicy after unmarshalling untyped statements doesn't match the actual one") + } +} + +// Tests validate removal of policy statement from the list of statements. +func TestRemoveBucketPolicyStatement(t *testing.T) { + testCases := []struct { + bucketName string + objectPrefix string + inputStatements []Statement + }{ + {"my-bucket", "", []Statement{}}, + {"read-only-bucket", "", setReadOnlyStatement("read-only-bucket", "")}, + {"write-only-bucket", "", setWriteOnlyStatement("write-only-bucket", "")}, + {"read-write-bucket", "", setReadWriteStatement("read-write-bucket", "")}, + } + for i, testCase := range testCases { + actualStatements := removeBucketPolicyStatement(testCase.inputStatements, testCase.bucketName, testCase.objectPrefix) + // empty statement is expected after the invocation of removeBucketPolicyStatement(). + if len(actualStatements) != 0 { + t.Errorf("Test %d: The expected statements from resource statement generator doesn't match the actual statements", i+1) + } + } +} + +// Tests validate removing of read only bucket statement. +func TestRemoveBucketPolicyStatementReadOnly(t *testing.T) { + var emptyStatement []Statement + testCases := []struct { + bucketName string + objectPrefix string + inputStatements []Statement + expectedStatements []Statement + }{ + {"my-bucket", "", []Statement{}, emptyStatement}, + {"read-only-bucket", "", setReadOnlyStatement("read-only-bucket", ""), emptyStatement}, + } + for i, testCase := range testCases { + actualStatements := removeBucketPolicyStatementReadOnly(testCase.inputStatements, testCase.bucketName, testCase.objectPrefix) + // empty statement is expected after the invocation of removeBucketPolicyStatement(). + if !reflect.DeepEqual(testCase.expectedStatements, actualStatements) { + t.Errorf("Test %d: Expected policy statements doesn't match the actual one", i+1) + } + } +} + +// Tests validate removing of write only bucket statement. +func TestRemoveBucketPolicyStatementWriteOnly(t *testing.T) { + var emptyStatement []Statement + testCases := []struct { + bucketName string + objectPrefix string + inputStatements []Statement + expectedStatements []Statement + }{ + {"my-bucket", "", []Statement{}, emptyStatement}, + {"write-only-bucket", "", setWriteOnlyStatement("write-only-bucket", ""), emptyStatement}, + } + for i, testCase := range testCases { + actualStatements := removeBucketPolicyStatementWriteOnly(testCase.inputStatements, testCase.bucketName, testCase.objectPrefix) + // empty statement is expected after the invocation of removeBucketPolicyStatement(). + if !reflect.DeepEqual(testCase.expectedStatements, actualStatements) { + t.Errorf("Test %d: Expected policy statements doesn't match the actual one", i+1) + } + } +} + +// Tests validate removing of read-write bucket statement. +func TestRemoveBucketPolicyStatementReadWrite(t *testing.T) { + var emptyStatement []Statement + testCases := []struct { + bucketName string + objectPrefix string + inputStatements []Statement + expectedStatements []Statement + }{ + {"my-bucket", "", []Statement{}, emptyStatement}, + {"read-write-bucket", "", setReadWriteStatement("read-write-bucket", ""), emptyStatement}, + } + for i, testCase := range testCases { + actualStatements := removeBucketPolicyStatementReadWrite(testCase.inputStatements, testCase.bucketName, testCase.objectPrefix) + // empty statement is expected after the invocation of removeBucketPolicyStatement(). + if !reflect.DeepEqual(testCase.expectedStatements, actualStatements) { + t.Errorf("Test %d: Expected policy statements doesn't match the actual one", i+1) + } + } +} + +// Tests validate whether the bucket policy is read only. +func TestIsBucketPolicyReadOnly(t *testing.T) { + testCases := []struct { + bucketName string + objectPrefix string + inputStatements []Statement + // expected result. + expectedResult bool + }{ + {"my-bucket", "", []Statement{}, false}, + {"read-only-bucket", "", setReadOnlyStatement("read-only-bucket", ""), true}, + {"write-only-bucket", "", setWriteOnlyStatement("write-only-bucket", ""), false}, + {"read-write-bucket", "", setReadWriteStatement("read-write-bucket", ""), true}, + } + for i, testCase := range testCases { + actualResult := isBucketPolicyReadOnly(testCase.inputStatements, testCase.bucketName, testCase.objectPrefix) + // empty statement is expected after the invocation of removeBucketPolicyStatement(). + if testCase.expectedResult != actualResult { + t.Errorf("Test %d: Expected isBucketPolicyReadonly to '%v', but instead found '%v'", i+1, testCase.expectedResult, actualResult) + } + } +} + +// Tests validate whether the bucket policy is read-write. +func TestIsBucketPolicyReadWrite(t *testing.T) { + testCases := []struct { + bucketName string + objectPrefix string + inputStatements []Statement + // expected result. + expectedResult bool + }{ + {"my-bucket", "", []Statement{}, false}, + {"read-only-bucket", "", setReadOnlyStatement("read-only-bucket", ""), false}, + {"write-only-bucket", "", setWriteOnlyStatement("write-only-bucket", ""), false}, + {"read-write-bucket", "", setReadWriteStatement("read-write-bucket", ""), true}, + } + for i, testCase := range testCases { + actualResult := isBucketPolicyReadWrite(testCase.inputStatements, testCase.bucketName, testCase.objectPrefix) + // empty statement is expected after the invocation of removeBucketPolicyStatement(). + if testCase.expectedResult != actualResult { + t.Errorf("Test %d: Expected isBucketPolicyReadonly to '%v', but instead found '%v'", i+1, testCase.expectedResult, actualResult) + } + } +} + +// Tests validate whether the bucket policy is read only. +func TestIsBucketPolicyWriteOnly(t *testing.T) { + testCases := []struct { + bucketName string + objectPrefix string + inputStatements []Statement + // expected result. + expectedResult bool + }{ + {"my-bucket", "", []Statement{}, false}, + {"read-only-bucket", "", setReadOnlyStatement("read-only-bucket", ""), false}, + {"write-only-bucket", "", setWriteOnlyStatement("write-only-bucket", ""), true}, + {"read-write-bucket", "", setReadWriteStatement("read-write-bucket", ""), true}, + } + for i, testCase := range testCases { + actualResult := isBucketPolicyWriteOnly(testCase.inputStatements, testCase.bucketName, testCase.objectPrefix) + // empty statement is expected after the invocation of removeBucketPolicyStatement(). + if testCase.expectedResult != actualResult { + t.Errorf("Test %d: Expected isBucketPolicyReadonly to '%v', but instead found '%v'", i+1, testCase.expectedResult, actualResult) + } + } +} diff --git a/vendor/src/github.com/minio/minio-go/copy-conditions.go b/vendor/src/github.com/minio/minio-go/copy-conditions.go new file mode 100644 index 000000000..9dd63f65e --- /dev/null +++ b/vendor/src/github.com/minio/minio-go/copy-conditions.go @@ -0,0 +1,97 @@ +/* + * Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2016 Minio, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package minio + +import ( + "net/http" + "time" +) + +// copyCondition explanation: +// http://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectCOPY.html +// +// Example: +// +// copyCondition { +// key: "x-amz-copy-if-modified-since", +// value: "Tue, 15 Nov 1994 12:45:26 GMT", +// } +// +type copyCondition struct { + key string + value string +} + +// CopyConditions - copy conditions. +type CopyConditions struct { + conditions []copyCondition +} + +// NewCopyConditions - Instantiate new list of conditions. +func NewCopyConditions() CopyConditions { + return CopyConditions{ + conditions: make([]copyCondition, 0), + } +} + +// SetMatchETag - set match etag. +func (c CopyConditions) SetMatchETag(etag string) error { + if etag == "" { + return ErrInvalidArgument("ETag cannot be empty.") + } + c.conditions = append(c.conditions, copyCondition{ + key: "x-amz-copy-source-if-match", + value: etag, + }) + return nil +} + +// SetMatchETagExcept - set match etag except. +func (c CopyConditions) SetMatchETagExcept(etag string) error { + if etag == "" { + return ErrInvalidArgument("ETag cannot be empty.") + } + c.conditions = append(c.conditions, copyCondition{ + key: "x-amz-copy-source-if-none-match", + value: etag, + }) + return nil +} + +// SetUnmodified - set unmodified time since. +func (c CopyConditions) SetUnmodified(modTime time.Time) error { + if modTime.IsZero() { + return ErrInvalidArgument("Modified since cannot be empty.") + } + c.conditions = append(c.conditions, copyCondition{ + key: "x-amz-copy-source-if-unmodified-since", + value: modTime.Format(http.TimeFormat), + }) + return nil +} + +// SetModified - set modified time since. +func (c CopyConditions) SetModified(modTime time.Time) error { + if modTime.IsZero() { + return ErrInvalidArgument("Modified since cannot be empty.") + } + c.conditions = append(c.conditions, copyCondition{ + key: "x-amz-copy-source-if-modified-since", + value: modTime.Format(http.TimeFormat), + }) + return nil +} diff --git a/vendor/src/github.com/minio/minio-go/examples/play/bucketexists.go b/vendor/src/github.com/minio/minio-go/examples/play/bucketexists.go deleted file mode 100644 index 0629d0a2d..000000000 --- a/vendor/src/github.com/minio/minio-go/examples/play/bucketexists.go +++ /dev/null @@ -1,46 +0,0 @@ -// +build ignore - -/* - * Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015 Minio, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package main - -import ( - "log" - - "github.com/minio/minio-go" -) - -func main() { - // Note: my-bucketname is a dummy value, please replace them with original value. - - // Requests are always secure by default. set inSecure=true to enable insecure access. - // inSecure boolean is the last argument for New(). - - // New provides a client object backend by automatically detected signature type based - // on the provider. - s3Client, err := minio.New("play.minio.io:9002", "Q3AM3UQ867SPQQA43P2F", "zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG", false) - if err != nil { - log.Fatalln(err) - } - - err = s3Client.BucketExists("my-bucketname") - if err != nil { - log.Fatalln(err) - } - - log.Println("Success") -} diff --git a/vendor/src/github.com/minio/minio-go/examples/play/fgetobject.go b/vendor/src/github.com/minio/minio-go/examples/play/fgetobject.go deleted file mode 100644 index 57856a578..000000000 --- a/vendor/src/github.com/minio/minio-go/examples/play/fgetobject.go +++ /dev/null @@ -1,44 +0,0 @@ -// +build ignore - -/* - * Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015 Minio, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package main - -import ( - "log" - - "github.com/minio/minio-go" -) - -func main() { - // Note: my-bucketname, my-objectname and my-filename.csv are dummy values, please replace them with original values. - - // Requests are always secure by default. set inSecure=true to enable insecure access. - // inSecure boolean is the last argument for New(). - - // New provides a client object backend by automatically detected signature type based - // on the provider. - s3Client, err := minio.New("play.minio.io:9002", "Q3AM3UQ867SPQQA43P2F", "zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG", false) - if err != nil { - log.Fatalln(err) - } - - if err := s3Client.FGetObject("bucket-name", "objectName", "fileName.csv"); err != nil { - log.Fatalln(err) - } - log.Println("Successfully saved fileName.csv") -} diff --git a/vendor/src/github.com/minio/minio-go/examples/play/fputobject.go b/vendor/src/github.com/minio/minio-go/examples/play/fputobject.go deleted file mode 100644 index 5f85b5c07..000000000 --- a/vendor/src/github.com/minio/minio-go/examples/play/fputobject.go +++ /dev/null @@ -1,44 +0,0 @@ -// +build ignore - -/* - * Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015 Minio, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package main - -import ( - "log" - - "github.com/minio/minio-go" -) - -func main() { - // Note: my-bucketname, my-objectname and my-filename.csv are dummy values, please replace them with original values. - - // Requests are always secure (HTTPS) by default. Set insecure=true to enable insecure (HTTP) access. - // This boolean value is the last argument for New(). - - // New returns an Amazon S3 compatible client object. API copatibality (v2 or v4) is automatically - // determined based on the Endpoint value. - s3Client, err := minio.New("play.minio.io:9002", "Q3AM3UQ867SPQQA43P2F", "zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG", false) - if err != nil { - log.Fatalln(err) - } - - if _, err := s3Client.FPutObject("my-bucketname", "my-objectname", "my-filename.csv", "application/csv"); err != nil { - log.Fatalln(err) - } - log.Println("Successfully uploaded my-filename.csv") -} diff --git a/vendor/src/github.com/minio/minio-go/examples/play/getbucketacl.go b/vendor/src/github.com/minio/minio-go/examples/play/getbucketacl.go deleted file mode 100644 index 202baa3a3..000000000 --- a/vendor/src/github.com/minio/minio-go/examples/play/getbucketacl.go +++ /dev/null @@ -1,46 +0,0 @@ -// +build ignore - -/* - * Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015 Minio, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package main - -import ( - "log" - - "github.com/minio/minio-go" -) - -func main() { - // Note: my-bucketname is a dummy value, please replace them with original value. - - // Requests are always secure by default. set inSecure=true to enable insecure access. - // inSecure boolean is the last argument for New(). - - // New provides a client object backend by automatically detected signature type based - // on the provider. - s3Client, err := minio.New("play.minio.io:9002", "Q3AM3UQ867SPQQA43P2F", "zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG", false) - if err != nil { - log.Fatalln(err) - } - - acl, err := s3Client.GetBucketACL("my-bucketname") - if err != nil { - log.Fatalln(err) - } - log.Println(acl) - -} diff --git a/vendor/src/github.com/minio/minio-go/examples/play/listbuckets.go b/vendor/src/github.com/minio/minio-go/examples/play/listbuckets.go deleted file mode 100644 index b5e505ccc..000000000 --- a/vendor/src/github.com/minio/minio-go/examples/play/listbuckets.go +++ /dev/null @@ -1,45 +0,0 @@ -// +build ignore - -/* - * Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015 Minio, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package main - -import ( - "log" - - "github.com/minio/minio-go" -) - -func main() { - // Requests are always secure by default. set inSecure=true to enable insecure access. - // inSecure boolean is the last argument for New(). - - // New provides a client object backend by automatically detected signature type based - // on the provider. - s3Client, err := minio.New("play.minio.io:9002", "Q3AM3UQ867SPQQA43P2F", "zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG", false) - if err != nil { - log.Fatalln(err) - } - - buckets, err := s3Client.ListBuckets() - if err != nil { - log.Fatalln(err) - } - for _, bucket := range buckets { - log.Println(bucket) - } -} diff --git a/vendor/src/github.com/minio/minio-go/examples/play/listincompleteuploads.go b/vendor/src/github.com/minio/minio-go/examples/play/listincompleteuploads.go deleted file mode 100644 index 23219f21c..000000000 --- a/vendor/src/github.com/minio/minio-go/examples/play/listincompleteuploads.go +++ /dev/null @@ -1,56 +0,0 @@ -// +build ignore - -/* - * Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015 Minio, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package main - -import ( - "fmt" - "log" - - "github.com/minio/minio-go" -) - -func main() { - // Note: my-bucketname and my-prefixname are dummy values, please replace them with original values. - - // Requests are always secure by default. set inSecure=true to enable insecure access. - // inSecure boolean is the last argument for New(). - - // New provides a client object backend by automatically detected signature type based - // on the provider. - s3Client, err := minio.New("play.minio.io:9002", "Q3AM3UQ867SPQQA43P2F", "zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG", false) - if err != nil { - log.Fatalln(err) - } - - // Create a done channel to control 'ListObjects' go routine. - doneCh := make(chan struct{}) - - // Indicate to our routine to exit cleanly upon return. - defer close(doneCh) - - // List all multipart uploads from a bucket-name with a matching prefix. - for multipartObject := range s3Client.ListIncompleteUploads("my-bucketname", "my-prefixname", true, doneCh) { - if multipartObject.Err != nil { - fmt.Println(multipartObject.Err) - return - } - fmt.Println(multipartObject) - } - return -} diff --git a/vendor/src/github.com/minio/minio-go/examples/play/listobjects.go b/vendor/src/github.com/minio/minio-go/examples/play/listobjects.go deleted file mode 100644 index eaa57e9e1..000000000 --- a/vendor/src/github.com/minio/minio-go/examples/play/listobjects.go +++ /dev/null @@ -1,56 +0,0 @@ -// +build ignore - -/* - * Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015 Minio, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package main - -import ( - "fmt" - "log" - - "github.com/minio/minio-go" -) - -func main() { - // Note: my-bucketname and my-prefixname are dummy values, please replace them with original values. - - // Requests are always secure by default. set inSecure=true to enable insecure access. - // inSecure boolean is the last argument for New(). - - // New provides a client object backend by automatically detected signature type based - // on the provider. - s3Client, err := minio.New("play.minio.io:9002", "Q3AM3UQ867SPQQA43P2F", "zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG", false) - if err != nil { - log.Fatalln(err) - } - - // Create a done channel to control 'ListObjects' go routine. - doneCh := make(chan struct{}) - - // Indicate to our routine to exit cleanly upon return. - defer close(doneCh) - - // List all objects from a bucket-name with a matching prefix. - for object := range s3Client.ListObjects("my-bucketname", "my-prefixname", true, doneCh) { - if object.Err != nil { - fmt.Println(object.Err) - return - } - fmt.Println(object) - } - return -} diff --git a/vendor/src/github.com/minio/minio-go/examples/play/makebucket.go b/vendor/src/github.com/minio/minio-go/examples/play/makebucket.go deleted file mode 100644 index 52bebf1a5..000000000 --- a/vendor/src/github.com/minio/minio-go/examples/play/makebucket.go +++ /dev/null @@ -1,45 +0,0 @@ -// +build ignore - -/* - * Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015 Minio, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package main - -import ( - "log" - - "github.com/minio/minio-go" -) - -func main() { - // Note: my-bucketname is a dummy value, please replace them with original value. - - // Requests are always secure by default. set inSecure=true to enable insecure access. - // inSecure boolean is the last argument for New(). - - // New provides a client object backend by automatically detected signature type based - // on the provider. - s3Client, err := minio.New("play.minio.io:9002", "Q3AM3UQ867SPQQA43P2F", "zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG", false) - if err != nil { - log.Fatalln(err) - } - - err = s3Client.MakeBucket("my-bucketname", minio.BucketACL("private"), "us-east-1") - if err != nil { - log.Fatalln(err) - } - log.Println("Success") -} diff --git a/vendor/src/github.com/minio/minio-go/examples/play/presignedgetobject.go b/vendor/src/github.com/minio/minio-go/examples/play/presignedgetobject.go deleted file mode 100644 index 2ba878a97..000000000 --- a/vendor/src/github.com/minio/minio-go/examples/play/presignedgetobject.go +++ /dev/null @@ -1,46 +0,0 @@ -// +build ignore - -/* - * Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015 Minio, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package main - -import ( - "log" - "time" - - "github.com/minio/minio-go" -) - -func main() { - // Note: my-bucketname and my-objectname are dummy values, please replace them with original values. - - // Requests are always secure by default. set inSecure=true to enable insecure access. - // inSecure boolean is the last argument for New(). - - // New provides a client object backend by automatically detected signature type based - // on the provider. - s3Client, err := minio.New("play.minio.io:9002", "Q3AM3UQ867SPQQA43P2F", "zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG", false) - if err != nil { - log.Fatalln(err) - } - - presignedURL, err := s3Client.PresignedGetObject("my-bucketname", "my-objectname", time.Duration(1000)*time.Second) - if err != nil { - log.Fatalln(err) - } - log.Println(presignedURL) -} diff --git a/vendor/src/github.com/minio/minio-go/examples/play/presignedpostpolicy.go b/vendor/src/github.com/minio/minio-go/examples/play/presignedpostpolicy.go deleted file mode 100644 index 65fa66ddf..000000000 --- a/vendor/src/github.com/minio/minio-go/examples/play/presignedpostpolicy.go +++ /dev/null @@ -1,56 +0,0 @@ -// +build ignore - -/* - * Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015 Minio, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package main - -import ( - "fmt" - "log" - "time" - - "github.com/minio/minio-go" -) - -func main() { - // Note: my-bucketname and my-objectname are dummy values, please replace them with original values. - - // Requests are always secure by default. set inSecure=true to enable insecure access. - // inSecure boolean is the last argument for New(). - - // New provides a client object backend by automatically detected signature type based - // on the provider. - s3Client, err := minio.New("play.minio.io:9002", "Q3AM3UQ867SPQQA43P2F", "zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG", false) - if err != nil { - log.Fatalln(err) - } - - policy := minio.NewPostPolicy() - policy.SetBucket("my-bucketname") - policy.SetKey("my-objectname") - policy.SetExpires(time.Now().UTC().AddDate(0, 0, 10)) // expires in 10 days - m, err := s3Client.PresignedPostPolicy(policy) - if err != nil { - log.Fatalln(err) - } - fmt.Printf("curl ") - for k, v := range m { - fmt.Printf("-F %s=%s ", k, v) - } - fmt.Printf("-F file=@/etc/bashrc ") - fmt.Printf("https://play.minio.io:9002/my-bucketname\n") -} diff --git a/vendor/src/github.com/minio/minio-go/examples/play/presignedputobject.go b/vendor/src/github.com/minio/minio-go/examples/play/presignedputobject.go deleted file mode 100644 index b55f721f7..000000000 --- a/vendor/src/github.com/minio/minio-go/examples/play/presignedputobject.go +++ /dev/null @@ -1,46 +0,0 @@ -// +build ignore - -/* - * Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015 Minio, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package main - -import ( - "log" - "time" - - "github.com/minio/minio-go" -) - -func main() { - // Note: my-bucketname and my-objectname are dummy values, please replace them with original values. - - // Requests are always secure by default. set inSecure=true to enable insecure access. - // inSecure boolean is the last argument for New(). - - // New provides a client object backend by automatically detected signature type based - // on the provider. - s3Client, err := minio.New("play.minio.io:9002", "Q3AM3UQ867SPQQA43P2F", "zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG", false) - if err != nil { - log.Fatalln(err) - } - - presignedURL, err := s3Client.PresignedPutObject("my-bucketname", "my-objectname", time.Duration(1000)*time.Second) - if err != nil { - log.Fatalln(err) - } - log.Println(presignedURL) -} diff --git a/vendor/src/github.com/minio/minio-go/examples/play/putobject.go b/vendor/src/github.com/minio/minio-go/examples/play/putobject.go deleted file mode 100644 index 073f75870..000000000 --- a/vendor/src/github.com/minio/minio-go/examples/play/putobject.go +++ /dev/null @@ -1,52 +0,0 @@ -// +build ignore - -/* - * Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015 Minio, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package main - -import ( - "log" - "os" - - "github.com/minio/minio-go" -) - -func main() { - // Note: my-bucketname, my-objectname and my-testfile are dummy values, please replace them with original values. - - // Requests are always secure by default. set inSecure=true to enable insecure access. - // inSecure boolean is the last argument for New(). - - // New provides a client object backend by automatically detected signature type based - // on the provider. - s3Client, err := minio.New("play.minio.io:9002", "Q3AM3UQ867SPQQA43P2F", "zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG", false) - if err != nil { - log.Fatalln(err) - } - - object, err := os.Open("my-testfile") - if err != nil { - log.Fatalln(err) - } - defer object.Close() - - n, err := s3Client.PutObject("my-bucketname", "my-objectname", object, "application/octet-stream") - if err != nil { - log.Fatalln(err) - } - log.Println("Uploaded", "my-objectname", " of size: ", n, "Successfully.") -} diff --git a/vendor/src/github.com/minio/minio-go/examples/play/removebucket.go b/vendor/src/github.com/minio/minio-go/examples/play/removebucket.go deleted file mode 100644 index 1d2d03ba3..000000000 --- a/vendor/src/github.com/minio/minio-go/examples/play/removebucket.go +++ /dev/null @@ -1,46 +0,0 @@ -// +build ignore - -/* - * Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015 Minio, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package main - -import ( - "log" - - "github.com/minio/minio-go" -) - -func main() { - // Note: my-bucketname is a dummy value, please replace them with original value. - - // Requests are always secure by default. set inSecure=true to enable insecure access. - // inSecure boolean is the last argument for New(). - - // New provides a client object backend by automatically detected signature type based - // on the provider. - s3Client, err := minio.New("play.minio.io:9002", "Q3AM3UQ867SPQQA43P2F", "zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG", false) - if err != nil { - log.Fatalln(err) - } - // This operation will only work if your bucket is empty. - err = s3Client.RemoveBucket("my-bucketname") - if err != nil { - log.Fatalln(err) - } - log.Println("Success") - -} diff --git a/vendor/src/github.com/minio/minio-go/examples/play/removeincompleteupload.go b/vendor/src/github.com/minio/minio-go/examples/play/removeincompleteupload.go deleted file mode 100644 index 458a4c450..000000000 --- a/vendor/src/github.com/minio/minio-go/examples/play/removeincompleteupload.go +++ /dev/null @@ -1,46 +0,0 @@ -// +build ignore - -/* - * Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015 Minio, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package main - -import ( - "log" - - "github.com/minio/minio-go" -) - -func main() { - // Note: my-bucketname and my-objectname are dummy values, please replace them with original values. - - // Requests are always secure by default. set inSecure=true to enable insecure access. - // inSecure boolean is the last argument for New(). - - // New provides a client object backend by automatically detected signature type based - // on the provider. - s3Client, err := minio.New("play.minio.io:9002", "Q3AM3UQ867SPQQA43P2F", "zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG", false) - if err != nil { - log.Fatalln(err) - } - - for err := range s3Client.RemoveIncompleteUpload("my-bucketname", "my-objectname") { - if err != nil { - log.Fatalln(err) - } - } - log.Println("Success") -} diff --git a/vendor/src/github.com/minio/minio-go/examples/play/removeobject.go b/vendor/src/github.com/minio/minio-go/examples/play/removeobject.go deleted file mode 100644 index 2301a77de..000000000 --- a/vendor/src/github.com/minio/minio-go/examples/play/removeobject.go +++ /dev/null @@ -1,44 +0,0 @@ -// +build ignore - -/* - * Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015 Minio, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package main - -import ( - "log" - - "github.com/minio/minio-go" -) - -func main() { - // Note: my-bucketname and my-objectname are dummy values, please replace them with original values. - - // Requests are always secure by default. set inSecure=true to enable insecure access. - // inSecure boolean is the last argument for New(). - - // New provides a client object backend by automatically detected signature type based - // on the provider. - s3Client, err := minio.New("play.minio.io:9002", "Q3AM3UQ867SPQQA43P2F", "zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG", false) - if err != nil { - log.Fatalln(err) - } - err = s3Client.RemoveObject("my-bucketname", "my-objectname") - if err != nil { - log.Fatalln(err) - } - log.Println("Success") -} diff --git a/vendor/src/github.com/minio/minio-go/examples/play/setbucketacl.go b/vendor/src/github.com/minio/minio-go/examples/play/setbucketacl.go deleted file mode 100644 index 7893018f7..000000000 --- a/vendor/src/github.com/minio/minio-go/examples/play/setbucketacl.go +++ /dev/null @@ -1,45 +0,0 @@ -// +build ignore - -/* - * Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015 Minio, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package main - -import ( - "log" - - "github.com/minio/minio-go" -) - -func main() { - // Note: my-bucketname is a dummy value, please replace them with original value. - - // Requests are always secure by default. set inSecure=true to enable insecure access. - // inSecure boolean is the last argument for New(). - - // New provides a client object backend by automatically detected signature type based - // on the provider. - s3Client, err := minio.New("play.minio.io:9002", "Q3AM3UQ867SPQQA43P2F", "zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG", false) - if err != nil { - log.Fatalln(err) - } - - err = s3Client.SetBucketACL("my-bucketname", minio.BucketACL("public-read-write")) - if err != nil { - log.Fatalln(err) - } - -} diff --git a/vendor/src/github.com/minio/minio-go/examples/play/statobject.go b/vendor/src/github.com/minio/minio-go/examples/play/statobject.go deleted file mode 100644 index 8f24460ab..000000000 --- a/vendor/src/github.com/minio/minio-go/examples/play/statobject.go +++ /dev/null @@ -1,44 +0,0 @@ -// +build ignore - -/* - * Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015 Minio, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package main - -import ( - "log" - - "github.com/minio/minio-go" -) - -func main() { - // Note: my-bucketname and my-objectname are dummy values, please replace them with original values. - - // Requests are always secure by default. set inSecure=true to enable insecure access. - // inSecure boolean is the last argument for New(). - - // New provides a client object backend by automatically detected signature type based - // on the provider. - s3Client, err := minio.New("play.minio.io:9002", "Q3AM3UQ867SPQQA43P2F", "zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG", false) - if err != nil { - log.Fatalln(err) - } - stat, err := s3Client.StatObject("my-bucketname", "my-objectname") - if err != nil { - log.Fatalln(err) - } - log.Println(stat) -} diff --git a/vendor/src/github.com/minio/minio-go/examples/s3/copyobject.go b/vendor/src/github.com/minio/minio-go/examples/s3/copyobject.go new file mode 100644 index 000000000..5517c2e98 --- /dev/null +++ b/vendor/src/github.com/minio/minio-go/examples/s3/copyobject.go @@ -0,0 +1,67 @@ +// +build ignore + +/* + * Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2016 Minio, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package main + +import ( + "log" + "time" + + "github.com/minio/minio-go" +) + +func main() { + // Note: YOUR-ACCESSKEYID, YOUR-SECRETACCESSKEY, my-testfile, my-bucketname and + // my-objectname are dummy values, please replace them with original values. + + // Requests are always secure (HTTPS) by default. Set insecure=true to enable insecure (HTTP) access. + // This boolean value is the last argument for New(). + + // New returns an Amazon S3 compatible client object. API copatibality (v2 or v4) is automatically + // determined based on the Endpoint value. + s3Client, err := minio.New("s3.amazonaws.com", "YOUR-ACCESSKEYID", "YOUR-SECRETACCESSKEY", false) + if err != nil { + log.Fatalln(err) + } + + // Enable trace. + // s3Client.TraceOn(os.Stderr) + + // All following conditions are allowed and can be combined together. + + // Set copy conditions. + var copyConds = minio.NewCopyConditions() + // Set modified condition, copy object modified since 2014 April. + copyConds.SetModified(time.Date(2014, time.April, 0, 0, 0, 0, 0, time.UTC)) + + // Set unmodified condition, copy object unmodified since 2014 April. + // copyConds.SetUnmodified(time.Date(2014, time.April, 0, 0, 0, 0, 0, time.UTC)) + + // Set matching ETag condition, copy object which matches the following ETag. + // copyConds.SetMatchETag("31624deb84149d2f8ef9c385918b653a") + + // Set matching ETag except condition, copy object which does not match the following ETag. + // copyConds.SetMatchETagExcept("31624deb84149d2f8ef9c385918b653a") + + // Initiate copy object. + err = s3Client.CopyObject("my-bucketname", "my-objectname", "/my-sourcebucketname/my-sourceobjectname", copyConds) + if err != nil { + log.Fatalln(err) + } + log.Println("Copied source object /my-sourcebucketname/my-sourceobjectname to destination /my-bucketname/my-objectname Successfully.") +} diff --git a/vendor/src/github.com/minio/minio-go/examples/play/getobject.go b/vendor/src/github.com/minio/minio-go/examples/s3/getbucketpolicy.go similarity index 61% rename from vendor/src/github.com/minio/minio-go/examples/play/getobject.go rename to vendor/src/github.com/minio/minio-go/examples/s3/getbucketpolicy.go index 96b584253..843954f49 100644 --- a/vendor/src/github.com/minio/minio-go/examples/play/getobject.go +++ b/vendor/src/github.com/minio/minio-go/examples/s3/getbucketpolicy.go @@ -1,7 +1,7 @@ // +build ignore /* - * Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015 Minio, Inc. + * Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015, 2016 Minio, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,44 +19,35 @@ package main import ( - "io" "log" - "os" "github.com/minio/minio-go" ) func main() { - // Note: my-bucketname, my-objectname and my-testfile are dummy values, please replace them with original values. + // Note: YOUR-ACCESSKEYID, YOUR-SECRETACCESSKEY and my-bucketname are + // dummy values, please replace them with original values. // Requests are always secure (HTTPS) by default. Set insecure=true to enable insecure (HTTP) access. // This boolean value is the last argument for New(). // New returns an Amazon S3 compatible client object. API copatibality (v2 or v4) is automatically // determined based on the Endpoint value. - s3Client, err := minio.New("play.minio.io:9002", "Q3AM3UQ867SPQQA43P2F", "zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG", false) + s3Client, err := minio.New("s3.amazonaws.com", "YOUR-ACCESSKEYID", "YOUR-SECRETACCESSKEY", false) if err != nil { log.Fatalln(err) } - reader, err := s3Client.GetObject("my-bucketname", "my-objectname") + // s3Client.TraceOn(os.Stderr) + + policy, err := s3Client.GetBucketPolicy("my-bucketname", "my-objectprefix") if err != nil { log.Fatalln(err) } - defer reader.Close() - - localFile, err := os.Create("my-testfile") - if err != nil { - log.Fatalln(err) - } - defer localfile.Close() - - stat, err := reader.Stat() - if err != nil { - log.Fatalln(err) - } - - if _, err := io.CopyN(localFile, reader, stat.Size); err != nil { - log.Fatalln(err) - } + // Description of policy output. + // "none" - The specified bucket does not have a bucket policy. + // "readonly" - Read only operatoins are allowed. + // "writeonly" - Write only operations are allowed. + // "readwrite" - both read and write operations are allowed, the bucket is public. + log.Println("Success - ", policy) } diff --git a/vendor/src/github.com/minio/minio-go/examples/s3/getobject.go b/vendor/src/github.com/minio/minio-go/examples/s3/getobject.go index 9413dc5e5..f49c71223 100644 --- a/vendor/src/github.com/minio/minio-go/examples/s3/getobject.go +++ b/vendor/src/github.com/minio/minio-go/examples/s3/getobject.go @@ -50,7 +50,7 @@ func main() { if err != nil { log.Fatalln(err) } - defer localfile.Close() + defer localFile.Close() stat, err := reader.Stat() if err != nil { diff --git a/vendor/src/github.com/minio/minio-go/examples/s3/listobjects-N.go b/vendor/src/github.com/minio/minio-go/examples/s3/listobjects-N.go new file mode 100644 index 000000000..81136f92e --- /dev/null +++ b/vendor/src/github.com/minio/minio-go/examples/s3/listobjects-N.go @@ -0,0 +1,76 @@ +// +build ignore + +/* + * Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015 Minio, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package main + +import ( + "fmt" + + "github.com/minio/minio-go" +) + +func main() { + // Note: YOUR-ACCESSKEYID, YOUR-SECRETACCESSKEY, my-bucketname and my-prefixname + // are dummy values, please replace them with original values. + + // Requests are always secure (HTTPS) by default. Set insecure=true to enable insecure (HTTP) access. + // This boolean value is the last argument for New(). + + // New returns an Amazon S3 compatible client object. API copatibality (v2 or v4) is automatically + // determined based on the Endpoint value. + s3Client, err := minio.New("s3.amazonaws.com", "YOUR-ACCESSKEYID", "YOUR-SECRETACCESSKEY", false) + if err != nil { + fmt.Println(err) + return + } + + // List 'N' number of objects from a bucket-name with a matching prefix. + listObjectsN := func(bucket, prefix string, recursive bool, N int) (objsInfo []minio.ObjectInfo, err error) { + // Create a done channel to control 'ListObjects' go routine. + doneCh := make(chan struct{}, 1) + + // Free the channel upon return. + defer close(doneCh) + + i := 1 + for object := range s3Client.ListObjects(bucket, prefix, recursive, doneCh) { + if object.Err != nil { + return nil, object.Err + } + i++ + // Verify if we have printed N objects. + if i == N { + // Indicate ListObjects go-routine to exit and stop + // feeding the objectInfo channel. + doneCh <- struct{}{} + } + objsInfo = append(objsInfo, object) + } + return objsInfo, nil + } + + // List recursively first 100 entries for prefix 'my-prefixname'. + recursive := true + objsInfo, err := listObjectsN("my-bucketname", "my-prefixname", recursive, 100) + if err != nil { + fmt.Println(err) + } + + // Print all the entries. + fmt.Println(objsInfo) +} diff --git a/vendor/src/github.com/minio/minio-go/examples/s3/makebucket.go b/vendor/src/github.com/minio/minio-go/examples/s3/makebucket.go index 22f9e18f2..02a09e553 100644 --- a/vendor/src/github.com/minio/minio-go/examples/s3/makebucket.go +++ b/vendor/src/github.com/minio/minio-go/examples/s3/makebucket.go @@ -38,7 +38,7 @@ func main() { log.Fatalln(err) } - err = s3Client.MakeBucket("my-bucketname", minio.BucketACL("private"), "us-east-1") + err = s3Client.MakeBucket("my-bucketname", "us-east-1") if err != nil { log.Fatalln(err) } diff --git a/vendor/src/github.com/minio/minio-go/examples/s3/presignedgetobject.go b/vendor/src/github.com/minio/minio-go/examples/s3/presignedgetobject.go index 08929cdc0..117f2ec23 100644 --- a/vendor/src/github.com/minio/minio-go/examples/s3/presignedgetobject.go +++ b/vendor/src/github.com/minio/minio-go/examples/s3/presignedgetobject.go @@ -20,6 +20,7 @@ package main import ( "log" + "net/url" "time" "github.com/minio/minio-go" @@ -39,7 +40,12 @@ func main() { log.Fatalln(err) } - presignedURL, err := s3Client.PresignedGetObject("my-bucketname", "my-objectname", time.Duration(1000)*time.Second) + // Set request parameters + reqParams := make(url.Values) + reqParams.Set("response-content-disposition", "attachment; filename=\"your-filename.txt\"") + + // Gernerate presigned get object url. + presignedURL, err := s3Client.PresignedGetObject("my-bucketname", "my-objectname", time.Duration(1000)*time.Second, reqParams) if err != nil { log.Fatalln(err) } diff --git a/vendor/src/github.com/minio/minio-go/examples/s3/getbucketacl.go b/vendor/src/github.com/minio/minio-go/examples/s3/removebucketpolicy.go similarity index 91% rename from vendor/src/github.com/minio/minio-go/examples/s3/getbucketacl.go rename to vendor/src/github.com/minio/minio-go/examples/s3/removebucketpolicy.go index 24991df0c..141f3c678 100644 --- a/vendor/src/github.com/minio/minio-go/examples/s3/getbucketacl.go +++ b/vendor/src/github.com/minio/minio-go/examples/s3/removebucketpolicy.go @@ -1,7 +1,7 @@ // +build ignore /* - * Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015 Minio, Inc. + * Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015, 2016 Minio, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -38,10 +38,9 @@ func main() { log.Fatalln(err) } - acl, err := s3Client.GetBucketACL("my-bucketname") + err = s3Client.RemoveBucketPolicy("my-bucketname", "my-objectprefix") if err != nil { log.Fatalln(err) } - log.Println(acl) - + log.Println("Success") } diff --git a/vendor/src/github.com/minio/minio-go/examples/s3/removeincompleteupload.go b/vendor/src/github.com/minio/minio-go/examples/s3/removeincompleteupload.go index 8b7533472..0990f662c 100644 --- a/vendor/src/github.com/minio/minio-go/examples/s3/removeincompleteupload.go +++ b/vendor/src/github.com/minio/minio-go/examples/s3/removeincompleteupload.go @@ -38,10 +38,9 @@ func main() { log.Fatalln(err) } - for err := range s3Client.RemoveIncompleteUpload("my-bucketname", "my-objectname") { - if err != nil { - log.Fatalln(err) - } + err = s3Client.RemoveIncompleteUpload("my-bucketname", "my-objectname") + if err != nil { + log.Fatalln(err) } log.Println("Success") } diff --git a/vendor/src/github.com/minio/minio-go/examples/s3/setbucketacl.go b/vendor/src/github.com/minio/minio-go/examples/s3/setbucketpolicy.go similarity index 85% rename from vendor/src/github.com/minio/minio-go/examples/s3/setbucketacl.go rename to vendor/src/github.com/minio/minio-go/examples/s3/setbucketpolicy.go index 59fb10ef7..8f29db13c 100644 --- a/vendor/src/github.com/minio/minio-go/examples/s3/setbucketacl.go +++ b/vendor/src/github.com/minio/minio-go/examples/s3/setbucketpolicy.go @@ -1,7 +1,7 @@ // +build ignore /* - * Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015 Minio, Inc. + * Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015, 2016 Minio, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,8 +25,8 @@ import ( ) func main() { - // Note: YOUR-ACCESSKEYID, YOUR-SECRETACCESSKEY and my-bucketname - // are dummy values, please replace them with original values. + // Note: YOUR-ACCESSKEYID, YOUR-SECRETACCESSKEY and my-bucketname are + // dummy values, please replace them with original values. // Requests are always secure (HTTPS) by default. Set insecure=true to enable insecure (HTTP) access. // This boolean value is the last argument for New(). @@ -38,9 +38,9 @@ func main() { log.Fatalln(err) } - err = s3Client.SetBucketACL("my-bucketname", minio.BucketACL("public-read-write")) + err = s3Client.SetBucketPolicy("my-bucketname", "my-objectprefix", minio.BucketPolicyReadWrite) if err != nil { log.Fatalln(err) } - + log.Println("Success") } diff --git a/vendor/src/github.com/minio/minio-go/hook-reader.go b/vendor/src/github.com/minio/minio-go/hook-reader.go index 043425f23..bc9ece049 100644 --- a/vendor/src/github.com/minio/minio-go/hook-reader.go +++ b/vendor/src/github.com/minio/minio-go/hook-reader.go @@ -1,5 +1,5 @@ /* - * Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015 Minio, Inc. + * Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015, 2016 Minio, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,6 +27,22 @@ type hookReader struct { hook io.Reader } +// Seek implements io.Seeker. Seeks source first, and if necessary +// seeks hook if Seek method is appropriately found. +func (hr *hookReader) Seek(offset int64, whence int) (n int64, err error) { + // Verify for source has embedded Seeker, use it. + sourceSeeker, ok := hr.source.(io.Seeker) + if ok { + return sourceSeeker.Seek(offset, whence) + } + // Verify if hook has embedded Seeker, use it. + hookSeeker, ok := hr.hook.(io.Seeker) + if ok { + return hookSeeker.Seek(offset, whence) + } + return n, nil +} + // Read implements io.Reader. Always reads from the source, the return // value 'n' number of bytes are reported through the hook. Returns // error for all non io.EOF conditions. @@ -44,7 +60,7 @@ func (hr *hookReader) Read(b []byte) (n int, err error) { return n, err } -// newHook returns a io.Reader which implements hookReader that +// newHook returns a io.ReadSeeker which implements hookReader that // reports the data read from the source to the hook. func newHook(source, hook io.Reader) io.Reader { if hook == nil { diff --git a/vendor/src/github.com/minio/minio-go/request-signature-v2.go b/vendor/src/github.com/minio/minio-go/request-signature-v2.go index aa0fc9f91..c14ce2aab 100644 --- a/vendor/src/github.com/minio/minio-go/request-signature-v2.go +++ b/vendor/src/github.com/minio/minio-go/request-signature-v2.go @@ -73,6 +73,11 @@ func preSignV2(req http.Request, accessKeyID, secretAccessKey string, expires in // Get encoded URL path. path := encodeURL2Path(req.URL) + if len(req.URL.Query()) > 0 { + // Keep the usual queries unescaped for string to sign. + query, _ := url.QueryUnescape(queryEncode(req.URL.Query())) + path = path + "?" + query + } // Find epoch expires when the request will expire. epochExpires := d.Unix() + expires @@ -93,12 +98,16 @@ func preSignV2(req http.Request, accessKeyID, secretAccessKey string, expires in query.Set("AWSAccessKeyId", accessKeyID) } - // Fill in Expires and Signature for presigned query. + // Fill in Expires for presigned query. query.Set("Expires", strconv.FormatInt(epochExpires, 10)) - query.Set("Signature", signature) // Encode query and save. - req.URL.RawQuery = query.Encode() + req.URL.RawQuery = queryEncode(query) + + // Save signature finally. + req.URL.RawQuery += "&Signature=" + urlEncodePath(signature) + + // Return. return &req } @@ -115,7 +124,7 @@ func postPresignSignatureV2(policyBase64, secretAccessKey string) string { // Signature = Base64( HMAC-SHA1( YourSecretAccessKeyID, UTF-8-Encoding-Of( StringToSign ) ) ); // // StringToSign = HTTP-Verb + "\n" + -// Content-MD5 + "\n" + +// Content-Md5 + "\n" + // Content-Type + "\n" + // Date + "\n" + // CanonicalizedProtocolHeaders + @@ -163,7 +172,7 @@ func signV2(req http.Request, accessKeyID, secretAccessKey string) *http.Request // From the Amazon docs: // // StringToSign = HTTP-Verb + "\n" + -// Content-MD5 + "\n" + +// Content-Md5 + "\n" + // Content-Type + "\n" + // Date + "\n" + // CanonicalizedProtocolHeaders + @@ -183,7 +192,7 @@ func getStringToSignV2(req http.Request) string { func writeDefaultHeaders(buf *bytes.Buffer, req http.Request) { buf.WriteString(req.Method) buf.WriteByte('\n') - buf.WriteString(req.Header.Get("Content-MD5")) + buf.WriteString(req.Header.Get("Content-Md5")) buf.WriteByte('\n') buf.WriteString(req.Header.Get("Content-Type")) buf.WriteByte('\n') @@ -226,7 +235,8 @@ func writeCanonicalizedHeaders(buf *bytes.Buffer, req http.Request) { } } -// Must be sorted: +// The following list is already sorted and should always be, otherwise we could +// have signature-related issues var resourceList = []string{ "acl", "location", @@ -234,13 +244,13 @@ var resourceList = []string{ "notification", "partNumber", "policy", - "response-content-type", - "response-content-language", - "response-expires", + "requestPayment", "response-cache-control", "response-content-disposition", "response-content-encoding", - "requestPayment", + "response-content-language", + "response-content-type", + "response-expires", "torrent", "uploadId", "uploads", @@ -262,7 +272,6 @@ func writeCanonicalizedResource(buf *bytes.Buffer, req http.Request) { path := encodeURL2Path(requestURL) buf.WriteString(path) - sort.Strings(resourceList) if requestURL.RawQuery != "" { var n int vals, _ := url.ParseQuery(requestURL.RawQuery) @@ -283,7 +292,7 @@ func writeCanonicalizedResource(buf *bytes.Buffer, req http.Request) { // Request parameters if len(vv[0]) > 0 { buf.WriteByte('=') - buf.WriteString(url.QueryEscape(vv[0])) + buf.WriteString(strings.Replace(url.QueryEscape(vv[0]), "+", "%20", -1)) } } } diff --git a/vendor/src/github.com/minio/minio-go/request-signature-v2_test.go b/vendor/src/github.com/minio/minio-go/request-signature-v2_test.go new file mode 100644 index 000000000..6d861fb81 --- /dev/null +++ b/vendor/src/github.com/minio/minio-go/request-signature-v2_test.go @@ -0,0 +1,35 @@ +/* + * Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015, 2016 Minio, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package minio + +import ( + "sort" + "testing" +) + +// Tests for 'func TestResourceListSorting(t *testing.T)'. +func TestResourceListSorting(t *testing.T) { + sortedResourceList := make([]string, len(resourceList)) + copy(sortedResourceList, resourceList) + sort.Strings(sortedResourceList) + for i := 0; i < len(resourceList); i++ { + if resourceList[i] != sortedResourceList[i] { + t.Errorf("Expected resourceList[%d] = \"%s\", resourceList is not correctly sorted.", i, sortedResourceList[i]) + break + } + } +} diff --git a/vendor/src/github.com/minio/minio-go/retry.go b/vendor/src/github.com/minio/minio-go/retry.go new file mode 100644 index 000000000..41b70e474 --- /dev/null +++ b/vendor/src/github.com/minio/minio-go/retry.go @@ -0,0 +1,138 @@ +/* + * Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015, 2016 Minio, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package minio + +import ( + "net" + "net/http" + "net/url" + "strings" + "time" +) + +// MaxRetry is the maximum number of retries before stopping. +var MaxRetry = 5 + +// MaxJitter will randomize over the full exponential backoff time +const MaxJitter = 1.0 + +// NoJitter disables the use of jitter for randomizing the exponential backoff time +const NoJitter = 0.0 + +// newRetryTimer creates a timer with exponentially increasing delays +// until the maximum retry attempts are reached. +func (c Client) newRetryTimer(maxRetry int, unit time.Duration, cap time.Duration, jitter float64, doneCh chan struct{}) <-chan int { + attemptCh := make(chan int) + + // computes the exponential backoff duration according to + // https://www.awsarchitectureblog.com/2015/03/backoff.html + exponentialBackoffWait := func(attempt int) time.Duration { + // normalize jitter to the range [0, 1.0] + if jitter < NoJitter { + jitter = NoJitter + } + if jitter > MaxJitter { + jitter = MaxJitter + } + + //sleep = random_between(0, min(cap, base * 2 ** attempt)) + sleep := unit * time.Duration(1< cap { + sleep = cap + } + if jitter != NoJitter { + sleep -= time.Duration(c.random.Float64() * float64(sleep) * jitter) + } + return sleep + } + + go func() { + defer close(attemptCh) + for i := 0; i < maxRetry; i++ { + select { + // Attempts start from 1. + case attemptCh <- i + 1: + case <-doneCh: + // Stop the routine. + return + } + time.Sleep(exponentialBackoffWait(i)) + } + }() + return attemptCh +} + +// isNetErrorRetryable - is network error retryable. +func isNetErrorRetryable(err error) bool { + switch err.(type) { + case net.Error: + switch err.(type) { + case *net.DNSError, *net.OpError, net.UnknownNetworkError: + return true + case *url.Error: + // For a URL error, where it replies back "connection closed" + // retry again. + if strings.Contains(err.Error(), "Connection closed by foreign host") { + return true + } + default: + if strings.Contains(err.Error(), "net/http: TLS handshake timeout") { + // If error is - tlsHandshakeTimeoutError, retry. + return true + } else if strings.Contains(err.Error(), "i/o timeout") { + // If error is - tcp timeoutError, retry. + return true + } + } + } + return false +} + +// List of AWS S3 error codes which are retryable. +var retryableS3Codes = map[string]struct{}{ + "RequestError": {}, + "RequestTimeout": {}, + "Throttling": {}, + "ThrottlingException": {}, + "RequestLimitExceeded": {}, + "RequestThrottled": {}, + "InternalError": {}, + "ExpiredToken": {}, + "ExpiredTokenException": {}, + // Add more AWS S3 codes here. +} + +// isS3CodeRetryable - is s3 error code retryable. +func isS3CodeRetryable(s3Code string) (ok bool) { + _, ok = retryableS3Codes[s3Code] + return ok +} + +// List of HTTP status codes which are retryable. +var retryableHTTPStatusCodes = map[int]struct{}{ + 429: {}, // http.StatusTooManyRequests is not part of the Go 1.5 library, yet + http.StatusInternalServerError: {}, + http.StatusBadGateway: {}, + http.StatusServiceUnavailable: {}, + // Add more HTTP status codes here. +} + +// isHTTPStatusRetryable - is HTTP error code retryable. +func isHTTPStatusRetryable(httpStatusCode int) (ok bool) { + _, ok = retryableHTTPStatusCodes[httpStatusCode] + return ok +} diff --git a/vendor/src/github.com/minio/minio-go/s3-endpoints.go b/vendor/src/github.com/minio/minio-go/s3-endpoints.go index 8c9ff5e88..a46b5e335 100644 --- a/vendor/src/github.com/minio/minio-go/s3-endpoints.go +++ b/vendor/src/github.com/minio/minio-go/s3-endpoints.go @@ -17,6 +17,7 @@ package minio // awsS3EndpointMap Amazon S3 endpoint map. +// "cn-north-1" adds support for AWS China. var awsS3EndpointMap = map[string]string{ "us-east-1": "s3.amazonaws.com", "us-west-2": "s3-us-west-2.amazonaws.com", @@ -27,6 +28,7 @@ var awsS3EndpointMap = map[string]string{ "ap-northeast-1": "s3-ap-northeast-1.amazonaws.com", "ap-northeast-2": "s3-ap-northeast-2.amazonaws.com", "sa-east-1": "s3-sa-east-1.amazonaws.com", + "cn-north-1": "s3.cn-north-1.amazonaws.com.cn", } // getS3Endpoint get Amazon S3 endpoint based on the bucket location. diff --git a/vendor/src/github.com/minio/minio-go/test-utils_test.go b/vendor/src/github.com/minio/minio-go/test-utils_test.go new file mode 100644 index 000000000..179c28a23 --- /dev/null +++ b/vendor/src/github.com/minio/minio-go/test-utils_test.go @@ -0,0 +1,64 @@ +/* + * Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015 Minio, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package minio + +import ( + "bytes" + "encoding/xml" + "io/ioutil" + "net/http" +) + +// Contains common used utilities for tests. + +// APIError Used for mocking error response from server. +type APIError struct { + Code string + Description string + HTTPStatusCode int +} + +// Mocks XML error response from the server. +func generateErrorResponse(resp *http.Response, APIErr APIError, bucketName string) *http.Response { + // generate error response. + errorResponse := getAPIErrorResponse(APIErr, bucketName) + encodedErrorResponse := encodeResponse(errorResponse) + // write Header. + resp.StatusCode = APIErr.HTTPStatusCode + resp.Body = ioutil.NopCloser(bytes.NewBuffer(encodedErrorResponse)) + + return resp +} + +// getErrorResponse gets in standard error and resource value and +// provides a encodable populated response values. +func getAPIErrorResponse(err APIError, bucketName string) ErrorResponse { + var errResp = ErrorResponse{} + errResp.Code = err.Code + errResp.Message = err.Description + errResp.BucketName = bucketName + return errResp +} + +// Encodes the response headers into XML format. +func encodeResponse(response interface{}) []byte { + var bytesBuffer bytes.Buffer + bytesBuffer.WriteString(xml.Header) + encode := xml.NewEncoder(&bytesBuffer) + encode.Encode(response) + return bytesBuffer.Bytes() +} diff --git a/vendor/src/github.com/minio/minio-go/utils.go b/vendor/src/github.com/minio/minio-go/utils.go index 63966c30c..104665849 100644 --- a/vendor/src/github.com/minio/minio-go/utils.go +++ b/vendor/src/github.com/minio/minio-go/utils.go @@ -17,7 +17,9 @@ package minio import ( + "bytes" "crypto/hmac" + "crypto/md5" "crypto/sha256" "encoding/hex" "encoding/xml" @@ -27,6 +29,7 @@ import ( "net/http" "net/url" "regexp" + "sort" "strings" "time" "unicode/utf8" @@ -45,6 +48,13 @@ func sum256(data []byte) []byte { return hash.Sum(nil) } +// sumMD5 calculate sumMD5 sum for an input byte array. +func sumMD5(data []byte) []byte { + hash := md5.New() + hash.Write(data) + return hash.Sum(nil) +} + // sumHMAC calculate hmac between two input byte array. func sumHMAC(key []byte, data []byte) []byte { hash := hmac.New(sha256.New, key) @@ -163,6 +173,23 @@ func isAmazonEndpoint(endpointURL *url.URL) bool { if endpointURL.Host == "s3.amazonaws.com" { return true } + if isAmazonChinaEndpoint(endpointURL) { + return true + } + return false +} + +// Match if it is exactly Amazon S3 China endpoint. +// Customers who wish to use the new Beijing Region are required to sign up for a separate set of account credentials unique to the China (Beijing) Region. +// Customers with existing AWS credentials will not be able to access resources in the new Region, and vice versa." +// For more info https://aws.amazon.com/about-aws/whats-new/2013/12/18/announcing-the-aws-china-beijing-region/ +func isAmazonChinaEndpoint(endpointURL *url.URL) bool { + if endpointURL == nil { + return false + } + if endpointURL.Host == "s3.cn-north-1.amazonaws.com.cn" { + return true + } return false } @@ -183,7 +210,7 @@ func isValidEndpointURL(endpointURL *url.URL) error { return ErrInvalidArgument("Endpoint url cannot be empty.") } if endpointURL.Path != "/" && endpointURL.Path != "" { - return ErrInvalidArgument("Endpoing url cannot have fully qualified paths.") + return ErrInvalidArgument("Endpoint url cannot have fully qualified paths.") } if strings.Contains(endpointURL.Host, ".amazonaws.com") { if !isAmazonEndpoint(endpointURL) { @@ -229,7 +256,7 @@ func isValidBucketName(bucketName string) error { if bucketName[0] == '.' || bucketName[len(bucketName)-1] == '.' { return ErrInvalidBucketName("Bucket name cannot start or end with a '.' dot.") } - if match, _ := regexp.MatchString("\\.\\.", bucketName); match == true { + if match, _ := regexp.MatchString("\\.\\.", bucketName); match { return ErrInvalidBucketName("Bucket name cannot have successive periods.") } if !validBucketName.MatchString(bucketName) { @@ -264,6 +291,31 @@ func isValidObjectPrefix(objectPrefix string) error { return nil } +// queryEncode - encodes query values in their URL encoded form. +func queryEncode(v url.Values) string { + if v == nil { + return "" + } + var buf bytes.Buffer + keys := make([]string, 0, len(v)) + for k := range v { + keys = append(keys, k) + } + sort.Strings(keys) + for _, k := range keys { + vs := v[k] + prefix := urlEncodePath(k) + "=" + for _, v := range vs { + if buf.Len() > 0 { + buf.WriteByte('&') + } + buf.WriteString(prefix) + buf.WriteString(urlEncodePath(v)) + } + } + return buf.String() +} + // urlEncodePath encode the strings from UTF-8 byte representations to HTML hex escape sequences // // This is necessary since regular url.Parse() and url.Encode() functions do not support UTF-8 diff --git a/vendor/src/github.com/minio/minio-go/utils_test.go b/vendor/src/github.com/minio/minio-go/utils_test.go new file mode 100644 index 000000000..045ec85da --- /dev/null +++ b/vendor/src/github.com/minio/minio-go/utils_test.go @@ -0,0 +1,430 @@ +/* + * Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015 Minio, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package minio + +import ( + "fmt" + "net/url" + "testing" + "time" +) + +// Tests for 'getEndpointURL(endpoint string, inSecure bool)'. +func TestGetEndpointURL(t *testing.T) { + testCases := []struct { + // Inputs. + endPoint string + inSecure bool + + // Expected result. + result string + err error + // Flag indicating whether the test is expected to pass or not. + shouldPass bool + }{ + {"s3.amazonaws.com", false, "https://s3.amazonaws.com", nil, true}, + {"s3.cn-north-1.amazonaws.com.cn", false, "https://s3.cn-north-1.amazonaws.com.cn", nil, true}, + {"s3.amazonaws.com", true, "http://s3.amazonaws.com", nil, true}, + {"s3.cn-north-1.amazonaws.com.cn", true, "http://s3.cn-north-1.amazonaws.com.cn", nil, true}, + {"192.168.1.1:9000", true, "http://192.168.1.1:9000", nil, true}, + {"192.168.1.1:9000", false, "https://192.168.1.1:9000", nil, true}, + {"192.168.1.1::9000", false, "", fmt.Errorf("too many colons in address %s", "192.168.1.1::9000"), false}, + {"13333.123123.-", false, "", fmt.Errorf("Endpoint: %s does not follow ip address or domain name standards.", "13333.123123.-"), false}, + {"13333.123123.-", false, "", fmt.Errorf("Endpoint: %s does not follow ip address or domain name standards.", "13333.123123.-"), false}, + {"s3.amazonaws.com:443", false, "", fmt.Errorf("Amazon S3 endpoint should be 's3.amazonaws.com'."), false}, + {"storage.googleapis.com:4000", false, "", fmt.Errorf("Google Cloud Storage endpoint should be 'storage.googleapis.com'."), false}, + {"s3.aamzza.-", false, "", fmt.Errorf("Endpoint: %s does not follow ip address or domain name standards.", "s3.aamzza.-"), false}, + {"", false, "", fmt.Errorf("Endpoint: does not follow ip address or domain name standards."), false}, + } + + for i, testCase := range testCases { + result, err := getEndpointURL(testCase.endPoint, testCase.inSecure) + if err != nil && testCase.shouldPass { + t.Errorf("Test %d: Expected to pass, but failed with: %s", i+1, err.Error()) + } + if err == nil && !testCase.shouldPass { + t.Errorf("Test %d: Expected to fail with \"%s\", but passed instead", i+1, testCase.err.Error()) + } + // Failed as expected, but does it fail for the expected reason. + if err != nil && !testCase.shouldPass { + if err.Error() != testCase.err.Error() { + t.Errorf("Test %d: Expected to fail with error \"%s\", but instead failed with error \"%s\" instead", i+1, testCase.err.Error(), err.Error()) + } + } + + // Test passes as expected, but the output values are verified for correctness here. + if err == nil && testCase.shouldPass { + if testCase.result != result.String() { + t.Errorf("Test %d: Expected the result Url to be \"%s\", but found \"%s\" instead", i+1, testCase.result, result.String()) + } + } + } +} + +// Tests for 'isValidDomain(host string) bool'. +func TestIsValidDomain(t *testing.T) { + testCases := []struct { + // Input. + host string + // Expected result. + result bool + }{ + {"s3.amazonaws.com", true}, + {"s3.cn-north-1.amazonaws.com.cn", true}, + {"s3.amazonaws.com_", false}, + {"%$$$", false}, + {"s3.amz.test.com", true}, + {"s3.%%", false}, + {"localhost", true}, + {"-localhost", false}, + {"", false}, + {"\n \t", false}, + {" ", false}, + } + + for i, testCase := range testCases { + result := isValidDomain(testCase.host) + if testCase.result != result { + t.Errorf("Test %d: Expected isValidDomain test to be '%v', but found '%v' instead", i+1, testCase.result, result) + } + } +} + +// Tests validate end point validator. +func TestIsValidEndpointURL(t *testing.T) { + testCases := []struct { + url string + err error + // Flag indicating whether the test is expected to pass or not. + shouldPass bool + }{ + {"", nil, true}, + {"/", nil, true}, + {"https://s3.amazonaws.com", nil, true}, + {"https://s3.cn-north-1.amazonaws.com.cn", nil, true}, + {"https://s3.amazonaws.com/", nil, true}, + {"https://storage.googleapis.com/", nil, true}, + {"192.168.1.1", fmt.Errorf("Endpoint url cannot have fully qualified paths."), false}, + {"https://amazon.googleapis.com/", fmt.Errorf("Google Cloud Storage endpoint should be 'storage.googleapis.com'."), false}, + {"https://storage.googleapis.com/bucket/", fmt.Errorf("Endpoint url cannot have fully qualified paths."), false}, + {"https://z3.amazonaws.com", fmt.Errorf("Amazon S3 endpoint should be 's3.amazonaws.com'."), false}, + {"https://s3.amazonaws.com/bucket/object", fmt.Errorf("Endpoint url cannot have fully qualified paths."), false}, + } + + for i, testCase := range testCases { + endPoint, e := url.Parse(testCase.url) + if e != nil { + t.Fatalf("Test %d: Fatal err \"%s\"", i+1, e.Error()) + } + err := isValidEndpointURL(endPoint) + if err != nil && testCase.shouldPass { + t.Errorf("Test %d: Expected to pass, but failed with: %s", i+1, err.Error()) + } + if err == nil && !testCase.shouldPass { + t.Errorf("Test %d: Expected to fail with \"%s\", but passed instead", i+1, testCase.err.Error()) + } + // Failed as expected, but does it fail for the expected reason. + if err != nil && !testCase.shouldPass { + if err.Error() != testCase.err.Error() { + t.Errorf("Test %d: Expected to fail with error \"%s\", but instead failed with error \"%s\" instead", i+1, testCase.err.Error(), err.Error()) + } + } + + } +} + +// Tests validate IP address validator. +func TestIsValidIP(t *testing.T) { + testCases := []struct { + // Input. + ip string + // Expected result. + result bool + }{ + {"192.168.1.1", true}, + {"192.168.1", false}, + {"192.168.1.1.1", false}, + {"-192.168.1.1", false}, + {"260.192.1.1", false}, + } + + for i, testCase := range testCases { + result := isValidIP(testCase.ip) + if testCase.result != result { + t.Errorf("Test %d: Expected isValidIP to be '%v' for input \"%s\", but found it to be '%v' instead", i+1, testCase.result, testCase.ip, result) + } + } + +} + +// Tests validate virtual host validator. +func TestIsVirtualHostSupported(t *testing.T) { + testCases := []struct { + url string + bucket string + // Expeceted result. + result bool + }{ + {"https://s3.amazonaws.com", "my-bucket", true}, + {"https://s3.cn-north-1.amazonaws.com.cn", "my-bucket", true}, + {"https://s3.amazonaws.com", "my-bucket.", false}, + {"https://amazons3.amazonaws.com", "my-bucket.", false}, + {"https://storage.googleapis.com/", "my-bucket", true}, + {"https://mystorage.googleapis.com/", "my-bucket", false}, + } + + for i, testCase := range testCases { + endPoint, e := url.Parse(testCase.url) + if e != nil { + t.Fatalf("Test %d: Fatal err \"%s\"", i+1, e.Error()) + } + result := isVirtualHostSupported(endPoint, testCase.bucket) + if testCase.result != result { + t.Errorf("Test %d: Expected isVirtualHostSupported to be '%v' for input url \"%s\" and bucket \"%s\", but found it to be '%v' instead", i+1, testCase.result, testCase.url, testCase.bucket, result) + } + } +} + +// Tests validate Amazon endpoint validator. +func TestIsAmazonEndpoint(t *testing.T) { + testCases := []struct { + url string + // Expected result. + result bool + }{ + {"https://192.168.1.1", false}, + {"192.168.1.1", false}, + {"http://storage.googleapis.com", false}, + {"https://storage.googleapis.com", false}, + {"storage.googleapis.com", false}, + {"s3.amazonaws.com", false}, + {"https://amazons3.amazonaws.com", false}, + {"-192.168.1.1", false}, + {"260.192.1.1", false}, + // valid inputs. + {"https://s3.amazonaws.com", true}, + {"https://s3.cn-north-1.amazonaws.com.cn", true}, + } + + for i, testCase := range testCases { + endPoint, e := url.Parse(testCase.url) + if e != nil { + t.Fatalf("Test %d: Fatal err \"%s\"", i+1, e.Error()) + } + result := isAmazonEndpoint(endPoint) + if testCase.result != result { + t.Errorf("Test %d: Expected isAmazonEndpoint to be '%v' for input \"%s\", but found it to be '%v' instead", i+1, testCase.result, testCase.url, result) + } + } + +} + +// Tests validate Amazon S3 China endpoint validator. +func TestIsAmazonChinaEndpoint(t *testing.T) { + testCases := []struct { + url string + // Expected result. + result bool + }{ + {"https://192.168.1.1", false}, + {"192.168.1.1", false}, + {"http://storage.googleapis.com", false}, + {"https://storage.googleapis.com", false}, + {"storage.googleapis.com", false}, + {"s3.amazonaws.com", false}, + {"https://amazons3.amazonaws.com", false}, + {"-192.168.1.1", false}, + {"260.192.1.1", false}, + // s3.amazonaws.com is not a valid Amazon S3 China end point. + {"https://s3.amazonaws.com", false}, + // valid input. + {"https://s3.cn-north-1.amazonaws.com.cn", true}, + } + + for i, testCase := range testCases { + endPoint, e := url.Parse(testCase.url) + if e != nil { + t.Fatalf("Test %d: Fatal err \"%s\"", i+1, e.Error()) + } + result := isAmazonChinaEndpoint(endPoint) + if testCase.result != result { + t.Errorf("Test %d: Expected isAmazonEndpoint to be '%v' for input \"%s\", but found it to be '%v' instead", i+1, testCase.result, testCase.url, result) + } + } + +} + +// Tests validate Google Cloud end point validator. +func TestIsGoogleEndpoint(t *testing.T) { + testCases := []struct { + url string + // Expected result. + result bool + }{ + {"192.168.1.1", false}, + {"https://192.168.1.1", false}, + {"s3.amazonaws.com", false}, + {"http://s3.amazonaws.com", false}, + {"https://s3.amazonaws.com", false}, + {"https://s3.cn-north-1.amazonaws.com.cn", false}, + {"-192.168.1.1", false}, + {"260.192.1.1", false}, + // valid inputs. + {"http://storage.googleapis.com", true}, + {"https://storage.googleapis.com", true}, + } + + for i, testCase := range testCases { + endPoint, e := url.Parse(testCase.url) + if e != nil { + t.Fatalf("Test %d: Fatal err \"%s\"", i+1, e.Error()) + } + result := isGoogleEndpoint(endPoint) + if testCase.result != result { + t.Errorf("Test %d: Expected isGoogleEndpoint to be '%v' for input \"%s\", but found it to be '%v' instead", i+1, testCase.result, testCase.url, result) + } + } + +} + +// Tests validate the expiry time validator. +func TestIsValidExpiry(t *testing.T) { + testCases := []struct { + // Input. + duration time.Duration + // Expected result. + err error + // Flag to indicate whether the test should pass. + shouldPass bool + }{ + {100 * time.Millisecond, fmt.Errorf("Expires cannot be lesser than 1 second."), false}, + {604801 * time.Second, fmt.Errorf("Expires cannot be greater than 7 days."), false}, + {0 * time.Second, fmt.Errorf("Expires cannot be lesser than 1 second."), false}, + {1 * time.Second, nil, true}, + {10000 * time.Second, nil, true}, + {999 * time.Second, nil, true}, + } + + for i, testCase := range testCases { + err := isValidExpiry(testCase.duration) + if err != nil && testCase.shouldPass { + t.Errorf("Test %d: Expected to pass, but failed with: %s", i+1, err.Error()) + } + if err == nil && !testCase.shouldPass { + t.Errorf("Test %d: Expected to fail with \"%s\", but passed instead", i+1, testCase.err.Error()) + } + // Failed as expected, but does it fail for the expected reason. + if err != nil && !testCase.shouldPass { + if err.Error() != testCase.err.Error() { + t.Errorf("Test %d: Expected to fail with error \"%s\", but instead failed with error \"%s\" instead", i+1, testCase.err.Error(), err.Error()) + } + } + + } +} + +// Tests validate the bucket name validator. +func TestIsValidBucketName(t *testing.T) { + testCases := []struct { + // Input. + bucketName string + // Expected result. + err error + // Flag to indicate whether test should Pass. + shouldPass bool + }{ + {".mybucket", ErrInvalidBucketName("Bucket name cannot start or end with a '.' dot."), false}, + {"mybucket.", ErrInvalidBucketName("Bucket name cannot start or end with a '.' dot."), false}, + {"mybucket-", ErrInvalidBucketName("Bucket name contains invalid characters."), false}, + {"my", ErrInvalidBucketName("Bucket name cannot be smaller than 3 characters."), false}, + {"", ErrInvalidBucketName("Bucket name cannot be empty."), false}, + {"my..bucket", ErrInvalidBucketName("Bucket name cannot have successive periods."), false}, + {"my.bucket.com", nil, true}, + {"my-bucket", nil, true}, + {"123my-bucket", nil, true}, + } + + for i, testCase := range testCases { + err := isValidBucketName(testCase.bucketName) + if err != nil && testCase.shouldPass { + t.Errorf("Test %d: Expected to pass, but failed with: %s", i+1, err.Error()) + } + if err == nil && !testCase.shouldPass { + t.Errorf("Test %d: Expected to fail with \"%s\", but passed instead", i+1, testCase.err.Error()) + } + // Failed as expected, but does it fail for the expected reason. + if err != nil && !testCase.shouldPass { + if err.Error() != testCase.err.Error() { + t.Errorf("Test %d: Expected to fail with error \"%s\", but instead failed with error \"%s\" instead", i+1, testCase.err.Error(), err.Error()) + } + } + + } + +} + +// Tests validate the query encoder. +func TestQueryEncode(t *testing.T) { + testCases := []struct { + queryKey string + valueToEncode []string + // Expected result. + result string + }{ + {"prefix", []string{"test@123", "test@456"}, "prefix=test%40123&prefix=test%40456"}, + {"@prefix", []string{"test@123"}, "%40prefix=test%40123"}, + {"prefix", []string{"test#123"}, "prefix=test%23123"}, + {"prefix#", []string{"test#123"}, "prefix%23=test%23123"}, + {"prefix", []string{"test123"}, "prefix=test123"}, + {"prefix", []string{"test本語123", "test123"}, "prefix=test%E6%9C%AC%E8%AA%9E123&prefix=test123"}, + } + + for i, testCase := range testCases { + urlValues := make(url.Values) + for _, valueToEncode := range testCase.valueToEncode { + urlValues.Add(testCase.queryKey, valueToEncode) + } + result := queryEncode(urlValues) + if testCase.result != result { + t.Errorf("Test %d: Expected queryEncode result to be \"%s\", but found it to be \"%s\" instead", i+1, testCase.result, result) + } + } +} + +// Tests validate the URL path encoder. +func TestUrlEncodePath(t *testing.T) { + testCases := []struct { + // Input. + inputStr string + // Expected result. + result string + }{ + {"thisisthe%url", "thisisthe%25url"}, + {"本語", "%E6%9C%AC%E8%AA%9E"}, + {"本語.1", "%E6%9C%AC%E8%AA%9E.1"}, + {">123", "%3E123"}, + {"myurl#link", "myurl%23link"}, + {"space in url", "space%20in%20url"}, + {"url+path", "url%2Bpath"}, + } + + for i, testCase := range testCases { + result := urlEncodePath(testCase.inputStr) + if testCase.result != result { + t.Errorf("Test %d: Expected queryEncode result to be \"%s\", but found it to be \"%s\" instead", i+1, testCase.result, result) + } + } +}