Switch s3 library to allow for s3 compatible backends. Fixes #315

This commit is contained in:
Chris Howey authored and Alexander Neumann committed 2016-01-17 18:46:08 +01:00
1 parent c969de7fad
commit 6d1552af51
68 files changed
+5994 -4655

No files matched your search

@@ -0,0 +1,2 @@
*~
*.test
+9
View File
@@ -0,0 +1,9 @@
language: go
go:
- 1.5.1
script:
- go vet ./...
- go test -race -v ./...
notifications:
slack:
secure: HrOX2k6F/sEl6Rr4m5vHOdJCIwV42be0kz1Jy/WSMvrl/fQ8YkldKviLeWh4aWt1kclsYhNQ4FqGML+RIZYsdOqej4fAw9Vi5pZkI1MzPJq0UjrtMqkqzvD90eDGQYCKwaXjEIN8cohwJeb6X0B0HKAd9sqJW5GH5SwnhH5WWP8=
+21
View File
@@ -0,0 +1,21 @@
### Developer Guidelines
``minio-go`` welcomes your contribution. To make the process as seamless as possible, we ask for the following:
* Go ahead and fork the project and make your changes. We encourage pull requests to discuss code changes.
- Fork it
- Create your feature branch (git checkout -b my-new-feature)
- Commit your changes (git commit -am 'Add some feature')
- Push to the branch (git push origin my-new-feature)
- Create new Pull Request
* When you're ready to create a pull request, be sure to:
- Have test cases for the new code. If you have questions about how to do it, please ask in your pull request.
- 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.
* Read [Effective Go](https://github.com/golang/go/wiki/CodeReviewComments) article from Golang project
- `minio-go` project is strictly conformant with Golang style
- if you happen to observe offending code, please feel free to send a pull request
+202
View File
@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
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.
+71
View File
@@ -0,0 +1,71 @@
# Minio Go Library for Amazon S3 Compatible Cloud Storage [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/minio/minio?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
## Install
```sh
$ go get github.com/minio/minio-go
```
## Example
```go
package main
import (
"log"
"github.com/minio/minio-go"
)
func main() {
config := minio.Config{
AccessKeyID: "YOUR-ACCESS-KEY-HERE",
SecretAccessKey: "YOUR-PASSWORD-HERE",
Endpoint: "https://s3.amazonaws.com",
}
s3Client, err := minio.New(config)
if err != nil {
log.Fatalln(err)
}
for bucket := range s3Client.ListBuckets() {
if bucket.Err != nil {
log.Fatalln(bucket.Err)
}
log.Println(bucket.Stat)
}
}
```
## Documentation
### Bucket Level
* [MakeBucket(bucket, acl) error](examples/s3/makebucket.go)
* [BucketExists(bucket) error](examples/s3/bucketexists.go)
* [RemoveBucket(bucket) error](examples/s3/removebucket.go)
* [GetBucketACL(bucket) (BucketACL, error)](examples/s3/getbucketacl.go)
* [SetBucketACL(bucket, BucketACL) error)](examples/s3/setbucketacl.go)
* [ListBuckets() <-chan BucketStat](examples/s3/listbuckets.go)
* [ListObjects(bucket, prefix, recursive) <-chan ObjectStat](examples/s3/listobjects.go)
* [ListIncompleteUploads(bucket, prefix, recursive) <-chan ObjectMultipartStat](examples/s3/listincompleteuploads.go)
### Object Level
* [PutObject(bucket, object, size, io.Reader) error](examples/s3/putobject.go)
* [GetObject(bucket, object) (io.Reader, ObjectStat, error)](examples/s3/getobject.go)
* [GetPartialObject(bucket, object, offset, length) (io.Reader, ObjectStat, error)](examples/s3/getpartialobject.go)
* [StatObject(bucket, object) (ObjectStat, error)](examples/s3/statobject.go)
* [RemoveObject(bucket, object) error](examples/s3/removeobject.go)
* [RemoveIncompleteUpload(bucket, object) <-chan error](examples/s3/removeincompleteupload.go)
### Presigned Bucket/Object Level
* [PresignedGetObject(bucket, object, time.Duration) (string, error)](examples/s3/presignedgetobject.go)
* [PresignedPutObject(bucket, object, time.Duration) (string, error)](examples/s3/presignedputobject.go)
* [PresignedPostPolicy(NewPostPolicy()) (map[string]string, error)](examples/s3/presignedpostpolicy.go)
### API Reference
[![GoDoc](http://img.shields.io/badge/go-documentation-blue.svg?style=flat-square)](http://godoc.org/github.com/minio/minio-go)
## Contribute
[Contributors Guide](./CONTRIBUTING.md)
[![Build Status](https://travis-ci.org/minio/minio-go.svg)](https://travis-ci.org/minio/minio-go) [![Build status](https://ci.appveyor.com/api/projects/status/1ep7n2resn6fk1w6?svg=true)](https://ci.appveyor.com/project/harshavardhana/minio-go)
+885
View File
@@ -0,0 +1,885 @@
/*
* 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/base64"
"encoding/json"
"encoding/xml"
"fmt"
"io"
"net/http"
"strconv"
"strings"
"time"
)
const (
separator = "/"
)
// apiCore container to hold unexported internal functions
type apiCore struct {
config *Config
}
// closeResp close non nil response with any response Body
func closeResp(resp *http.Response) {
if resp != nil && resp.Body != nil {
resp.Body.Close()
}
}
// putBucketRequest wrapper creates a new putBucket request
func (a apiCore) putBucketRequest(bucket, acl, location string) (*request, error) {
var r *request
var err error
op := &operation{
HTTPServer: a.config.Endpoint,
HTTPMethod: "PUT",
HTTPPath: separator + bucket,
}
var createBucketConfigBuffer *bytes.Reader
// If location is set use it and create proper bucket configuration
switch {
case location != "":
createBucketConfig := new(createBucketConfiguration)
createBucketConfig.Location = location
var createBucketConfigBytes []byte
switch {
case a.config.AcceptType == "application/xml":
createBucketConfigBytes, err = xml.Marshal(createBucketConfig)
case a.config.AcceptType == "application/json":
createBucketConfigBytes, err = json.Marshal(createBucketConfig)
default:
createBucketConfigBytes, err = xml.Marshal(createBucketConfig)
}
if err != nil {
return nil, err
}
createBucketConfigBuffer = bytes.NewReader(createBucketConfigBytes)
}
switch {
case createBucketConfigBuffer == nil:
r, err = newRequest(op, a.config, nil)
if err != nil {
return nil, err
}
default:
r, err = newRequest(op, a.config, createBucketConfigBuffer)
if err != nil {
return nil, err
}
r.req.ContentLength = int64(createBucketConfigBuffer.Len())
}
// by default bucket is private
switch {
case acl != "":
r.Set("x-amz-acl", acl)
default:
r.Set("x-amz-acl", "private")
}
return r, nil
}
/// Bucket Write Operations
// putBucket create a new bucket
//
// Requires valid AWS Access Key ID to authenticate requests
// Anonymous requests are never allowed to create buckets
//
// optional arguments are acl and location - by default all buckets are created
// with ``private`` acl and location set to US Standard if one wishes to set
// different ACLs and Location one can set them properly.
//
// ACL valid values
// ------------------
// private - owner gets full access [DEFAULT]
// 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
// ------------------
//
// Location valid values
// ------------------
// [ us-west-1 | us-west-2 | eu-west-1 | eu-central-1 | ap-southeast-1 | ap-northeast-1 | ap-southeast-2 | sa-east-1 ]
//
// Default - US standard
func (a apiCore) putBucket(bucket, acl, location string) error {
req, err := a.putBucketRequest(bucket, acl, location)
if err != nil {
return err
}
resp, err := req.Do()
defer closeResp(resp)
if err != nil {
return err
}
if resp != nil {
if resp.StatusCode != http.StatusOK {
return BodyToErrorResponse(resp.Body, a.config.AcceptType)
}
}
return nil
}
// putBucketRequestACL wrapper creates a new putBucketACL request
func (a apiCore) putBucketACLRequest(bucket, acl string) (*request, error) {
op := &operation{
HTTPServer: a.config.Endpoint,
HTTPMethod: "PUT",
HTTPPath: separator + bucket + "?acl",
}
req, err := newRequest(op, a.config, nil)
if err != nil {
return nil, err
}
req.Set("x-amz-acl", acl)
return req, nil
}
// putBucketACL set the permissions on an existing bucket using Canned ACL's
func (a apiCore) putBucketACL(bucket, acl string) error {
req, err := a.putBucketACLRequest(bucket, acl)
if err != nil {
return err
}
resp, err := req.Do()
defer closeResp(resp)
if err != nil {
return err
}
if resp != nil {
if resp.StatusCode != http.StatusOK {
return BodyToErrorResponse(resp.Body, a.config.AcceptType)
}
}
return nil
}
// getBucketACLRequest wrapper creates a new getBucketACL request
func (a apiCore) getBucketACLRequest(bucket string) (*request, error) {
op := &operation{
HTTPServer: a.config.Endpoint,
HTTPMethod: "GET",
HTTPPath: separator + bucket + "?acl",
}
req, err := newRequest(op, a.config, nil)
if err != nil {
return nil, err
}
return req, nil
}
// getBucketACL get the acl information on an existing bucket
func (a apiCore) getBucketACL(bucket string) (accessControlPolicy, error) {
req, err := a.getBucketACLRequest(bucket)
if err != nil {
return accessControlPolicy{}, err
}
resp, err := req.Do()
defer closeResp(resp)
if err != nil {
return accessControlPolicy{}, err
}
if resp != nil {
if resp.StatusCode != http.StatusOK {
return accessControlPolicy{}, BodyToErrorResponse(resp.Body, a.config.AcceptType)
}
}
policy := accessControlPolicy{}
err = acceptTypeDecoder(resp.Body, a.config.AcceptType, &policy)
if err != nil {
return accessControlPolicy{}, err
}
if policy.AccessControlList.Grant == nil {
errorResponse := ErrorResponse{
Code: "InternalError",
Message: "Access control Grant list is empty, please report this at https://github.com/minio/minio-go/issues",
Resource: separator + bucket,
RequestID: resp.Header.Get("x-amz-request-id"),
HostID: resp.Header.Get("x-amz-id-2"),
}
return accessControlPolicy{}, errorResponse
}
return policy, nil
}
// getBucketLocationRequest wrapper creates a new getBucketLocation request
func (a apiCore) getBucketLocationRequest(bucket string) (*request, error) {
op := &operation{
HTTPServer: a.config.Endpoint,
HTTPMethod: "GET",
HTTPPath: separator + bucket + "?location",
}
req, err := newRequest(op, a.config, nil)
if err != nil {
return nil, err
}
return req, nil
}
// getBucketLocation uses location subresource to return a bucket's region
func (a apiCore) getBucketLocation(bucket string) (string, error) {
req, err := a.getBucketLocationRequest(bucket)
if err != nil {
return "", err
}
resp, err := req.Do()
defer closeResp(resp)
if err != nil {
return "", err
}
if resp != nil {
if resp.StatusCode != http.StatusOK {
return "", BodyToErrorResponse(resp.Body, a.config.AcceptType)
}
}
var locationConstraint string
err = acceptTypeDecoder(resp.Body, a.config.AcceptType, &locationConstraint)
if err != nil {
return "", err
}
return locationConstraint, nil
}
// listObjectsRequest wrapper creates a new listObjects request
func (a apiCore) listObjectsRequest(bucket, marker, prefix, delimiter string, maxkeys int) (*request, error) {
// resourceQuery - get resources properly escaped and lined up before using them in http request
resourceQuery := func() (*string, error) {
switch {
case marker != "":
marker = fmt.Sprintf("&marker=%s", getURLEncodedPath(marker))
fallthrough
case prefix != "":
prefix = fmt.Sprintf("&prefix=%s", getURLEncodedPath(prefix))
fallthrough
case delimiter != "":
delimiter = fmt.Sprintf("&delimiter=%s", delimiter)
}
query := fmt.Sprintf("?max-keys=%d", maxkeys) + marker + prefix + delimiter
return &query, nil
}
query, err := resourceQuery()
if err != nil {
return nil, err
}
op := &operation{
HTTPServer: a.config.Endpoint,
HTTPMethod: "GET",
HTTPPath: separator + bucket + *query,
}
r, err := newRequest(op, a.config, nil)
if err != nil {
return nil, err
}
return r, nil
}
/// Bucket Read Operations
// listObjects - (List Objects) - List some or all (up to 1000) of the objects in a bucket.
//
// You can use the request parameters as selection criteria to return a subset of the objects in a bucket.
// request paramters :-
// ---------
// ?marker - Specifies the key to start with when listing objects in a bucket.
// ?delimiter - A delimiter is a character you use to group keys.
// ?prefix - Limits the response to keys that begin with the specified prefix.
// ?max-keys - Sets the maximum number of keys returned in the response body.
func (a apiCore) listObjects(bucket, marker, prefix, delimiter string, maxkeys int) (listBucketResult, error) {
if err := invalidBucketError(bucket); err != nil {
return listBucketResult{}, err
}
req, err := a.listObjectsRequest(bucket, marker, prefix, delimiter, maxkeys)
if err != nil {
return listBucketResult{}, err
}
resp, err := req.Do()
defer closeResp(resp)
if err != nil {
return listBucketResult{}, err
}
if resp != nil {
if resp.StatusCode != http.StatusOK {
return listBucketResult{}, BodyToErrorResponse(resp.Body, a.config.AcceptType)
}
}
listBucketResult := listBucketResult{}
err = acceptTypeDecoder(resp.Body, a.config.AcceptType, &listBucketResult)
if err != nil {
return listBucketResult, err
}
// close body while returning, along with any error
return listBucketResult, nil
}
// headBucketRequest wrapper creates a new headBucket request
func (a apiCore) headBucketRequest(bucket string) (*request, error) {
op := &operation{
HTTPServer: a.config.Endpoint,
HTTPMethod: "HEAD",
HTTPPath: separator + bucket,
}
return newRequest(op, a.config, nil)
}
// headBucket useful to determine if a bucket exists and you have permission to access it.
func (a apiCore) headBucket(bucket string) error {
if err := invalidBucketError(bucket); err != nil {
return err
}
req, err := a.headBucketRequest(bucket)
if err != nil {
return err
}
resp, err := req.Do()
defer closeResp(resp)
if err != nil {
return err
}
if resp != nil {
if resp.StatusCode != http.StatusOK {
// Head has no response body, handle it
var errorResponse ErrorResponse
switch resp.StatusCode {
case http.StatusNotFound:
errorResponse = ErrorResponse{
Code: "NoSuchBucket",
Message: "The specified bucket does not exist.",
Resource: separator + bucket,
RequestID: resp.Header.Get("x-amz-request-id"),
HostID: resp.Header.Get("x-amz-id-2"),
}
case http.StatusForbidden:
errorResponse = ErrorResponse{
Code: "AccessDenied",
Message: "Access Denied",
Resource: separator + bucket,
RequestID: resp.Header.Get("x-amz-request-id"),
HostID: resp.Header.Get("x-amz-id-2"),
}
default:
errorResponse = ErrorResponse{
Code: resp.Status,
Message: resp.Status,
Resource: separator + bucket,
RequestID: resp.Header.Get("x-amz-request-id"),
HostID: resp.Header.Get("x-amz-id-2"),
}
}
return errorResponse
}
}
return nil
}
// deleteBucketRequest wrapper creates a new deleteBucket request
func (a apiCore) deleteBucketRequest(bucket string) (*request, error) {
op := &operation{
HTTPServer: a.config.Endpoint,
HTTPMethod: "DELETE",
HTTPPath: separator + bucket,
}
return newRequest(op, a.config, nil)
}
// deleteBucket deletes the bucket named in the URI
//
// NOTE: -
// All objects (including all object versions and delete markers)
// in the bucket must be deleted before successfully attempting this request
func (a apiCore) deleteBucket(bucket string) error {
if err := invalidBucketError(bucket); err != nil {
return err
}
req, err := a.deleteBucketRequest(bucket)
if err != nil {
return err
}
resp, err := req.Do()
defer closeResp(resp)
if err != nil {
return err
}
if resp != nil {
if resp.StatusCode != http.StatusNoContent {
var errorResponse ErrorResponse
switch resp.StatusCode {
case http.StatusNotFound:
errorResponse = ErrorResponse{
Code: "NoSuchBucket",
Message: "The specified bucket does not exist.",
Resource: separator + bucket,
RequestID: resp.Header.Get("x-amz-request-id"),
HostID: resp.Header.Get("x-amz-id-2"),
}
case http.StatusForbidden:
errorResponse = ErrorResponse{
Code: "AccessDenied",
Message: "Access Denied",
Resource: separator + bucket,
RequestID: resp.Header.Get("x-amz-request-id"),
HostID: resp.Header.Get("x-amz-id-2"),
}
case http.StatusConflict:
errorResponse = ErrorResponse{
Code: "Conflict",
Message: "Bucket not empty",
Resource: separator + bucket,
RequestID: resp.Header.Get("x-amz-request-id"),
HostID: resp.Header.Get("x-amz-id-2"),
}
default:
errorResponse = ErrorResponse{
Code: resp.Status,
Message: resp.Status,
Resource: separator + bucket,
RequestID: resp.Header.Get("x-amz-request-id"),
HostID: resp.Header.Get("x-amz-id-2"),
}
}
return errorResponse
}
}
return nil
}
/// Object Read/Write/Stat Operations
func (a apiCore) putObjectUnAuthenticatedRequest(bucket, object, contentType string, size int64, body io.Reader) (*request, error) {
if strings.TrimSpace(contentType) == "" {
contentType = "application/octet-stream"
}
op := &operation{
HTTPServer: a.config.Endpoint,
HTTPMethod: "PUT",
HTTPPath: separator + bucket + separator + object,
}
r, err := newUnauthenticatedRequest(op, a.config, body)
if err != nil {
return nil, err
}
// Content-MD5 is not set consciously
r.Set("Content-Type", contentType)
r.req.ContentLength = size
return r, nil
}
// putObjectUnAuthenticated - add an object to a bucket
// NOTE: You must have WRITE permissions on a bucket to add an object to it.
func (a apiCore) putObjectUnAuthenticated(bucket, object, contentType string, size int64, body io.Reader) (ObjectStat, error) {
req, err := a.putObjectUnAuthenticatedRequest(bucket, object, contentType, size, body)
if err != nil {
return ObjectStat{}, err
}
resp, err := req.Do()
defer closeResp(resp)
if err != nil {
return ObjectStat{}, err
}
if resp != nil {
if resp.StatusCode != http.StatusOK {
return ObjectStat{}, BodyToErrorResponse(resp.Body, a.config.AcceptType)
}
}
var metadata ObjectStat
metadata.ETag = strings.Trim(resp.Header.Get("ETag"), "\"") // trim off the odd double quotes
return metadata, nil
}
// putObjectRequest wrapper creates a new PutObject request
func (a apiCore) putObjectRequest(bucket, object, contentType string, md5SumBytes []byte, size int64, body io.ReadSeeker) (*request, error) {
if strings.TrimSpace(contentType) == "" {
contentType = "application/octet-stream"
}
op := &operation{
HTTPServer: a.config.Endpoint,
HTTPMethod: "PUT",
HTTPPath: separator + bucket + separator + object,
}
r, err := newRequest(op, a.config, body)
if err != nil {
return nil, err
}
// set Content-MD5 as base64 encoded md5
r.Set("Content-MD5", base64.StdEncoding.EncodeToString(md5SumBytes))
r.Set("Content-Type", contentType)
r.req.ContentLength = size
return r, nil
}
// putObject - add an object to a bucket
// NOTE: You must have WRITE permissions on a bucket to add an object to it.
func (a apiCore) putObject(bucket, object, contentType string, md5SumBytes []byte, size int64, body io.ReadSeeker) (ObjectStat, error) {
req, err := a.putObjectRequest(bucket, object, contentType, md5SumBytes, size, body)
if err != nil {
return ObjectStat{}, err
}
resp, err := req.Do()
defer closeResp(resp)
if err != nil {
return ObjectStat{}, err
}
if resp != nil {
if resp.StatusCode != http.StatusOK {
return ObjectStat{}, BodyToErrorResponse(resp.Body, a.config.AcceptType)
}
}
var metadata ObjectStat
metadata.ETag = strings.Trim(resp.Header.Get("ETag"), "\"") // trim off the odd double quotes
return metadata, nil
}
func (a apiCore) presignedPostPolicy(p *PostPolicy) map[string]string {
t := time.Now().UTC()
r := new(request)
r.config = a.config
credential := getCredential(r.config.AccessKeyID, r.config.Region, t)
p.addNewPolicy(policy{"eq", "$x-amz-date", t.Format(iso8601DateFormat)})
p.addNewPolicy(policy{"eq", "$x-amz-algorithm", authHeader})
p.addNewPolicy(policy{"eq", "$x-amz-credential", credential})
policyBase64 := p.base64()
p.formData["policy"] = policyBase64
p.formData["x-amz-algorithm"] = authHeader
p.formData["x-amz-credential"] = credential
p.formData["x-amz-date"] = t.Format(iso8601DateFormat)
p.formData["x-amz-signature"] = r.PostPresignSignature(policyBase64, t)
return p.formData
}
func (a apiCore) presignedPutObject(bucket, object string, expires int64) (string, error) {
op := &operation{
HTTPServer: a.config.Endpoint,
HTTPMethod: "PUT",
HTTPPath: separator + bucket + separator + object,
}
r, err := newPresignedRequest(op, a.config, strconv.FormatInt(expires, 10))
if err != nil {
return "", err
}
return r.PreSignV4()
}
func (a apiCore) presignedGetObjectRequest(bucket, object string, expires, offset, length int64) (*request, error) {
op := &operation{
HTTPServer: a.config.Endpoint,
HTTPMethod: "GET",
HTTPPath: separator + bucket + separator + object,
}
r, err := newPresignedRequest(op, a.config, strconv.FormatInt(expires, 10))
if err != nil {
return nil, err
}
switch {
case length > 0 && offset > 0:
r.Set("Range", fmt.Sprintf("bytes=%d-%d", offset, offset+length-1))
case offset > 0 && length == 0:
r.Set("Range", fmt.Sprintf("bytes=%d-", offset))
case length > 0 && offset == 0:
r.Set("Range", fmt.Sprintf("bytes=-%d", length))
}
return r, nil
}
func (a apiCore) presignedGetObject(bucket, object string, expires, offset, length int64) (string, error) {
if err := invalidArgumentError(object); err != nil {
return "", err
}
req, err := a.presignedGetObjectRequest(bucket, object, expires, offset, length)
if err != nil {
return "", err
}
return req.PreSignV4()
}
// getObjectRequest wrapper creates a new getObject request
func (a apiCore) getObjectRequest(bucket, object string, offset, length int64) (*request, error) {
op := &operation{
HTTPServer: a.config.Endpoint,
HTTPMethod: "GET",
HTTPPath: separator + bucket + separator + object,
}
r, err := newRequest(op, a.config, nil)
if err != nil {
return nil, err
}
switch {
case length > 0 && offset > 0:
r.Set("Range", fmt.Sprintf("bytes=%d-%d", offset, offset+length-1))
case offset > 0 && length == 0:
r.Set("Range", fmt.Sprintf("bytes=%d-", offset))
case length > 0 && offset == 0:
r.Set("Range", fmt.Sprintf("bytes=-%d", length))
}
return r, nil
}
// getObject - retrieve object from Object Storage
//
// Additionally this function also takes range arguments to download the specified
// range bytes of an object. Setting offset and length = 0 will download the full object.
//
// For more information about the HTTP Range header, go to http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35.
func (a apiCore) getObject(bucket, object string, offset, length int64) (io.ReadCloser, ObjectStat, error) {
if err := invalidArgumentError(object); err != nil {
return nil, ObjectStat{}, err
}
req, err := a.getObjectRequest(bucket, object, offset, length)
if err != nil {
return nil, ObjectStat{}, err
}
resp, err := req.Do()
if err != nil {
return nil, ObjectStat{}, err
}
if resp != nil {
switch resp.StatusCode {
case http.StatusOK:
case http.StatusPartialContent:
default:
return nil, ObjectStat{}, BodyToErrorResponse(resp.Body, a.config.AcceptType)
}
}
md5sum := strings.Trim(resp.Header.Get("ETag"), "\"") // trim off the odd double quotes
date, err := time.Parse(http.TimeFormat, resp.Header.Get("Last-Modified"))
if err != nil {
return nil, ObjectStat{}, ErrorResponse{
Code: "InternalError",
Message: "Last-Modified time format not recognized, please report this issue at https://github.com/minio/minio-go/issues",
RequestID: resp.Header.Get("x-amz-request-id"),
HostID: resp.Header.Get("x-amz-id-2"),
}
}
contentType := strings.TrimSpace(resp.Header.Get("Content-Type"))
if contentType == "" {
contentType = "application/octet-stream"
}
var objectstat ObjectStat
objectstat.ETag = md5sum
objectstat.Key = object
objectstat.Size = resp.ContentLength
objectstat.LastModified = date
objectstat.ContentType = contentType
// do not close body here, caller will close
return resp.Body, objectstat, nil
}
// deleteObjectRequest wrapper creates a new deleteObject request
func (a apiCore) deleteObjectRequest(bucket, object string) (*request, error) {
op := &operation{
HTTPServer: a.config.Endpoint,
HTTPMethod: "DELETE",
HTTPPath: separator + bucket + separator + object,
}
return newRequest(op, a.config, nil)
}
// deleteObject deletes a given object from a bucket
func (a apiCore) deleteObject(bucket, object string) error {
if err := invalidBucketError(bucket); err != nil {
return err
}
if err := invalidArgumentError(object); err != nil {
return err
}
req, err := a.deleteObjectRequest(bucket, object)
if err != nil {
return err
}
resp, err := req.Do()
defer closeResp(resp)
if err != nil {
return err
}
if resp != nil {
if resp.StatusCode != http.StatusNoContent {
var errorResponse ErrorResponse
switch resp.StatusCode {
case http.StatusNotFound:
errorResponse = ErrorResponse{
Code: "NoSuchKey",
Message: "The specified key does not exist.",
Resource: separator + bucket + separator + object,
RequestID: resp.Header.Get("x-amz-request-id"),
HostID: resp.Header.Get("x-amz-id-2"),
}
case http.StatusForbidden:
errorResponse = ErrorResponse{
Code: "AccessDenied",
Message: "Access Denied",
Resource: separator + bucket + separator + object,
RequestID: resp.Header.Get("x-amz-request-id"),
HostID: resp.Header.Get("x-amz-id-2"),
}
default:
errorResponse = ErrorResponse{
Code: resp.Status,
Message: resp.Status,
Resource: separator + bucket + separator + object,
RequestID: resp.Header.Get("x-amz-request-id"),
HostID: resp.Header.Get("x-amz-id-2"),
}
}
return errorResponse
}
}
return nil
}
// headObjectRequest wrapper creates a new headObject request
func (a apiCore) headObjectRequest(bucket, object string) (*request, error) {
op := &operation{
HTTPServer: a.config.Endpoint,
HTTPMethod: "HEAD",
HTTPPath: separator + bucket + separator + object,
}
return newRequest(op, a.config, nil)
}
// headObject retrieves metadata from an object without returning the object itself
func (a apiCore) headObject(bucket, object string) (ObjectStat, error) {
if err := invalidBucketError(bucket); err != nil {
return ObjectStat{}, err
}
if err := invalidArgumentError(object); err != nil {
return ObjectStat{}, err
}
req, err := a.headObjectRequest(bucket, object)
if err != nil {
return ObjectStat{}, err
}
resp, err := req.Do()
defer closeResp(resp)
if err != nil {
return ObjectStat{}, err
}
if resp != nil {
if resp.StatusCode != http.StatusOK {
var errorResponse ErrorResponse
switch resp.StatusCode {
case http.StatusNotFound:
errorResponse = ErrorResponse{
Code: "NoSuchKey",
Message: "The specified key does not exist.",
Resource: separator + bucket + separator + object,
RequestID: resp.Header.Get("x-amz-request-id"),
HostID: resp.Header.Get("x-amz-id-2"),
}
case http.StatusForbidden:
errorResponse = ErrorResponse{
Code: "AccessDenied",
Message: "Access Denied",
Resource: separator + bucket + separator + object,
RequestID: resp.Header.Get("x-amz-request-id"),
HostID: resp.Header.Get("x-amz-id-2"),
}
default:
errorResponse = ErrorResponse{
Code: resp.Status,
Message: resp.Status,
Resource: separator + bucket + separator + object,
RequestID: resp.Header.Get("x-amz-request-id"),
HostID: resp.Header.Get("x-amz-id-2"),
}
}
return ObjectStat{}, errorResponse
}
}
md5sum := strings.Trim(resp.Header.Get("ETag"), "\"") // trim off the odd double quotes
size, err := strconv.ParseInt(resp.Header.Get("Content-Length"), 10, 64)
if err != nil {
return ObjectStat{}, ErrorResponse{
Code: "InternalError",
Message: "Content-Length not recognized, please report this issue at https://github.com/minio/minio-go/issues",
RequestID: resp.Header.Get("x-amz-request-id"),
HostID: resp.Header.Get("x-amz-id-2"),
}
}
date, err := time.Parse(http.TimeFormat, resp.Header.Get("Last-Modified"))
if err != nil {
return ObjectStat{}, ErrorResponse{
Code: "InternalError",
Message: "Last-Modified time format not recognized, please report this issue at https://github.com/minio/minio-go/issues",
RequestID: resp.Header.Get("x-amz-request-id"),
HostID: resp.Header.Get("x-amz-id-2"),
}
}
contentType := strings.TrimSpace(resp.Header.Get("Content-Type"))
if contentType == "" {
contentType = "application/octet-stream"
}
var objectstat ObjectStat
objectstat.ETag = md5sum
objectstat.Key = object
objectstat.Size = size
objectstat.LastModified = date
objectstat.ContentType = contentType
return objectstat, nil
}
/// Service Operations
// listBucketRequest wrapper creates a new listBuckets request
func (a apiCore) listBucketsRequest() (*request, error) {
op := &operation{
HTTPServer: a.config.Endpoint,
HTTPMethod: "GET",
HTTPPath: separator,
}
return newRequest(op, a.config, nil)
}
// listBuckets list of all buckets owned by the authenticated sender of the request
func (a apiCore) listBuckets() (listAllMyBucketsResult, error) {
req, err := a.listBucketsRequest()
if err != nil {
return listAllMyBucketsResult{}, err
}
resp, err := req.Do()
defer closeResp(resp)
if err != nil {
return listAllMyBucketsResult{}, err
}
if resp != nil {
// for un-authenticated requests, amazon sends a redirect handle it
if resp.StatusCode == http.StatusTemporaryRedirect {
return listAllMyBucketsResult{}, ErrorResponse{
Code: "AccessDenied",
Message: "Anonymous access is forbidden for this operation",
RequestID: resp.Header.Get("x-amz-request-id"),
HostID: resp.Header.Get("x-amz-id-2"),
}
}
if resp.StatusCode != http.StatusOK {
return listAllMyBucketsResult{}, BodyToErrorResponse(resp.Body, a.config.AcceptType)
}
}
listAllMyBucketsResult := listAllMyBucketsResult{}
err = acceptTypeDecoder(resp.Body, a.config.AcceptType, &listAllMyBucketsResult)
if err != nil {
return listAllMyBucketsResult, err
}
return listAllMyBucketsResult, nil
}
+329
View File
@@ -0,0 +1,329 @@
/*
* 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/base64"
"encoding/hex"
"encoding/json"
"encoding/xml"
"fmt"
"io"
"net/http"
"strconv"
)
// listMultipartUploadsRequest wrapper creates a new listMultipartUploads request
func (a apiCore) listMultipartUploadsRequest(bucket, keymarker, uploadIDMarker, prefix, delimiter string, maxuploads int) (*request, error) {
// resourceQuery - get resources properly escaped and lined up before using them in http request
resourceQuery := func() (string, error) {
switch {
case keymarker != "":
keymarker = fmt.Sprintf("&key-marker=%s", getURLEncodedPath(keymarker))
fallthrough
case uploadIDMarker != "":
uploadIDMarker = fmt.Sprintf("&upload-id-marker=%s", uploadIDMarker)
fallthrough
case prefix != "":
prefix = fmt.Sprintf("&prefix=%s", getURLEncodedPath(prefix))
fallthrough
case delimiter != "":
delimiter = fmt.Sprintf("&delimiter=%s", delimiter)
}
query := fmt.Sprintf("?uploads&max-uploads=%d", maxuploads) + keymarker + uploadIDMarker + prefix + delimiter
return query, nil
}
query, err := resourceQuery()
if err != nil {
return nil, err
}
op := &operation{
HTTPServer: a.config.Endpoint,
HTTPMethod: "GET",
HTTPPath: separator + bucket + query,
}
r, err := newRequest(op, a.config, nil)
if err != nil {
return nil, err
}
return r, nil
}
// listMultipartUploads - (List Multipart Uploads) - Lists some or all (up to 1000) in-progress multipart uploads in a bucket.
//
// You can use the request parameters as selection criteria to return a subset of the uploads in a bucket.
// request paramters :-
// ---------
// ?key-marker - Specifies the multipart upload after which listing should begin
// ?upload-id-marker - Together with key-marker specifies the multipart upload after which listing should begin
// ?delimiter - A delimiter is a character you use to group keys.
// ?prefix - Limits the response to keys that begin with the specified prefix.
// ?max-uploads - Sets the maximum number of multipart uploads returned in the response body.
func (a apiCore) listMultipartUploads(bucket, keymarker, uploadIDMarker, prefix, delimiter string, maxuploads int) (listMultipartUploadsResult, error) {
req, err := a.listMultipartUploadsRequest(bucket, keymarker, uploadIDMarker, prefix, delimiter, maxuploads)
if err != nil {
return listMultipartUploadsResult{}, err
}
resp, err := req.Do()
defer closeResp(resp)
if err != nil {
return listMultipartUploadsResult{}, err
}
if resp != nil {
if resp.StatusCode != http.StatusOK {
return listMultipartUploadsResult{}, BodyToErrorResponse(resp.Body, a.config.AcceptType)
}
}
listMultipartUploadsResult := listMultipartUploadsResult{}
err = acceptTypeDecoder(resp.Body, a.config.AcceptType, &listMultipartUploadsResult)
if err != nil {
return listMultipartUploadsResult, err
}
// close body while returning, along with any error
return listMultipartUploadsResult, nil
}
// initiateMultipartRequest wrapper creates a new initiateMultiPart request
func (a apiCore) initiateMultipartRequest(bucket, object string) (*request, error) {
op := &operation{
HTTPServer: a.config.Endpoint,
HTTPMethod: "POST",
HTTPPath: separator + bucket + separator + object + "?uploads",
}
return newRequest(op, a.config, nil)
}
// initiateMultipartUpload initiates a multipart upload and returns an upload ID
func (a apiCore) initiateMultipartUpload(bucket, object string) (initiateMultipartUploadResult, error) {
req, err := a.initiateMultipartRequest(bucket, object)
if err != nil {
return initiateMultipartUploadResult{}, err
}
resp, err := req.Do()
defer closeResp(resp)
if err != nil {
return initiateMultipartUploadResult{}, err
}
if resp != nil {
if resp.StatusCode != http.StatusOK {
return initiateMultipartUploadResult{}, BodyToErrorResponse(resp.Body, a.config.AcceptType)
}
}
initiateMultipartUploadResult := initiateMultipartUploadResult{}
err = acceptTypeDecoder(resp.Body, a.config.AcceptType, &initiateMultipartUploadResult)
if err != nil {
return initiateMultipartUploadResult, err
}
return initiateMultipartUploadResult, nil
}
// completeMultipartUploadRequest wrapper creates a new CompleteMultipartUpload request
func (a apiCore) completeMultipartUploadRequest(bucket, object, uploadID string, complete completeMultipartUpload) (*request, error) {
op := &operation{
HTTPServer: a.config.Endpoint,
HTTPMethod: "POST",
HTTPPath: separator + bucket + separator + object + "?uploadId=" + uploadID,
}
var completeMultipartUploadBytes []byte
var err error
switch {
case a.config.AcceptType == "application/xml":
completeMultipartUploadBytes, err = xml.Marshal(complete)
case a.config.AcceptType == "application/json":
completeMultipartUploadBytes, err = json.Marshal(complete)
default:
completeMultipartUploadBytes, err = xml.Marshal(complete)
}
if err != nil {
return nil, err
}
completeMultipartUploadBuffer := bytes.NewReader(completeMultipartUploadBytes)
r, err := newRequest(op, a.config, completeMultipartUploadBuffer)
if err != nil {
return nil, err
}
r.req.ContentLength = int64(completeMultipartUploadBuffer.Len())
return r, nil
}
// completeMultipartUpload completes a multipart upload by assembling previously uploaded parts.
func (a apiCore) completeMultipartUpload(bucket, object, uploadID string, c completeMultipartUpload) (completeMultipartUploadResult, error) {
req, err := a.completeMultipartUploadRequest(bucket, object, uploadID, c)
if err != nil {
return completeMultipartUploadResult{}, err
}
resp, err := req.Do()
defer closeResp(resp)
if err != nil {
return completeMultipartUploadResult{}, err
}
if resp != nil {
if resp.StatusCode != http.StatusOK {
return completeMultipartUploadResult{}, BodyToErrorResponse(resp.Body, a.config.AcceptType)
}
}
completeMultipartUploadResult := completeMultipartUploadResult{}
err = acceptTypeDecoder(resp.Body, a.config.AcceptType, &completeMultipartUploadResult)
if err != nil {
return completeMultipartUploadResult, err
}
return completeMultipartUploadResult, nil
}
// abortMultipartUploadRequest wrapper creates a new AbortMultipartUpload request
func (a apiCore) abortMultipartUploadRequest(bucket, object, uploadID string) (*request, error) {
op := &operation{
HTTPServer: a.config.Endpoint,
HTTPMethod: "DELETE",
HTTPPath: separator + bucket + separator + object + "?uploadId=" + uploadID,
}
return newRequest(op, a.config, nil)
}
// abortMultipartUpload aborts a multipart upload for the given uploadID, all parts are deleted
func (a apiCore) abortMultipartUpload(bucket, object, uploadID string) error {
req, err := a.abortMultipartUploadRequest(bucket, object, uploadID)
if err != nil {
return err
}
resp, err := req.Do()
defer closeResp(resp)
if err != nil {
return err
}
if resp != nil {
if resp.StatusCode != http.StatusNoContent {
// Abort has no response body, handle it
var errorResponse ErrorResponse
switch resp.StatusCode {
case http.StatusNotFound:
errorResponse = ErrorResponse{
Code: "NoSuchUpload",
Message: "The specified multipart upload does not exist.",
Resource: separator + bucket + separator + object,
RequestID: resp.Header.Get("x-amz-request-id"),
}
case http.StatusForbidden:
errorResponse = ErrorResponse{
Code: "AccessDenied",
Message: "Access Denied",
Resource: separator + bucket + separator + object,
RequestID: resp.Header.Get("x-amz-request-id"),
}
default:
errorResponse = ErrorResponse{
Code: resp.Status,
Message: "",
Resource: separator + bucket + separator + object,
RequestID: resp.Header.Get("x-amz-request-id"),
}
}
return errorResponse
}
}
return nil
}
// listObjectPartsRequest wrapper creates a new ListObjectParts request
func (a apiCore) listObjectPartsRequest(bucket, object, uploadID string, partNumberMarker, maxParts int) (*request, error) {
// resourceQuery - get resources properly escaped and lined up before using them in http request
resourceQuery := func() string {
var partNumberMarkerStr string
switch {
case partNumberMarker != 0:
partNumberMarkerStr = fmt.Sprintf("&part-number-marker=%d", partNumberMarker)
}
return fmt.Sprintf("?uploadId=%s&max-parts=%d", uploadID, maxParts) + partNumberMarkerStr
}
op := &operation{
HTTPServer: a.config.Endpoint,
HTTPMethod: "GET",
HTTPPath: separator + bucket + separator + object + resourceQuery(),
}
return newRequest(op, a.config, nil)
}
// listObjectParts (List Parts) - lists some or all (up to 1000) parts that have been uploaded for a specific multipart upload
//
// You can use the request parameters as selection criteria to return a subset of the uploads in a bucket.
// request paramters :-
// ---------
// ?part-number-marker - Specifies the part after which listing should begin.
func (a apiCore) listObjectParts(bucket, object, uploadID string, partNumberMarker, maxParts int) (listObjectPartsResult, error) {
req, err := a.listObjectPartsRequest(bucket, object, uploadID, partNumberMarker, maxParts)
if err != nil {
return listObjectPartsResult{}, err
}
resp, err := req.Do()
defer closeResp(resp)
if err != nil {
return listObjectPartsResult{}, err
}
if resp != nil {
if resp.StatusCode != http.StatusOK {
return listObjectPartsResult{}, BodyToErrorResponse(resp.Body, a.config.AcceptType)
}
}
listObjectPartsResult := listObjectPartsResult{}
err = acceptTypeDecoder(resp.Body, a.config.AcceptType, &listObjectPartsResult)
if err != nil {
return listObjectPartsResult, err
}
return listObjectPartsResult, nil
}
// uploadPartRequest wrapper creates a new UploadPart request
func (a apiCore) uploadPartRequest(bucket, object, uploadID string, md5SumBytes []byte, partNumber int, size int64, body io.ReadSeeker) (*request, error) {
op := &operation{
HTTPServer: a.config.Endpoint,
HTTPMethod: "PUT",
HTTPPath: separator + bucket + separator + object + "?partNumber=" + strconv.Itoa(partNumber) + "&uploadId=" + uploadID,
}
r, err := newRequest(op, a.config, body)
if err != nil {
return nil, err
}
// set Content-MD5 as base64 encoded md5
r.Set("Content-MD5", base64.StdEncoding.EncodeToString(md5SumBytes))
r.req.ContentLength = size
return r, nil
}
// uploadPart uploads a part in a multipart upload.
func (a apiCore) uploadPart(bucket, object, uploadID string, md5SumBytes []byte, partNumber int, size int64, body io.ReadSeeker) (completePart, error) {
req, err := a.uploadPartRequest(bucket, object, uploadID, md5SumBytes, partNumber, size, body)
if err != nil {
return completePart{}, err
}
cPart := completePart{}
cPart.PartNumber = partNumber
cPart.ETag = "\"" + hex.EncodeToString(md5SumBytes) + "\""
// initiate the request
resp, err := req.Do()
defer closeResp(resp)
if err != nil {
return completePart{}, err
}
if resp != nil {
if resp.StatusCode != http.StatusOK {
return completePart{}, BodyToErrorResponse(resp.Body, a.config.AcceptType)
}
}
return cPart, nil
}
File diff suppressed because it is too large. Load diff
+170
View File
@@ -0,0 +1,170 @@
/*
* 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_test
// bucketHandler is an http.Handler that verifies bucket responses and validates incoming requests
import (
"bytes"
"io"
"net/http"
"strconv"
"time"
)
type bucketHandler struct {
resource string
}
func (h bucketHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
switch {
case r.Method == "GET":
switch {
case r.URL.Path == "/":
response := []byte("<?xml version=\"1.0\" encoding=\"UTF-8\"?><ListAllMyBucketsResult xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\"><Buckets><Bucket><Name>bucket</Name><CreationDate>2015-05-20T23:05:09.230Z</CreationDate></Bucket></Buckets><Owner><ID>minio</ID><DisplayName>minio</DisplayName></Owner></ListAllMyBucketsResult>")
w.Header().Set("Content-Length", strconv.Itoa(len(response)))
w.Write(response)
case r.URL.Path == "/bucket":
_, ok := r.URL.Query()["acl"]
if ok {
response := []byte("<?xml version=\"1.0\" encoding=\"UTF-8\"?><AccessControlPolicy><Owner><ID>75aa57f09aa0c8caeab4f8c24e99d10f8e7faeebf76c078efc7c6caea54ba06a</ID><DisplayName>CustomersName@amazon.com</DisplayName></Owner><AccessControlList><Grant><Grantee xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"CanonicalUser\"><ID>75aa57f09aa0c8caeab4f8c24e99d10f8e7faeebf76c078efc7c6caea54ba06a</ID><DisplayName>CustomersName@amazon.com</DisplayName></Grantee><Permission>FULL_CONTROL</Permission></Grant></AccessControlList></AccessControlPolicy>")
w.Header().Set("Content-Length", strconv.Itoa(len(response)))
w.Write(response)
return
}
fallthrough
case r.URL.Path == "/bucket":
response := []byte("<?xml version=\"1.0\" encoding=\"UTF-8\"?><ListBucketResult xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\"><Contents><ETag>\"259d04a13802ae09c7e41be50ccc6baa\"</ETag><Key>object</Key><LastModified>2015-05-21T18:24:21.097Z</LastModified><Size>22061</Size><Owner><ID>minio</ID><DisplayName>minio</DisplayName></Owner><StorageClass>STANDARD</StorageClass></Contents><Delimiter></Delimiter><EncodingType></EncodingType><IsTruncated>false</IsTruncated><Marker></Marker><MaxKeys>1000</MaxKeys><Name>testbucket</Name><NextMarker></NextMarker><Prefix></Prefix></ListBucketResult>")
w.Header().Set("Content-Length", strconv.Itoa(len(response)))
w.Write(response)
}
case r.Method == "PUT":
switch {
case r.URL.Path == h.resource:
_, ok := r.URL.Query()["acl"]
if ok {
switch r.Header.Get("x-amz-acl") {
case "public-read-write":
fallthrough
case "public-read":
fallthrough
case "private":
fallthrough
case "authenticated-read":
w.WriteHeader(http.StatusOK)
return
default:
w.WriteHeader(http.StatusNotImplemented)
return
}
}
w.WriteHeader(http.StatusOK)
default:
w.WriteHeader(http.StatusBadRequest)
}
case r.Method == "HEAD":
switch {
case r.URL.Path == h.resource:
w.WriteHeader(http.StatusOK)
default:
w.WriteHeader(http.StatusForbidden)
}
case r.Method == "DELETE":
switch {
case r.URL.Path != h.resource:
w.WriteHeader(http.StatusNotFound)
default:
h.resource = ""
w.WriteHeader(http.StatusNoContent)
}
}
}
// objectHandler is an http.Handler that verifies object responses and validates incoming requests
type objectHandler struct {
resource string
data []byte
}
func (h objectHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
switch {
case r.Method == "PUT":
length, err := strconv.Atoi(r.Header.Get("Content-Length"))
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
var buffer bytes.Buffer
_, err = io.CopyN(&buffer, r.Body, int64(length))
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
if !bytes.Equal(h.data, buffer.Bytes()) {
w.WriteHeader(http.StatusInternalServerError)
return
}
w.Header().Set("ETag", "\"9af2f8218b150c351ad802c6f3d66abe\"")
w.WriteHeader(http.StatusOK)
case r.Method == "HEAD":
if r.URL.Path != h.resource {
w.WriteHeader(http.StatusNotFound)
return
}
w.Header().Set("Content-Length", strconv.Itoa(len(h.data)))
w.Header().Set("Last-Modified", time.Now().UTC().Format(http.TimeFormat))
w.Header().Set("ETag", "\"9af2f8218b150c351ad802c6f3d66abe\"")
w.WriteHeader(http.StatusOK)
case r.Method == "POST":
_, ok := r.URL.Query()["uploads"]
if ok {
response := []byte("<?xml version=\"1.0\" encoding=\"UTF-8\"?><InitiateMultipartUploadResult xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\"><Bucket>example-bucket</Bucket><Key>object</Key><UploadId>XXBsb2FkIElEIGZvciBlbHZpbmcncyVcdS1tb3ZpZS5tMnRzEEEwbG9hZA</UploadId></InitiateMultipartUploadResult>")
w.Header().Set("Content-Length", strconv.Itoa(len(response)))
w.Write(response)
return
}
case r.Method == "GET":
_, ok := r.URL.Query()["uploadId"]
if ok {
uploadID := r.URL.Query().Get("uploadId")
if uploadID != "XXBsb2FkIElEIGZvciBlbHZpbmcncyVcdS1tb3ZpZS5tMnRzEEEwbG9hZA" {
w.WriteHeader(http.StatusNotFound)
return
}
response := []byte("<?xml version=\"1.0\" encoding=\"UTF-8\"?><ListPartsResult xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\"><Bucket>example-bucket</Bucket><Key>example-object</Key><UploadId>XXBsb2FkIElEIGZvciBlbHZpbmcncyVcdS1tb3ZpZS5tMnRzEEEwbG9hZA</UploadId><Initiator><ID>arn:aws:iam::111122223333:user/some-user-11116a31-17b5-4fb7-9df5-b288870f11xx</ID><DisplayName>umat-user-11116a31-17b5-4fb7-9df5-b288870f11xx</DisplayName></Initiator><Owner><ID>75aa57f09aa0c8caeab4f8c24e99d10f8e7faeebf76c078efc7c6caea54ba06a</ID><DisplayName>someName</DisplayName></Owner><StorageClass>STANDARD</StorageClass><PartNumberMarker>1</PartNumberMarker><NextPartNumberMarker>3</NextPartNumberMarker><MaxParts>2</MaxParts><IsTruncated>true</IsTruncated><Part><PartNumber>2</PartNumber><LastModified>2010-11-10T20:48:34.000Z</LastModified><ETag>\"7778aef83f66abc1fa1e8477f296d394\"</ETag><Size>10485760</Size></Part><Part><PartNumber>3</PartNumber><LastModified>2010-11-10T20:48:33.000Z</LastModified><ETag>\"aaaa18db4cc2f85cedef654fccc4a4x8\"</ETag><Size>10485760</Size></Part></ListPartsResult>")
w.Header().Set("Content-Length", strconv.Itoa(len(response)))
w.Write(response)
return
}
if r.URL.Path != h.resource {
w.WriteHeader(http.StatusNotFound)
return
}
w.Header().Set("Content-Length", strconv.Itoa(len(h.data)))
w.Header().Set("Last-Modified", time.Now().UTC().Format(http.TimeFormat))
w.Header().Set("ETag", "\"9af2f8218b150c351ad802c6f3d66abe\"")
w.WriteHeader(http.StatusOK)
io.Copy(w, bytes.NewReader(h.data))
case r.Method == "DELETE":
if r.URL.Path != h.resource {
w.WriteHeader(http.StatusNotFound)
return
}
h.resource = ""
h.data = nil
w.WriteHeader(http.StatusNoContent)
}
}
+110
View File
@@ -0,0 +1,110 @@
/*
* 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 (
"strings"
"testing"
)
func TestACLTypes(t *testing.T) {
want := map[string]bool{
"private": true,
"public-read": true,
"public-read-write": true,
"authenticated-read": true,
"invalid": false,
}
for acl, ok := range want {
if BucketACL(acl).isValidBucketACL() != ok {
t.Fatal("Error")
}
}
}
func TestUserAgent(t *testing.T) {
conf := new(Config)
conf.SetUserAgent("minio", "1.0", "amd64")
if !strings.Contains(conf.userAgent, "minio") {
t.Fatalf("Error")
}
}
func TestGetRegion(t *testing.T) {
region := getRegion("s3.amazonaws.com")
if region != "us-east-1" {
t.Fatalf("Error")
}
region = getRegion("localhost:9000")
if region != "milkyway" {
t.Fatalf("Error")
}
}
func TestPartSize(t *testing.T) {
var maxPartSize int64 = 1024 * 1024 * 1024 * 5
partSize := calculatePartSize(5000000000000000000)
if partSize > minimumPartSize {
if partSize > maxPartSize {
t.Fatal("invalid result, cannot be bigger than maxPartSize 5GB")
}
}
partSize = calculatePartSize(50000000000)
if partSize > minimumPartSize {
t.Fatal("invalid result, cannot be bigger than minimumPartSize 5MB")
}
}
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 != getURLEncodedPath(u.name) {
t.Errorf("Error")
}
}
}
+287
View File
@@ -0,0 +1,287 @@
/*
* 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_test
import (
"bytes"
"io"
"net/http/httptest"
"testing"
"time"
"github.com/minio/minio-go"
)
func TestBucketOperations(t *testing.T) {
bucket := bucketHandler(bucketHandler{
resource: "/bucket",
})
server := httptest.NewServer(bucket)
defer server.Close()
a, err := minio.New(minio.Config{Endpoint: server.URL})
if err != nil {
t.Fatal("Error")
}
err = a.MakeBucket("bucket", "private")
if err != nil {
t.Fatal("Error")
}
err = a.BucketExists("bucket")
if err != nil {
t.Fatal("Error")
}
err = a.BucketExists("bucket1")
if err == nil {
t.Fatal("Error")
}
if err.Error() != "Access Denied" {
t.Fatal("Error")
}
err = a.SetBucketACL("bucket", "public-read-write")
if err != nil {
t.Fatal("Error")
}
acl, err := a.GetBucketACL("bucket")
if err != nil {
t.Fatal("Error")
}
if acl != minio.BucketACL("private") {
t.Fatal("Error")
}
for b := range a.ListBuckets() {
if b.Err != nil {
t.Fatal(b.Err.Error())
}
if b.Stat.Name != "bucket" {
t.Fatal("Error")
}
}
for o := range a.ListObjects("bucket", "", true) {
if o.Err != nil {
t.Fatal(o.Err.Error())
}
if o.Stat.Key != "object" {
t.Fatal("Error")
}
}
err = a.RemoveBucket("bucket")
if err != nil {
t.Fatal("Error")
}
err = a.RemoveBucket("bucket1")
if err == nil {
t.Fatal("Error")
}
if err.Error() != "The specified bucket does not exist." {
t.Fatal("Error")
}
}
func TestBucketOperationsFail(t *testing.T) {
bucket := bucketHandler(bucketHandler{
resource: "/bucket",
})
server := httptest.NewServer(bucket)
defer server.Close()
a, err := minio.New(minio.Config{Endpoint: server.URL})
if err != nil {
t.Fatal("Error")
}
err = a.MakeBucket("bucket$$$", "private")
if err == nil {
t.Fatal("Error")
}
err = a.BucketExists("bucket.")
if err == nil {
t.Fatal("Error")
}
err = a.SetBucketACL("bucket-.", "public-read-write")
if err == nil {
t.Fatal("Error")
}
_, err = a.GetBucketACL("bucket??")
if err == nil {
t.Fatal("Error")
}
for o := range a.ListObjects("bucket??", "", true) {
if o.Err == nil {
t.Fatal(o.Err.Error())
}
}
err = a.RemoveBucket("bucket??")
if err == nil {
t.Fatal("Error")
}
if err.Error() != "The specified bucket is not valid." {
t.Fatal("Error")
}
}
func TestObjectOperations(t *testing.T) {
object := objectHandler(objectHandler{
resource: "/bucket/object",
data: []byte("Hello, World"),
})
server := httptest.NewServer(object)
defer server.Close()
a, err := minio.New(minio.Config{Endpoint: server.URL})
if err != nil {
t.Fatal("Error")
}
data := []byte("Hello, World")
err = a.PutObject("bucket", "object", "", int64(len(data)), bytes.NewReader(data))
if err != nil {
t.Fatal("Error")
}
metadata, err := a.StatObject("bucket", "object")
if err != nil {
t.Fatal("Error")
}
if metadata.Key != "object" {
t.Fatal("Error")
}
if metadata.ETag != "9af2f8218b150c351ad802c6f3d66abe" {
t.Fatal("Error")
}
reader, metadata, err := a.GetObject("bucket", "object")
if err != nil {
t.Fatal("Error")
}
if metadata.Key != "object" {
t.Fatal("Error")
}
if metadata.ETag != "9af2f8218b150c351ad802c6f3d66abe" {
t.Fatal("Error")
}
var buffer bytes.Buffer
_, err = io.Copy(&buffer, reader)
if !bytes.Equal(buffer.Bytes(), data) {
t.Fatal("Error")
}
err = a.RemoveObject("bucket", "object")
if err != nil {
t.Fatal("Error")
}
err = a.RemoveObject("bucket", "object1")
if err == nil {
t.Fatal("Error")
}
if err.Error() != "The specified key does not exist." {
t.Fatal("Error")
}
}
func TestPresignedURL(t *testing.T) {
object := objectHandler(objectHandler{
resource: "/bucket/object",
data: []byte("Hello, World"),
})
server := httptest.NewServer(object)
defer server.Close()
a, err := minio.New(minio.Config{Endpoint: server.URL})
if err != nil {
t.Fatal("Error")
}
// should error out for invalid access keys
_, err = a.PresignedGetObject("bucket", "object", time.Duration(1000)*time.Second)
if err == nil {
t.Fatal("Error")
}
a, err = minio.New(minio.Config{
Endpoint: server.URL,
AccessKeyID: "accessKey",
SecretAccessKey: "secretKey",
})
if err != nil {
t.Fatal("Error")
}
url, err := a.PresignedGetObject("bucket", "object", time.Duration(1000)*time.Second)
if err != nil {
t.Fatal("Error")
}
if url == "" {
t.Fatal("Error")
}
_, err = a.PresignedGetObject("bucket", "object", time.Duration(0)*time.Second)
if err == nil {
t.Fatal("Error")
}
_, err = a.PresignedGetObject("bucket", "object", time.Duration(604801)*time.Second)
if err == nil {
t.Fatal("Error")
}
}
func TestErrorResponse(t *testing.T) {
errorResponse := []byte("<?xml version=\"1.0\" encoding=\"UTF-8\"?><Error><Code>AccessDenied</Code><Message>Access Denied</Message><Resource>/mybucket/myphoto.jpg</Resource><RequestId>F19772218238A85A</RequestId><HostId>GuWkjyviSiGHizehqpmsD1ndz5NClSP19DOT+s2mv7gXGQ8/X1lhbDGiIJEXpGFD</HostId></Error>")
errorReader := bytes.NewReader(errorResponse)
err := minio.BodyToErrorResponse(errorReader, "application/xml")
if err == nil {
t.Fatal("Error")
}
if err.Error() != "Access Denied" {
t.Fatal("Error")
}
resp := minio.ToErrorResponse(err)
// valid all fields
if resp == nil {
t.Fatal("Error")
}
if resp.Code != "AccessDenied" {
t.Fatal("Error")
}
if resp.RequestID != "F19772218238A85A" {
t.Fatal("Error")
}
if resp.Message != "Access Denied" {
t.Fatal("Error")
}
if resp.Resource != "/mybucket/myphoto.jpg" {
t.Fatal("Error")
}
if resp.HostID != "GuWkjyviSiGHizehqpmsD1ndz5NClSP19DOT+s2mv7gXGQ8/X1lhbDGiIJEXpGFD" {
t.Fatal("Error")
}
if resp.ToXML() == "" {
t.Fatal("Error")
}
if resp.ToJSON() == "" {
t.Fatal("Error")
}
}
+41
View File
@@ -0,0 +1,41 @@
# version format
version: "{build}"
# Operating system (build VM template)
os: Windows Server 2012 R2
clone_folder: c:\gopath\src\github.com\minio\minio-go
# environment variables
environment:
GOPATH: c:\gopath
GO15VENDOREXPERIMENT: 1
# scripts that run after cloning repository
install:
- set PATH=%GOPATH%\bin;c:\go\bin;%PATH%
- rd C:\Go /s /q
- appveyor DownloadFile https://storage.googleapis.com/golang/go1.5.1.windows-amd64.zip
- 7z x go1.5.1.windows-amd64.zip -oC:\ >nul
- 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/fzipp/gocyclo
- go get -u github.com/remyoudompheng/go-misc/deadcode
# to run your custom scripts instead of automatic MSBuild
build_script:
- go vet ./...
- gofmt -s -l .
- golint github.com/minio/minio-go...
- gocyclo -over 30 .
- deadcode
- go test
- go test -race
# to disable automatic tests
test: off
# to disable deployment
deploy: off
+75
View File
@@ -0,0 +1,75 @@
/*
* 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")
)
// String printer helper
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
}
+136
View File
@@ -0,0 +1,136 @@
/*
* 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"
"crypto/md5"
"io"
)
// part - message structure for results from the MultiPart
type part struct {
MD5Sum []byte
ReadSeeker io.ReadSeeker
Err error
Len int64
Num int // part number
}
// skipPart - skipping uploaded parts
type skipPart struct {
md5sum []byte
partNumber int
}
// chopper reads from io.Reader, partitions the data into chunks of given chunksize, and sends
// each chunk as io.ReadSeeker to the caller over a channel
//
// This method runs until an EOF or error occurs. If an error occurs,
// the method sends the error over the channel and returns.
// Before returning, the channel is always closed.
//
// additionally this function also skips list of parts if provided
func chopper(reader io.Reader, chunkSize int64, skipParts []skipPart) <-chan part {
ch := make(chan part, 3)
go chopperInRoutine(reader, chunkSize, skipParts, ch)
return ch
}
func chopperInRoutine(reader io.Reader, chunkSize int64, skipParts []skipPart, ch chan part) {
defer close(ch)
p := make([]byte, chunkSize)
n, err := io.ReadFull(reader, p)
if err == io.EOF || err == io.ErrUnexpectedEOF { // short read, only single part return
m := md5.Sum(p[0:n])
ch <- part{
MD5Sum: m[:],
ReadSeeker: bytes.NewReader(p[0:n]),
Err: nil,
Len: int64(n),
Num: 1,
}
return
}
// catastrophic error send error and return
if err != nil {
ch <- part{
ReadSeeker: nil,
Err: err,
Num: 0,
}
return
}
// send the first part
var num = 1
md5SumBytes := md5.Sum(p)
sp := skipPart{
partNumber: num,
md5sum: md5SumBytes[:],
}
if !isPartNumberUploaded(sp, skipParts) {
ch <- part{
MD5Sum: md5SumBytes[:],
ReadSeeker: bytes.NewReader(p),
Err: nil,
Len: int64(n),
Num: num,
}
}
for err == nil {
var n int
p := make([]byte, chunkSize)
n, err = io.ReadFull(reader, p)
if err != nil {
if err != io.EOF && err != io.ErrUnexpectedEOF { // catastrophic error
ch <- part{
ReadSeeker: nil,
Err: err,
Num: 0,
}
return
}
}
num++
md5SumBytes := md5.Sum(p[0:n])
sp := skipPart{
partNumber: num,
md5sum: md5SumBytes[:],
}
if isPartNumberUploaded(sp, skipParts) {
continue
}
ch <- part{
MD5Sum: md5SumBytes[:],
ReadSeeker: bytes.NewReader(p[0:n]),
Err: nil,
Len: int64(n),
Num: num,
}
}
}
// to verify if partNumber is part of the skip part list
func isPartNumberUploaded(part skipPart, skipParts []skipPart) bool {
for _, p := range skipParts {
if p.partNumber == part.partNumber && bytes.Equal(p.md5sum, part.md5sum) {
return true
}
}
return false
}
+115
View File
@@ -0,0 +1,115 @@
/*
* 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 (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"encoding/xml"
"io"
"strings"
"time"
)
// decoder provides a unified decoding method interface
type decoder interface {
Decode(v interface{}) error
}
// acceptTypeDecoder provide decoded value in given acceptType
func acceptTypeDecoder(body io.Reader, acceptType string, v interface{}) error {
var d decoder
switch {
case acceptType == "application/xml":
d = xml.NewDecoder(body)
case acceptType == "application/json":
d = json.NewDecoder(body)
default:
d = xml.NewDecoder(body)
}
return d.Decode(v)
}
// sum256Reader calculate sha256 sum for an input read seeker
func sum256Reader(reader io.ReadSeeker) ([]byte, error) {
h := sha256.New()
var err error
start, _ := reader.Seek(0, 1)
defer reader.Seek(start, 0)
for err == nil {
length := 0
byteBuffer := make([]byte, 1024*1024)
length, err = reader.Read(byteBuffer)
byteBuffer = byteBuffer[0:length]
h.Write(byteBuffer)
}
if err != io.EOF {
return nil, err
}
return h.Sum(nil), nil
}
// sum256 calculate sha256 sum for an input byte array
func sum256(data []byte) []byte {
hash := sha256.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)
hash.Write(data)
return hash.Sum(nil)
}
// getSigningKey hmac seed to calculate final signature
func getSigningKey(secret, region string, t time.Time) []byte {
date := sumHMAC([]byte("AWS4"+secret), []byte(t.Format(yyyymmdd)))
regionbytes := sumHMAC(date, []byte(region))
service := sumHMAC(regionbytes, []byte("s3"))
signingKey := sumHMAC(service, []byte("aws4_request"))
return signingKey
}
// getSignature final signature in hexadecimal form
func getSignature(signingKey []byte, stringToSign string) string {
return hex.EncodeToString(sumHMAC(signingKey, []byte(stringToSign)))
}
// getScope generate a string of a specific date, an AWS region, and a service
func getScope(region string, t time.Time) string {
scope := strings.Join([]string{
t.Format(yyyymmdd),
region,
"s3",
"aws4_request",
}, "/")
return scope
}
// getCredential generate a credential string
func getCredential(accessKeyID, region string, t time.Time) string {
scope := getScope(region, t)
return accessKeyID + "/" + scope
}
+181
View File
@@ -0,0 +1,181 @@
/*
* 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/xml"
"time"
)
// listAllMyBucketsResult container for listBuckets response
type listAllMyBucketsResult struct {
// Container for one or more buckets.
Buckets struct {
Bucket []BucketStat
}
Owner owner
}
// owner container for bucket owner information
type owner struct {
DisplayName string
ID string
}
// commonPrefix container for prefix response
type commonPrefix struct {
Prefix string
}
// listBucketResult container for listObjects response
type listBucketResult struct {
CommonPrefixes []commonPrefix // A response can contain CommonPrefixes only if you have specified a delimiter
Contents []ObjectStat // Metadata about each object returned
Delimiter string
// Encoding type used to encode object keys in the response.
EncodingType string
// A flag that indicates whether or not ListObjects returned all of the results
// that satisfied the search criteria.
IsTruncated bool
Marker string
MaxKeys int64
Name string
// When response is truncated (the IsTruncated element value in the response
// is true), you can use the key name in this field as marker in the subsequent
// request to get next set of objects. Object storage lists objects in alphabetical
// order Note: This element is returned only if you have delimiter request parameter
// specified. If response does not include the NextMaker and it is truncated,
// you can use the value of the last Key in the response as the marker in the
// subsequent request to get the next set of object keys.
NextMarker string
Prefix string
}
// listMultipartUploadsResult container for ListMultipartUploads response
type listMultipartUploadsResult struct {
Bucket string
KeyMarker string
UploadIDMarker string `xml:"UploadIdMarker"`
NextKeyMarker string
NextUploadIDMarker string `xml:"NextUploadIdMarker"`
EncodingType string
MaxUploads int64
IsTruncated bool
Uploads []ObjectMultipartStat `xml:"Upload"`
Prefix string
Delimiter string
CommonPrefixes []commonPrefix // A response can contain CommonPrefixes only if you specify a delimiter
}
// initiator container for who initiated multipart upload
type initiator struct {
ID string
DisplayName string
}
// partMetadata container for particular part of an object
type partMetadata struct {
// Part number identifies the part.
PartNumber int
// Date and time the part was uploaded.
LastModified time.Time
// Entity tag returned when the part was uploaded, usually md5sum of the part
ETag string
// Size of the uploaded part data.
Size int64
}
// listObjectPartsResult container for ListObjectParts response.
type listObjectPartsResult struct {
Bucket string
Key string
UploadID string `xml:"UploadId"`
Initiator initiator
Owner owner
StorageClass string
PartNumberMarker int
NextPartNumberMarker int
MaxParts int
// Indicates whether the returned list of parts is truncated.
IsTruncated bool
Parts []partMetadata `xml:"Part"`
EncodingType string
}
// initiateMultipartUploadResult container for InitiateMultiPartUpload response.
type initiateMultipartUploadResult struct {
Bucket string
Key string
UploadID string `xml:"UploadId"`
}
// completeMultipartUploadResult container for completed multipart upload response.
type completeMultipartUploadResult struct {
Location string
Bucket string
Key string
ETag string
}
// completePart sub container lists individual part numbers and their md5sum, part of completeMultipartUpload.
type completePart struct {
XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ Part" json:"-"`
// Part number identifies the part.
PartNumber int
ETag string
}
// completeMultipartUpload container for completing multipart upload
type completeMultipartUpload struct {
XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ CompleteMultipartUpload" json:"-"`
Parts []completePart `xml:"Part"`
}
// createBucketConfiguration container for bucket configuration
type createBucketConfiguration struct {
XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ CreateBucketConfiguration" json:"-"`
Location string `xml:"LocationConstraint"`
}
type grant struct {
Grantee struct {
ID string
DisplayName string
EmailAddress string
Type string
URI string
}
Permission string
}
type accessControlPolicy struct {
AccessControlList struct {
Grant []grant
}
Owner owner
}
+168
View File
@@ -0,0 +1,168 @@
/*
* 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"
"encoding/xml"
"io"
"regexp"
"strings"
"unicode/utf8"
)
/* **** SAMPLE ERROR RESPONSE ****
<?xml version="1.0" encoding="UTF-8"?>
<Error>
<Code>AccessDenied</Code>
<Message>Access Denied</Message>
<Resource>/mybucket/myphoto.jpg</Resource>
<RequestId>F19772218238A85A</RequestId>
<HostId>GuWkjyviSiGHizehqpmsD1ndz5NClSP19DOT+s2mv7gXGQ8/X1lhbDGiIJEXpGFD</HostId>
</Error>
*/
// ErrorResponse is the type error returned by some API operations.
type ErrorResponse struct {
XMLName xml.Name `xml:"Error" json:"-"`
Code string
Message string
Resource string
RequestID string `xml:"RequestId"`
HostID string `xml:"HostId"`
}
// ToErrorResponse returns parsed ErrorResponse struct, if input is nil or not ErrorResponse return value is nil
// this fuction is useful when some one wants to dig deeper into the error structures over the network.
//
// for example:
//
// import s3 "github.com/minio/minio-go"
// ...
// ...
// ..., err := s3.GetObject(...)
// if err != nil {
// resp := s3.ToErrorResponse(err)
// fmt.Println(resp.ToXML())
// }
// ...
// ...
func ToErrorResponse(err error) *ErrorResponse {
switch err := err.(type) {
case ErrorResponse:
return &err
default:
return nil
}
}
// ToXML send raw xml marshalled as string
func (e ErrorResponse) ToXML() string {
b, err := xml.Marshal(&e)
if err != nil {
panic(err)
}
return string(b)
}
// ToJSON send raw json marshalled as string
func (e ErrorResponse) ToJSON() string {
b, err := json.Marshal(&e)
if err != nil {
panic(err)
}
return string(b)
}
// Error formats HTTP error string
func (e ErrorResponse) Error() string {
return e.Message
}
// BodyToErrorResponse returns a new encoded ErrorResponse structure
func BodyToErrorResponse(errBody io.Reader, acceptType string) error {
var errorResponse ErrorResponse
err := acceptTypeDecoder(errBody, acceptType, &errorResponse)
if err != nil {
return err
}
return errorResponse
}
// invalidBucketToError - invalid bucket to errorResponse
func invalidBucketError(bucket string) error {
// verify bucket name in accordance with
// - http://docs.aws.amazon.com/AmazonS3/latest/dev/UsingBucket.html
isValidBucket := func(bucket string) bool {
if len(bucket) < 3 || len(bucket) > 63 {
return false
}
if bucket[0] == '.' || bucket[len(bucket)-1] == '.' {
return false
}
if match, _ := regexp.MatchString("\\.\\.", bucket); match == true {
return false
}
// We don't support buckets with '.' in them
match, _ := regexp.MatchString("^[a-zA-Z][a-zA-Z0-9\\-]+[a-zA-Z0-9]$", bucket)
return match
}
if !isValidBucket(strings.TrimSpace(bucket)) {
// no resource since bucket is empty string
errorResponse := ErrorResponse{
Code: "InvalidBucketName",
Message: "The specified bucket is not valid.",
RequestID: "minio",
}
return errorResponse
}
return nil
}
// invalidObjectError invalid object name to errorResponse
func invalidObjectError(object string) error {
if strings.TrimSpace(object) == "" || object == "" {
// no resource since object name is empty
errorResponse := ErrorResponse{
Code: "NoSuchKey",
Message: "The specified key does not exist.",
RequestID: "minio",
}
return errorResponse
}
return nil
}
// invalidArgumentError invalid argument to errorResponse
func invalidArgumentError(arg string) error {
errorResponse := ErrorResponse{
Code: "InvalidArgument",
Message: "Invalid Argument",
RequestID: "minio",
}
if strings.TrimSpace(arg) == "" || arg == "" {
// no resource since arg is empty string
return errorResponse
}
if !utf8.ValidString(arg) {
// add resource to reply back with invalid string
errorResponse.Resource = arg
return errorResponse
}
return nil
}
@@ -0,0 +1,40 @@
// +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() {
config := minio.Config{
Endpoint: "https://play.minio.io:9000",
}
s3Client, err := minio.New(config)
if err != nil {
log.Fatalln(err)
}
err = s3Client.BucketExists("mybucket")
if err != nil {
log.Fatalln(err)
}
log.Println("Success")
}
@@ -0,0 +1,41 @@
// +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() {
config := minio.Config{
Endpoint: "https://play.minio.io:9000",
}
s3Client, err := minio.New(config)
if err != nil {
log.Fatalln(err)
}
acl, err := s3Client.GetBucketACL("mybucket")
if err != nil {
log.Fatalln(err)
}
log.Println(acl)
}
@@ -0,0 +1,51 @@
// +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 (
"io"
"log"
"os"
"github.com/minio/minio-go"
)
func main() {
config := minio.Config{
Endpoint: "https://play.minio.io:9000",
}
s3Client, err := minio.New(config)
if err != nil {
log.Fatalln(err)
}
reader, stat, err := s3Client.GetObject("mybucket", "myobject")
if err != nil {
log.Fatalln(err)
}
localfile, err := os.Create("testfile")
if err != nil {
log.Fatalln(err)
}
defer localfile.Close()
if _, err = io.CopyN(localfile, reader, stat.Size); err != nil {
log.Fatalln(err)
}
}
@@ -0,0 +1,51 @@
// +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 (
"io"
"log"
"os"
"github.com/minio/minio-go"
)
func main() {
config := minio.Config{
Endpoint: "https://play.minio.io:9000",
}
s3Client, err := minio.New(config)
if err != nil {
log.Fatalln(err)
}
reader, stat, err := s3Client.GetPartialObject("mybucket", "myobject", 0, 10)
if err != nil {
log.Fatalln(err)
}
localfile, err := os.Create("testfile")
if err != nil {
log.Fatalln(err)
}
defer localfile.Close()
if _, err = io.CopyN(localfile, reader, stat.Size); err != nil {
log.Fatalln(err)
}
}
@@ -0,0 +1,41 @@
// +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() {
config := minio.Config{
Endpoint: "https://play.minio.io:9000",
}
s3Client, err := minio.New(config)
if err != nil {
log.Fatalln(err)
}
for bucket := range s3Client.ListBuckets() {
if bucket.Err != nil {
log.Fatalln(bucket.Err)
}
log.Println(bucket.Stat)
}
}
@@ -0,0 +1,44 @@
// +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() {
config := minio.Config{
AccessKeyID: "YOUR-ACCESS-KEY-HERE",
SecretAccessKey: "YOUR-PASSWORD-HERE",
Endpoint: "https://play.minio.io:9000",
}
s3Client, err := minio.New(config)
if err != nil {
log.Fatalln(err)
}
// Recursive
for multipartObject := range s3Client.ListIncompleteUploads("mybucket", "myobject", true) {
if multipartObject.Err != nil {
log.Fatalln(multipartObject.Err)
}
log.Println(multipartObject)
}
}
@@ -0,0 +1,41 @@
// +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() {
config := minio.Config{
Endpoint: "https://play.minio.io:9000",
}
s3Client, err := minio.New(config)
if err != nil {
log.Fatalln(err)
}
for object := range s3Client.ListObjects("mybucket", "", true) {
if object.Err != nil {
log.Fatalln(object.Err)
}
log.Println(object.Stat)
}
}
@@ -0,0 +1,40 @@
// +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() {
config := minio.Config{
Endpoint: "https://play.minio.io:9000",
}
s3Client, err := minio.New(config)
if err != nil {
log.Fatalln(err)
}
err = s3Client.MakeBucket("mybucket", "")
if err != nil {
log.Fatalln(err)
}
log.Println("Success")
}
@@ -0,0 +1,52 @@
// +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() {
config := minio.Config{
Endpoint: "https://play.minio.io:9000",
}
s3Client, err := minio.New(config)
if err != nil {
log.Fatalln(err)
}
object, err := os.Open("testfile")
if err != nil {
log.Fatalln(err)
}
defer object.Close()
objectInfo, err := object.Stat()
if err != nil {
object.Close()
log.Fatalln(err)
}
err = s3Client.PutObject("mybucket", "myobject", "application/octet-stream", objectInfo.Size(), object)
if err != nil {
log.Fatalln(err)
}
}
@@ -0,0 +1,43 @@
// +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() {
config := minio.Config{
AccessKeyID: "YOUR-ACCESS-KEY-HERE",
SecretAccessKey: "YOUR-PASSWORD-HERE",
Endpoint: "https://play.minio.io:9000",
}
s3Client, err := minio.New(config)
if err != nil {
log.Fatalln(err)
}
err = s3Client.RemoveBucket("mybucket")
if err != nil {
log.Fatalln(err)
}
log.Println("Success")
}
@@ -0,0 +1,41 @@
// +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() {
config := minio.Config{
Endpoint: "https://play.minio.io:9000",
}
s3Client, err := minio.New(config)
if err != nil {
log.Fatalln(err)
}
for err := range s3Client.RemoveIncompleteUpload("mybucket", "myobject") {
if err != nil {
log.Fatalln(err)
}
}
log.Println("Success")
}
@@ -0,0 +1,42 @@
// +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() {
config := minio.Config{
AccessKeyID: "YOUR-ACCESS-KEY-HERE",
SecretAccessKey: "YOUR-PASSWORD-HERE",
Endpoint: "https://play.minio.io:9000",
}
s3Client, err := minio.New(config)
if err != nil {
log.Fatalln(err)
}
err = s3Client.RemoveObject("mybucket", "myobject")
if err != nil {
log.Fatalln(err)
}
log.Println("Success")
}
@@ -0,0 +1,40 @@
// +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() {
config := minio.Config{
Endpoint: "https://play.minio.io:9000",
}
s3Client, err := minio.New(config)
if err != nil {
log.Fatalln(err)
}
err = s3Client.SetBucketACL("mybucket", minio.BucketACL("public-read-write"))
if err != nil {
log.Fatalln(err)
}
}
@@ -0,0 +1,40 @@
// +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() {
config := minio.Config{
Endpoint: "https://play.minio.io:9000",
}
s3Client, err := minio.New(config)
if err != nil {
log.Fatalln(err)
}
stat, err := s3Client.StatObject("mybucket", "myobject")
if err != nil {
log.Fatalln(err)
}
log.Println(stat)
}
@@ -0,0 +1,42 @@
// +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() {
config := minio.Config{
AccessKeyID: "YOUR-ACCESS-KEY-HERE",
SecretAccessKey: "YOUR-PASSWORD-HERE",
Endpoint: "https://s3.amazonaws.com",
}
s3Client, err := minio.New(config)
if err != nil {
log.Fatalln(err)
}
err = s3Client.BucketExists("mybucket")
if err != nil {
log.Fatalln(err)
}
log.Println("Success")
}
@@ -0,0 +1,43 @@
// +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() {
config := minio.Config{
AccessKeyID: "YOUR-ACCESS-KEY-HERE",
SecretAccessKey: "YOUR-PASSWORD-HERE",
Endpoint: "https://s3.amazonaws.com",
}
s3Client, err := minio.New(config)
if err != nil {
log.Fatalln(err)
}
acl, err := s3Client.GetBucketACL("mybucket")
if err != nil {
log.Fatalln(err)
}
log.Println(acl)
}
@@ -0,0 +1,53 @@
// +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 (
"io"
"log"
"os"
"github.com/minio/minio-go"
)
func main() {
config := minio.Config{
AccessKeyID: "YOUR-ACCESS-KEY-HERE",
SecretAccessKey: "YOUR-PASSWORD-HERE",
Endpoint: "https://s3.amazonaws.com",
}
s3Client, err := minio.New(config)
if err != nil {
log.Fatalln(err)
}
reader, stat, err := s3Client.GetObject("mybucket", "myobject")
if err != nil {
log.Fatalln(err)
}
localfile, err := os.Create("testfile")
if err != nil {
log.Fatalln(err)
}
defer localfile.Close()
if _, err = io.CopyN(localfile, reader, stat.Size); err != nil {
log.Fatalln(err)
}
}
@@ -0,0 +1,53 @@
// +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 (
"io"
"log"
"os"
"github.com/minio/minio-go"
)
func main() {
config := minio.Config{
AccessKeyID: "YOUR-ACCESS-KEY-HERE",
SecretAccessKey: "YOUR-PASSWORD-HERE",
Endpoint: "https://s3.amazonaws.com",
}
s3Client, err := minio.New(config)
if err != nil {
log.Fatalln(err)
}
reader, stat, err := s3Client.GetPartialObject("mybucket", "myobject", 0, 10)
if err != nil {
log.Fatalln(err)
}
localfile, err := os.Create("testfile")
if err != nil {
log.Fatalln(err)
}
defer localfile.Close()
if _, err = io.CopyN(localfile, reader, stat.Size); err != nil {
log.Fatalln(err)
}
}
@@ -0,0 +1,43 @@
// +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() {
config := minio.Config{
AccessKeyID: "YOUR-ACCESS-KEY-HERE",
SecretAccessKey: "YOUR-PASSWORD-HERE",
Endpoint: "https://s3.amazonaws.com",
}
s3Client, err := minio.New(config)
if err != nil {
log.Fatalln(err)
}
for bucket := range s3Client.ListBuckets() {
if bucket.Err != nil {
log.Fatalln(bucket.Err)
}
log.Println(bucket.Stat)
}
}
@@ -0,0 +1,44 @@
// +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() {
config := minio.Config{
AccessKeyID: "YOUR-ACCESS-KEY-HERE",
SecretAccessKey: "YOUR-PASSWORD-HERE",
Endpoint: "https://s3.amazonaws.com",
}
s3Client, err := minio.New(config)
if err != nil {
log.Fatalln(err)
}
// Recursive
for multipartObject := range s3Client.ListIncompleteUploads("mybucket", "myobject", true) {
if multipartObject.Err != nil {
log.Fatalln(multipartObject.Err)
}
log.Println(multipartObject)
}
}
@@ -0,0 +1,43 @@
// +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() {
config := minio.Config{
AccessKeyID: "YOUR-ACCESS-KEY-HERE",
SecretAccessKey: "YOUR-PASSWORD-HERE",
Endpoint: "https://s3.amazonaws.com",
}
s3Client, err := minio.New(config)
if err != nil {
log.Fatalln(err)
}
for object := range s3Client.ListObjects("mybucket", "", true) {
if object.Err != nil {
log.Fatalln(object.Err)
}
log.Println(object.Stat)
}
}
@@ -0,0 +1,42 @@
// +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() {
config := minio.Config{
AccessKeyID: "YOUR-ACCESS-KEY-HERE",
SecretAccessKey: "YOUR-PASSWORD-HERE",
Endpoint: "https://s3.amazonaws.com",
}
s3Client, err := minio.New(config)
if err != nil {
log.Fatalln(err)
}
err = s3Client.MakeBucket("mybucket", "")
if err != nil {
log.Fatalln(err)
}
log.Println("Success")
}
@@ -0,0 +1,43 @@
// +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() {
config := minio.Config{
AccessKeyID: "YOUR-ACCESS-KEY-HERE",
SecretAccessKey: "YOUR-PASSWORD-HERE",
Endpoint: "https://s3.amazonaws.com",
}
s3Client, err := minio.New(config)
if err != nil {
log.Fatalln(err)
}
string, err := s3Client.PresignedGetObject("mybucket", "myobject", time.Duration(1000)*time.Second)
if err != nil {
log.Fatalln(err)
}
log.Println(string)
}
@@ -0,0 +1,54 @@
// +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() {
config := minio.Config{
AccessKeyID: "YOUR-ACCESS-KEY-HERE",
SecretAccessKey: "YOUR-PASSWORD-HERE",
Endpoint: "https://s3.amazonaws.com",
}
s3Client, err := minio.New(config)
if err != nil {
log.Fatalln(err)
}
policy := minio.NewPostPolicy()
policy.SetKey("myobject")
policy.SetBucket("mybucket")
policy.SetExpires(time.Now().UTC().AddDate(0, 0, 10)) // expires in 10 days
m, err := s3Client.PresignedPostPolicy(policy)
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("curl ")
for k, v := range m {
fmt.Printf("-F %s=%s ", k, v)
}
fmt.Printf("-F file=@/etc/bashrc ")
fmt.Printf(config.Endpoint + "/mybucket\n")
}
@@ -0,0 +1,43 @@
// +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() {
config := minio.Config{
AccessKeyID: "YOUR-ACCESS-KEY-HERE",
SecretAccessKey: "YOUR-PASSWORD-HERE",
Endpoint: "https://s3.amazonaws.com",
}
s3Client, err := minio.New(config)
if err != nil {
log.Fatalln(err)
}
string, err := s3Client.PresignedPutObject("mybucket", "myobject", time.Duration(1000)*time.Second)
if err != nil {
log.Fatalln(err)
}
log.Println(string)
}
@@ -0,0 +1,54 @@
// +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() {
config := minio.Config{
AccessKeyID: "YOUR-ACCESS-KEY-HERE",
SecretAccessKey: "YOUR-PASSWORD-HERE",
Endpoint: "https://s3.amazonaws.com",
}
s3Client, err := minio.New(config)
if err != nil {
log.Fatalln(err)
}
object, err := os.Open("testfile")
if err != nil {
log.Fatalln(err)
}
defer object.Close()
objectInfo, err := object.Stat()
if err != nil {
object.Close()
log.Fatalln(err)
}
err = s3Client.PutObject("mybucket", "myobject", "application/octet-stream", objectInfo.Size(), object)
if err != nil {
log.Fatalln(err)
}
}
@@ -0,0 +1,43 @@
// +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() {
config := minio.Config{
AccessKeyID: "YOUR-ACCESS-KEY-HERE",
SecretAccessKey: "YOUR-PASSWORD-HERE",
Endpoint: "https://s3.amazonaws.com",
}
s3Client, err := minio.New(config)
if err != nil {
log.Fatalln(err)
}
err = s3Client.RemoveBucket("mybucket")
if err != nil {
log.Fatalln(err)
}
log.Println("Success")
}
@@ -0,0 +1,43 @@
// +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() {
config := minio.Config{
AccessKeyID: "YOUR-ACCESS-KEY-HERE",
SecretAccessKey: "YOUR-PASSWORD-HERE",
Endpoint: "https://s3.amazonaws.com",
}
s3Client, err := minio.New(config)
if err != nil {
log.Fatalln(err)
}
for err := range s3Client.RemoveIncompleteUpload("mybucket", "myobject") {
if err != nil {
log.Fatalln(err)
}
}
log.Println("Success")
}
@@ -0,0 +1,42 @@
// +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() {
config := minio.Config{
AccessKeyID: "YOUR-ACCESS-KEY-HERE",
SecretAccessKey: "YOUR-PASSWORD-HERE",
Endpoint: "https://s3.amazonaws.com",
}
s3Client, err := minio.New(config)
if err != nil {
log.Fatalln(err)
}
err = s3Client.RemoveObject("mybucket", "myobject")
if err != nil {
log.Fatalln(err)
}
log.Println("Success")
}
@@ -0,0 +1,42 @@
// +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() {
config := minio.Config{
AccessKeyID: "YOUR-ACCESS-KEY-HERE",
SecretAccessKey: "YOUR-PASSWORD-HERE",
Endpoint: "https://s3.amazonaws.com",
}
s3Client, err := minio.New(config)
if err != nil {
log.Fatalln(err)
}
err = s3Client.SetBucketACL("mybucket", minio.BucketACL("public-read-write"))
if err != nil {
log.Fatalln(err)
}
}
@@ -0,0 +1,42 @@
// +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() {
config := minio.Config{
AccessKeyID: "YOUR-ACCESS-KEY-HERE",
SecretAccessKey: "YOUR-PASSWORD-HERE",
Endpoint: "https://s3.amazonaws.com",
}
s3Client, err := minio.New(config)
if err != nil {
log.Fatalln(err)
}
stat, err := s3Client.StatObject("mybucket", "myobject")
if err != nil {
log.Fatalln(err)
}
log.Println(stat)
}
+152
View File
@@ -0,0 +1,152 @@
package minio
import (
"encoding/base64"
"errors"
"fmt"
"strings"
"time"
)
// expirationDateFormat date format for expiration key in json policy
const expirationDateFormat = "2006-01-02T15:04:05.999Z"
// Policy explanation: http://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-HTTPPOSTConstructPolicy.html
type policy struct {
matchType string
key string
value string
}
// PostPolicy provides strict static type conversion and validation for Amazon S3's POST policy JSON string.
type PostPolicy struct {
expiration time.Time // expiration date and time of the POST policy.
policies []policy
contentLengthRange struct {
min int
max int
}
// Post form data
formData map[string]string
}
// NewPostPolicy instantiate new post policy
func NewPostPolicy() *PostPolicy {
p := &PostPolicy{}
p.policies = make([]policy, 0)
p.formData = make(map[string]string)
return p
}
// SetExpires expiration time
func (p *PostPolicy) SetExpires(t time.Time) error {
if t.IsZero() {
return errors.New("time input invalid")
}
p.expiration = t
return nil
}
// SetKey Object name
func (p *PostPolicy) SetKey(key string) error {
if strings.TrimSpace(key) == "" || key == "" {
return errors.New("key invalid")
}
policy := policy{"eq", "$key", key}
p.policies = append(p.policies, policy)
p.formData["key"] = key
return nil
}
// SetKeyStartsWith Object name that can start with
func (p *PostPolicy) SetKeyStartsWith(keyStartsWith string) error {
if strings.TrimSpace(keyStartsWith) == "" || keyStartsWith == "" {
return errors.New("key-starts-with invalid")
}
policy := policy{"starts-with", "$key", keyStartsWith}
p.policies = append(p.policies, policy)
p.formData["key"] = keyStartsWith
return nil
}
// SetBucket bucket name
func (p *PostPolicy) SetBucket(bucket string) error {
if strings.TrimSpace(bucket) == "" || bucket == "" {
return errors.New("bucket invalid")
}
policy := policy{"eq", "$bucket", bucket}
p.policies = append(p.policies, policy)
p.formData["bucket"] = bucket
return nil
}
// SetContentType content-type
func (p *PostPolicy) SetContentType(contentType string) error {
if strings.TrimSpace(contentType) == "" || contentType == "" {
return errors.New("contentType invalid")
}
policy := policy{"eq", "$Content-Type", contentType}
if err := p.addNewPolicy(policy); err != nil {
return err
}
p.formData["Content-Type"] = contentType
return nil
}
// SetContentLength - set new min and max content legnth condition
func (p *PostPolicy) SetContentLength(min, max int) error {
if min > max {
return errors.New("minimum cannot be bigger than maximum")
}
if min < 0 {
return errors.New("minimum cannot be negative")
}
if max < 0 {
return errors.New("maximum cannot be negative")
}
p.contentLengthRange.min = min
p.contentLengthRange.max = max
return nil
}
// addNewPolicy - internal helper to validate adding new policies
func (p *PostPolicy) addNewPolicy(po policy) error {
if po.matchType == "" || po.key == "" || po.value == "" {
return errors.New("policy invalid")
}
p.policies = append(p.policies, po)
return nil
}
// Stringer interface for printing in pretty manner
func (p PostPolicy) String() string {
return string(p.marshalJSON())
}
// marshalJSON provides Marshalled JSON
func (p PostPolicy) marshalJSON() []byte {
expirationStr := `"expiration":"` + p.expiration.Format(expirationDateFormat) + `"`
var policiesStr string
policies := []string{}
for _, po := range p.policies {
policies = append(policies, fmt.Sprintf("[\"%s\",\"%s\",\"%s\"]", po.matchType, po.key, po.value))
}
if p.contentLengthRange.min != 0 || p.contentLengthRange.max != 0 {
policies = append(policies, fmt.Sprintf("[\"content-length-range\", %d, %d]",
p.contentLengthRange.min, p.contentLengthRange.max))
}
if len(policies) > 0 {
policiesStr = `"conditions":[` + strings.Join(policies, ",") + "]"
}
retStr := "{"
retStr = retStr + expirationStr + ","
retStr = retStr + policiesStr
retStr = retStr + "}"
return []byte(retStr)
}
// base64 produces base64 of PostPolicy's Marshalled json
func (p PostPolicy) base64() string {
return base64.StdEncoding.EncodeToString(p.marshalJSON())
}
+498
View File
@@ -0,0 +1,498 @@
/*
* 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/hex"
"errors"
"io"
"io/ioutil"
"net/http"
"regexp"
"sort"
"strings"
"time"
"unicode/utf8"
)
// operation - rest operation
type operation struct {
HTTPServer string
HTTPMethod string
HTTPPath string
}
// request - a http request
type request struct {
req *http.Request
config *Config
body io.ReadSeeker
expires string
}
const (
authHeader = "AWS4-HMAC-SHA256"
iso8601DateFormat = "20060102T150405Z"
yyyymmdd = "20060102"
)
///
/// Excerpts from @lsegal - https://github.com/aws/aws-sdk-js/issues/659#issuecomment-120477258
///
/// User-Agent:
///
/// This is ignored from signing because signing this causes problems with generating pre-signed URLs
/// (that are executed by other agents) or when customers pass requests through proxies, which may
/// modify the user-agent.
///
/// Content-Length:
///
/// This is ignored from signing because generating a pre-signed URL should not provide a content-length
/// constraint, specifically when vending a S3 pre-signed PUT URL. The corollary to this is that when
/// sending regular requests (non-pre-signed), the signature contains a checksum of the body, which
/// implicitly validates the payload length (since changing the number of bytes would change the checksum)
/// and therefore this header is not valuable in the signature.
///
/// Content-Type:
///
/// Signing this header causes quite a number of problems in browser environments, where browsers
/// like to modify and normalize the content-type header in different ways. There is more information
/// on this in https://github.com/aws/aws-sdk-js/issues/244. Avoiding this field simplifies logic
/// and reduces the possibility of future bugs
///
/// Authorization:
///
/// Is skipped for obvious reasons
///
var ignoredHeaders = map[string]bool{
"Authorization": true,
"Content-Type": true,
"Content-Length": true,
"User-Agent": true,
}
// getURLEncodedPath 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
// non english characters cannot be parsed due to the nature in which url.Encode() is written
//
// This function on the other hand is a direct replacement for url.Encode() technique to support
// pretty much every UTF-8 character.
func getURLEncodedPath(pathName string) string {
// if object matches reserved string, no need to encode them
reservedNames := regexp.MustCompile("^[a-zA-Z0-9-_.~/]+$")
if reservedNames.MatchString(pathName) {
return pathName
}
var encodedPathname string
for _, s := range pathName {
if 'A' <= s && s <= 'Z' || 'a' <= s && s <= 'z' || '0' <= s && s <= '9' { // §2.3 Unreserved characters (mark)
encodedPathname = encodedPathname + string(s)
continue
}
switch s {
case '-', '_', '.', '~', '/': // §2.3 Unreserved characters (mark)
encodedPathname = encodedPathname + string(s)
continue
default:
len := utf8.RuneLen(s)
if len < 0 {
// if utf8 cannot convert return the same string as is
return pathName
}
u := make([]byte, len)
utf8.EncodeRune(u, s)
for _, r := range u {
hex := hex.EncodeToString([]byte{r})
encodedPathname = encodedPathname + "%" + strings.ToUpper(hex)
}
}
}
return encodedPathname
}
func path2BucketAndObject(path string) (bucketName, objectName string) {
pathSplits := strings.SplitN(path, "?", 2)
splits := strings.SplitN(pathSplits[0], separator, 3)
switch len(splits) {
case 0, 1:
bucketName = ""
objectName = ""
case 2:
bucketName = splits[1]
objectName = ""
case 3:
bucketName = splits[1]
objectName = splits[2]
}
return bucketName, objectName
}
// path2Object gives objectName from URL path
func path2Object(path string) (objectName string) {
_, objectName = path2BucketAndObject(path)
return
}
// path2Bucket gives bucketName from URL path
func path2Bucket(path string) (bucketName string) {
bucketName, _ = path2BucketAndObject(path)
return
}
// path2Query gives query part from URL path
func path2Query(path string) (query string) {
pathSplits := strings.SplitN(path, "?", 2)
if len(pathSplits) > 1 {
query = pathSplits[1]
}
return
}
func (op *operation) getRequestURL(config Config) (url string) {
// parse URL for the combination of HTTPServer + HTTPPath
url = op.HTTPServer + separator
if !config.isVirtualStyle {
url += path2Bucket(op.HTTPPath)
}
objectName := getURLEncodedPath(path2Object(op.HTTPPath))
queryPath := path2Query(op.HTTPPath)
if objectName == "" && queryPath != "" {
url += "?" + queryPath
return
}
if objectName != "" && queryPath == "" {
if strings.HasSuffix(url, separator) {
url += objectName
} else {
url += separator + objectName
}
return
}
if objectName != "" && queryPath != "" {
if strings.HasSuffix(url, separator) {
url += objectName + "?" + queryPath
} else {
url += separator + objectName + "?" + queryPath
}
}
return
}
func newPresignedRequest(op *operation, config *Config, expires string) (*request, error) {
// if no method default to POST
method := op.HTTPMethod
if method == "" {
method = "POST"
}
u := op.getRequestURL(*config)
// get a new HTTP request, for the requested method
req, err := http.NewRequest(method, u, nil)
if err != nil {
return nil, err
}
// set UserAgent
req.Header.Set("User-Agent", config.userAgent)
// set Accept header for response encoding style, if available
if config.AcceptType != "" {
req.Header.Set("Accept", config.AcceptType)
}
// save for subsequent use
r := new(request)
r.config = config
r.expires = expires
r.req = req
r.body = nil
return r, nil
}
// newUnauthenticatedRequest - instantiate a new unauthenticated request
func newUnauthenticatedRequest(op *operation, config *Config, body io.Reader) (*request, error) {
// if no method default to POST
method := op.HTTPMethod
if method == "" {
method = "POST"
}
u := op.getRequestURL(*config)
// get a new HTTP request, for the requested method
req, err := http.NewRequest(method, u, nil)
if err != nil {
return nil, err
}
// set UserAgent
req.Header.Set("User-Agent", config.userAgent)
// set Accept header for response encoding style, if available
if config.AcceptType != "" {
req.Header.Set("Accept", config.AcceptType)
}
// add body
switch {
case body == nil:
req.Body = nil
default:
req.Body = ioutil.NopCloser(body)
}
// save for subsequent use
r := new(request)
r.req = req
r.config = config
return r, nil
}
// newRequest - instantiate a new request
func newRequest(op *operation, config *Config, body io.ReadSeeker) (*request, error) {
// if no method default to POST
method := op.HTTPMethod
if method == "" {
method = "POST"
}
u := op.getRequestURL(*config)
// get a new HTTP request, for the requested method
req, err := http.NewRequest(method, u, nil)
if err != nil {
return nil, err
}
// set UserAgent
req.Header.Set("User-Agent", config.userAgent)
// set Accept header for response encoding style, if available
if config.AcceptType != "" {
req.Header.Set("Accept", config.AcceptType)
}
// add body
switch {
case body == nil:
req.Body = nil
default:
req.Body = ioutil.NopCloser(body)
}
// save for subsequent use
r := new(request)
r.config = config
r.req = req
r.body = body
return r, nil
}
// Do - start the request
func (r *request) Do() (resp *http.Response, err error) {
if r.config.AccessKeyID != "" && r.config.SecretAccessKey != "" {
r.SignV4()
}
transport := http.DefaultTransport
if r.config.Transport != nil {
transport = r.config.Transport
}
// do not use http.Client{}, while it may seem intuitive but the problem seems to be
// that http.Client{} internally follows redirects and there is no easier way to disable
// it from outside using a configuration parameter -
// this auto redirect causes complications in verifying subsequent errors
//
// The best is to use RoundTrip() directly, so the request comes back to the caller where
// we are going to handle such replies. And indeed that is the right thing to do here.
//
return transport.RoundTrip(r.req)
}
// Set - set additional headers if any
func (r *request) Set(key, value string) {
r.req.Header.Set(key, value)
}
// Get - get header values
func (r *request) Get(key string) string {
return r.req.Header.Get(key)
}
// getHashedPayload get the hexadecimal value of the SHA256 hash of the request payload
func (r *request) getHashedPayload() string {
hash := func() string {
switch {
case r.expires != "":
return "UNSIGNED-PAYLOAD"
case r.body == nil:
return hex.EncodeToString(sum256([]byte{}))
default:
sum256Bytes, _ := sum256Reader(r.body)
return hex.EncodeToString(sum256Bytes)
}
}
hashedPayload := hash()
if hashedPayload != "UNSIGNED-PAYLOAD" {
r.req.Header.Set("X-Amz-Content-Sha256", hashedPayload)
}
return hashedPayload
}
// getCanonicalHeaders generate a list of request headers with their values
func (r *request) getCanonicalHeaders() string {
var headers []string
vals := make(map[string][]string)
for k, vv := range r.req.Header {
if _, ok := ignoredHeaders[http.CanonicalHeaderKey(k)]; ok {
continue // ignored header
}
headers = append(headers, strings.ToLower(k))
vals[strings.ToLower(k)] = vv
}
headers = append(headers, "host")
sort.Strings(headers)
var buf bytes.Buffer
for _, k := range headers {
buf.WriteString(k)
buf.WriteByte(':')
switch {
case k == "host":
buf.WriteString(r.req.URL.Host)
fallthrough
default:
for idx, v := range vals[k] {
if idx > 0 {
buf.WriteByte(',')
}
buf.WriteString(v)
}
buf.WriteByte('\n')
}
}
return buf.String()
}
// getSignedHeaders generate a string i.e alphabetically sorted, semicolon-separated list of lowercase request header names
func (r *request) getSignedHeaders() string {
var headers []string
for k := range r.req.Header {
if _, ok := ignoredHeaders[http.CanonicalHeaderKey(k)]; ok {
continue // ignored header
}
headers = append(headers, strings.ToLower(k))
}
headers = append(headers, "host")
sort.Strings(headers)
return strings.Join(headers, ";")
}
// getCanonicalRequest generate a canonical request of style
//
// canonicalRequest =
// <HTTPMethod>\n
// <CanonicalURI>\n
// <CanonicalQueryString>\n
// <CanonicalHeaders>\n
// <SignedHeaders>\n
// <HashedPayload>
//
func (r *request) getCanonicalRequest(hashedPayload string) string {
r.req.URL.RawQuery = strings.Replace(r.req.URL.Query().Encode(), "+", "%20", -1)
canonicalRequest := strings.Join([]string{
r.req.Method,
getURLEncodedPath(r.req.URL.Path),
r.req.URL.RawQuery,
r.getCanonicalHeaders(),
r.getSignedHeaders(),
hashedPayload,
}, "\n")
return canonicalRequest
}
// getStringToSign a string based on selected query values
func (r *request) getStringToSign(canonicalRequest string, t time.Time) string {
stringToSign := authHeader + "\n" + t.Format(iso8601DateFormat) + "\n"
stringToSign = stringToSign + getScope(r.config.Region, t) + "\n"
stringToSign = stringToSign + hex.EncodeToString(sum256([]byte(canonicalRequest)))
return stringToSign
}
// Presign the request, in accordance with - http://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-query-string-auth.html
func (r *request) PreSignV4() (string, error) {
if r.config.AccessKeyID == "" && r.config.SecretAccessKey == "" {
return "", errors.New("presign requires accesskey and secretkey")
}
r.SignV4()
return r.req.URL.String(), nil
}
func (r *request) PostPresignSignature(policyBase64 string, t time.Time) string {
signingkey := getSigningKey(r.config.SecretAccessKey, r.config.Region, t)
signature := getSignature(signingkey, policyBase64)
return signature
}
// SignV4 the request before Do(), in accordance with - http://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-authenticating-requests.html
func (r *request) SignV4() {
query := r.req.URL.Query()
if r.expires != "" {
query.Set("X-Amz-Algorithm", authHeader)
}
t := time.Now().UTC()
// Add date if not present
if r.expires != "" {
query.Set("X-Amz-Date", t.Format(iso8601DateFormat))
query.Set("X-Amz-Expires", r.expires)
} else {
r.Set("X-Amz-Date", t.Format(iso8601DateFormat))
}
hashedPayload := r.getHashedPayload()
signedHeaders := r.getSignedHeaders()
if r.expires != "" {
query.Set("X-Amz-SignedHeaders", signedHeaders)
}
credential := getCredential(r.config.AccessKeyID, r.config.Region, t)
if r.expires != "" {
query.Set("X-Amz-Credential", credential)
r.req.URL.RawQuery = query.Encode()
}
canonicalRequest := r.getCanonicalRequest(hashedPayload)
stringToSign := r.getStringToSign(canonicalRequest, t)
signingKey := getSigningKey(r.config.SecretAccessKey, r.config.Region, t)
signature := getSignature(signingKey, stringToSign)
if r.expires != "" {
r.req.URL.RawQuery += "&X-Amz-Signature=" + signature
} else {
// final Authorization header
parts := []string{
authHeader + " Credential=" + credential,
"SignedHeaders=" + signedHeaders,
"Signature=" + signature,
}
auth := strings.Join(parts, ", ")
r.Set("Authorization", auth)
}
}