mirror of
https://github.com/minio/mc.git
synced 2025-11-12 01:02:26 +03:00
Add `share` command leveraging new PresignedURL API from minio-go
This commit is contained in:
4
Godeps/Godeps.json
generated
4
Godeps/Godeps.json
generated
@@ -29,8 +29,8 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/minio/minio-go",
|
"ImportPath": "github.com/minio/minio-go",
|
||||||
"Comment": "v0.2.0-19-g44dd766",
|
"Comment": "v0.2.0-21-gd9184e5",
|
||||||
"Rev": "44dd766b3563b2f95cc7cf03c4789b28254f3d8a"
|
"Rev": "d9184e5834efb7cd4a24df4d6ebab9b51dad4d9c"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/minio/minio/pkg/probe",
|
"ImportPath": "github.com/minio/minio/pkg/probe",
|
||||||
|
|||||||
36
Godeps/_workspace/src/github.com/minio/minio-go/api-v1.go
generated
vendored
36
Godeps/_workspace/src/github.com/minio/minio-go/api-v1.go
generated
vendored
@@ -524,6 +524,42 @@ func (a apiV1) putObject(bucket, object, contentType string, md5SumBytes []byte,
|
|||||||
return metadata, nil
|
return metadata, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a apiV1) presignedGetObjectRequest(bucket, object string, expires, offset, length int64) (*request, error) {
|
||||||
|
encodedObject, err := urlEncodeName(object)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
op := &operation{
|
||||||
|
HTTPServer: a.config.Endpoint,
|
||||||
|
HTTPMethod: "GET",
|
||||||
|
HTTPPath: separator + bucket + separator + encodedObject,
|
||||||
|
}
|
||||||
|
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 apiV1) 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(), nil
|
||||||
|
}
|
||||||
|
|
||||||
// getObjectRequest wrapper creates a new getObject request
|
// getObjectRequest wrapper creates a new getObject request
|
||||||
func (a apiV1) getObjectRequest(bucket, object string, offset, length int64) (*request, error) {
|
func (a apiV1) getObjectRequest(bucket, object string, offset, length int64) (*request, error) {
|
||||||
encodedObject, err := urlEncodeName(object)
|
encodedObject, err := urlEncodeName(object)
|
||||||
|
|||||||
28
Godeps/_workspace/src/github.com/minio/minio-go/api-v2.go
generated
vendored
28
Godeps/_workspace/src/github.com/minio/minio-go/api-v2.go
generated
vendored
@@ -36,6 +36,9 @@ type API interface {
|
|||||||
|
|
||||||
// Object Read/Write/Stat operations
|
// Object Read/Write/Stat operations
|
||||||
ObjectAPI
|
ObjectAPI
|
||||||
|
|
||||||
|
// Presigned API
|
||||||
|
PresignedAPI
|
||||||
}
|
}
|
||||||
|
|
||||||
// BucketAPI - bucket specific Read/Write/Stat interface
|
// BucketAPI - bucket specific Read/Write/Stat interface
|
||||||
@@ -65,6 +68,12 @@ type ObjectAPI interface {
|
|||||||
DropIncompleteUploads(bucket, object string) <-chan error
|
DropIncompleteUploads(bucket, object string) <-chan error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PresignedAPI - object specific for now
|
||||||
|
type PresignedAPI interface {
|
||||||
|
PresignedGetObject(bucket, object string, expires time.Duration) (string, error)
|
||||||
|
PresignedGetPartialObject(bucket, object string, expires time.Duration, offset, length int64) (string, error)
|
||||||
|
}
|
||||||
|
|
||||||
// BucketStatCh - bucket metadata over read channel
|
// BucketStatCh - bucket metadata over read channel
|
||||||
type BucketStatCh struct {
|
type BucketStatCh struct {
|
||||||
Stat BucketStat
|
Stat BucketStat
|
||||||
@@ -214,6 +223,25 @@ func New(config Config) (API, error) {
|
|||||||
|
|
||||||
/// Object operations
|
/// Object operations
|
||||||
|
|
||||||
|
/// Expires maximum is 7days - ie. 604800 and minimum is 1
|
||||||
|
|
||||||
|
// PresignedGetObject get a presigned URL to retrieve an object for third party apps
|
||||||
|
func (a apiV2) PresignedGetObject(bucket, object string, expires time.Duration) (string, error) {
|
||||||
|
expireSeconds := int64(expires / time.Second)
|
||||||
|
if expireSeconds < 1 || expireSeconds > 604800 {
|
||||||
|
return "", invalidArgumentError("")
|
||||||
|
}
|
||||||
|
return a.presignedGetObject(bucket, object, expireSeconds, 0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a apiV2) PresignedGetPartialObject(bucket, object string, expires time.Duration, offset, length int64) (string, error) {
|
||||||
|
expireSeconds := int64(expires / time.Second)
|
||||||
|
if expireSeconds < 1 || expireSeconds > 604800 {
|
||||||
|
return "", invalidArgumentError("")
|
||||||
|
}
|
||||||
|
return a.presignedGetObject(bucket, object, expireSeconds, offset, length)
|
||||||
|
}
|
||||||
|
|
||||||
// GetObject retrieve object
|
// GetObject retrieve object
|
||||||
|
|
||||||
// Downloads full object with no ranges, if you need ranges use GetPartialObject
|
// Downloads full object with no ranges, if you need ranges use GetPartialObject
|
||||||
|
|||||||
43
Godeps/_workspace/src/github.com/minio/minio-go/examples/s3/presignedgetobject.go
generated
vendored
Normal file
43
Godeps/_workspace/src/github.com/minio/minio-go/examples/s3/presignedgetobject.go
generated
vendored
Normal file
@@ -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)
|
||||||
|
}
|
||||||
109
Godeps/_workspace/src/github.com/minio/minio-go/request.go
generated
vendored
109
Godeps/_workspace/src/github.com/minio/minio-go/request.go
generated
vendored
@@ -37,9 +37,10 @@ type operation struct {
|
|||||||
|
|
||||||
// request - a http request
|
// request - a http request
|
||||||
type request struct {
|
type request struct {
|
||||||
req *http.Request
|
req *http.Request
|
||||||
config *Config
|
config *Config
|
||||||
body io.ReadSeeker
|
body io.ReadSeeker
|
||||||
|
expires string
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -157,9 +158,9 @@ func httpNewRequest(method, urlStr string, body io.Reader) (*http.Request, error
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
uEncoded.Opaque = separator + bucketName + separator + encodedObjectName
|
uEncoded.Opaque = "//" + uEncoded.Host + separator + bucketName + separator + encodedObjectName
|
||||||
} else {
|
} else {
|
||||||
uEncoded.Opaque = separator + bucketName
|
uEncoded.Opaque = "//" + uEncoded.Host + separator + bucketName
|
||||||
}
|
}
|
||||||
rc, ok := body.(io.ReadCloser)
|
rc, ok := body.(io.ReadCloser)
|
||||||
if !ok && body != nil {
|
if !ok && body != nil {
|
||||||
@@ -188,6 +189,39 @@ func httpNewRequest(method, urlStr string, body io.Reader) (*http.Request, error
|
|||||||
return req, nil
|
return req, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 := httpNewRequest(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
|
||||||
|
}
|
||||||
|
|
||||||
// newRequest - instantiate a new request
|
// newRequest - instantiate a new request
|
||||||
func newRequest(op *operation, config *Config, body io.ReadSeeker) (*request, error) {
|
func newRequest(op *operation, config *Config, body io.ReadSeeker) (*request, error) {
|
||||||
// if no method default to POST
|
// if no method default to POST
|
||||||
@@ -249,6 +283,14 @@ func (r *request) Do() (resp *http.Response, err error) {
|
|||||||
return transport.RoundTrip(r.req)
|
return transport.RoundTrip(r.req)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *request) SetQuery(key, value string) {
|
||||||
|
r.req.URL.Query().Set(key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *request) AddQuery(key, value string) {
|
||||||
|
r.req.URL.Query().Add(key, value)
|
||||||
|
}
|
||||||
|
|
||||||
// Set - set additional headers if any
|
// Set - set additional headers if any
|
||||||
func (r *request) Set(key, value string) {
|
func (r *request) Set(key, value string) {
|
||||||
r.req.Header.Set(key, value)
|
r.req.Header.Set(key, value)
|
||||||
@@ -263,6 +305,8 @@ func (r *request) Get(key string) string {
|
|||||||
func (r *request) getHashedPayload() string {
|
func (r *request) getHashedPayload() string {
|
||||||
hash := func() string {
|
hash := func() string {
|
||||||
switch {
|
switch {
|
||||||
|
case r.expires != "":
|
||||||
|
return "UNSIGNED-PAYLOAD"
|
||||||
case r.body == nil:
|
case r.body == nil:
|
||||||
return hex.EncodeToString(sum256([]byte{}))
|
return hex.EncodeToString(sum256([]byte{}))
|
||||||
default:
|
default:
|
||||||
@@ -271,7 +315,10 @@ func (r *request) getHashedPayload() string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
hashedPayload := hash()
|
hashedPayload := hash()
|
||||||
r.req.Header.Add("x-amz-content-sha256", hashedPayload)
|
r.req.Header.Set("X-Amz-Content-Sha256", hashedPayload)
|
||||||
|
if hashedPayload == "UNSIGNED-PAYLOAD" {
|
||||||
|
r.req.Header.Set("X-Amz-Content-Sha256", "")
|
||||||
|
}
|
||||||
return hashedPayload
|
return hashedPayload
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -336,9 +383,13 @@ func (r *request) getSignedHeaders() string {
|
|||||||
//
|
//
|
||||||
func (r *request) getCanonicalRequest(hashedPayload string) string {
|
func (r *request) getCanonicalRequest(hashedPayload string) string {
|
||||||
r.req.URL.RawQuery = strings.Replace(r.req.URL.Query().Encode(), "+", "%20", -1)
|
r.req.URL.RawQuery = strings.Replace(r.req.URL.Query().Encode(), "+", "%20", -1)
|
||||||
|
|
||||||
|
// get path URI from Opaque
|
||||||
|
uri := strings.Join(strings.Split(r.req.URL.Opaque, "/")[3:], "/")
|
||||||
|
|
||||||
canonicalRequest := strings.Join([]string{
|
canonicalRequest := strings.Join([]string{
|
||||||
r.req.Method,
|
r.req.Method,
|
||||||
r.req.URL.Opaque,
|
"/" + uri,
|
||||||
r.req.URL.RawQuery,
|
r.req.URL.RawQuery,
|
||||||
r.getCanonicalHeaders(),
|
r.getCanonicalHeaders(),
|
||||||
r.getSignedHeaders(),
|
r.getSignedHeaders(),
|
||||||
@@ -381,28 +432,52 @@ func (r *request) getSignature(signingKey []byte, stringToSign string) string {
|
|||||||
return hex.EncodeToString(sumHMAC(signingKey, []byte(stringToSign)))
|
return hex.EncodeToString(sumHMAC(signingKey, []byte(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 {
|
||||||
|
r.SignV4()
|
||||||
|
return r.req.URL.String()
|
||||||
|
}
|
||||||
|
|
||||||
// SignV4 the request before Do(), in accordance with - http://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-authenticating-requests.html
|
// 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() {
|
func (r *request) SignV4() {
|
||||||
|
query := r.req.URL.Query()
|
||||||
|
if r.expires != "" {
|
||||||
|
query.Set("X-Amz-Algorithm", authHeader)
|
||||||
|
}
|
||||||
t := time.Now().UTC()
|
t := time.Now().UTC()
|
||||||
// Add date if not present
|
// Add date if not present
|
||||||
if date := r.Get("Date"); date == "" {
|
if r.expires != "" {
|
||||||
r.Set("x-amz-date", t.Format(iso8601Format))
|
query.Set("X-Amz-Date", t.Format(iso8601Format))
|
||||||
|
query.Set("X-Amz-Expires", r.expires)
|
||||||
|
} else {
|
||||||
|
r.Set("X-Amz-Date", t.Format(iso8601Format))
|
||||||
}
|
}
|
||||||
|
|
||||||
hashedPayload := r.getHashedPayload()
|
hashedPayload := r.getHashedPayload()
|
||||||
signedHeaders := r.getSignedHeaders()
|
signedHeaders := r.getSignedHeaders()
|
||||||
canonicalRequest := r.getCanonicalRequest(hashedPayload)
|
if r.expires != "" {
|
||||||
|
query.Set("X-Amz-SignedHeaders", signedHeaders)
|
||||||
|
}
|
||||||
scope := r.getScope(t)
|
scope := r.getScope(t)
|
||||||
|
if r.expires != "" {
|
||||||
|
query.Set("X-Amz-Credential", r.config.AccessKeyID+"/"+scope)
|
||||||
|
r.req.URL.RawQuery = query.Encode()
|
||||||
|
}
|
||||||
|
canonicalRequest := r.getCanonicalRequest(hashedPayload)
|
||||||
stringToSign := r.getStringToSign(canonicalRequest, t)
|
stringToSign := r.getStringToSign(canonicalRequest, t)
|
||||||
signingKey := r.getSigningKey(t)
|
signingKey := r.getSigningKey(t)
|
||||||
signature := r.getSignature(signingKey, stringToSign)
|
signature := r.getSignature(signingKey, stringToSign)
|
||||||
|
|
||||||
// final Authorization header
|
if r.expires != "" {
|
||||||
parts := []string{
|
r.req.URL.RawQuery += "&X-Amz-Signature=" + signature
|
||||||
authHeader + " Credential=" + r.config.AccessKeyID + "/" + scope,
|
} else {
|
||||||
"SignedHeaders=" + signedHeaders,
|
// final Authorization header
|
||||||
"Signature=" + signature,
|
parts := []string{
|
||||||
|
authHeader + " Credential=" + r.config.AccessKeyID + "/" + scope,
|
||||||
|
"SignedHeaders=" + signedHeaders,
|
||||||
|
"Signature=" + signature,
|
||||||
|
}
|
||||||
|
auth := strings.Join(parts, ", ")
|
||||||
|
r.Set("Authorization", auth)
|
||||||
}
|
}
|
||||||
auth := strings.Join(parts, ", ")
|
|
||||||
r.Set("Authorization", auth)
|
|
||||||
}
|
}
|
||||||
|
|||||||
1
main.go
1
main.go
@@ -111,6 +111,7 @@ func registerApp() *cli.App {
|
|||||||
registerCmd(cpCmd) // copy objects and files from multiple sources to single destination
|
registerCmd(cpCmd) // copy objects and files from multiple sources to single destination
|
||||||
registerCmd(mirrorCmd) // mirror objects and files from single source to multiple destinations
|
registerCmd(mirrorCmd) // mirror objects and files from single source to multiple destinations
|
||||||
registerCmd(sessionCmd) // session handling for resuming copy and mirror operations
|
registerCmd(sessionCmd) // session handling for resuming copy and mirror operations
|
||||||
|
registerCmd(shareCmd) // share any given url for third party access
|
||||||
registerCmd(diffCmd) // compare two objects
|
registerCmd(diffCmd) // compare two objects
|
||||||
registerCmd(accessCmd) // set permissions [public, private, readonly, authenticated] for buckets and folders.
|
registerCmd(accessCmd) // set permissions [public, private, readonly, authenticated] for buckets and folders.
|
||||||
registerCmd(configCmd) // generate configuration "/home/harsha/.mc/config.json" file.
|
registerCmd(configCmd) // generate configuration "/home/harsha/.mc/config.json" file.
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ type Client interface {
|
|||||||
SetBucketACL(acl string) *probe.Error
|
SetBucketACL(acl string) *probe.Error
|
||||||
|
|
||||||
// Object operations
|
// Object operations
|
||||||
|
PresignedGetObject(expires time.Duration, offset, length int64) (string, *probe.Error)
|
||||||
GetObject(offset, length int64) (body io.ReadCloser, size int64, err *probe.Error)
|
GetObject(offset, length int64) (body io.ReadCloser, size int64, err *probe.Error)
|
||||||
PutObject(size int64, data io.Reader) *probe.Error
|
PutObject(size int64, data io.Reader) *probe.Error
|
||||||
|
|
||||||
|
|||||||
@@ -95,6 +95,15 @@ type GenericObjectError struct {
|
|||||||
Object string
|
Object string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ObjectAlreadyExists - typed return for MethodNotAllowed
|
||||||
|
type ObjectAlreadyExists struct {
|
||||||
|
Object string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ObjectAlreadyExists) Error() string {
|
||||||
|
return "Object #" + e.Object + " already exists."
|
||||||
|
}
|
||||||
|
|
||||||
// ObjectNotFound - object requested does not exist
|
// ObjectNotFound - object requested does not exist
|
||||||
type ObjectNotFound GenericObjectError
|
type ObjectNotFound GenericObjectError
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
|
||||||
@@ -121,6 +122,10 @@ func (f *fsClient) get() (io.ReadCloser, int64, *probe.Error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *fsClient) PresignedGetObject(expires time.Duration, offset, length int64) (string, *probe.Error) {
|
||||||
|
return "", probe.New(client.APINotImplemented{API: "PresignedGetObject"})
|
||||||
|
}
|
||||||
|
|
||||||
// GetObject download an full or part object from bucket
|
// GetObject download an full or part object from bucket
|
||||||
// getobject returns a reader, length and nil for no errors
|
// getobject returns a reader, length and nil for no errors
|
||||||
// with errors getobject will return nil reader, length and typed errors
|
// with errors getobject will return nil reader, length and typed errors
|
||||||
|
|||||||
@@ -101,13 +101,14 @@ func (c *s3Client) GetObject(offset, length int64) (io.ReadCloser, int64, *probe
|
|||||||
return reader, metadata.Size, nil
|
return reader, metadata.Size, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ObjectAlreadyExists - typed return for MethodNotAllowed
|
// PresignedGetObject - get a presigned usable get object url to share
|
||||||
type ObjectAlreadyExists struct {
|
func (c *s3Client) PresignedGetObject(expires time.Duration, offset, length int64) (string, *probe.Error) {
|
||||||
Object string
|
bucket, object := c.url2BucketAndObject()
|
||||||
}
|
presignedURL, err := c.api.PresignedGetPartialObject(bucket, object, expires, offset, length)
|
||||||
|
if err != nil {
|
||||||
func (e ObjectAlreadyExists) Error() string {
|
return "", probe.New(err)
|
||||||
return "Object #" + e.Object + " already exists."
|
}
|
||||||
|
return presignedURL, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// PutObject - put object
|
// PutObject - put object
|
||||||
@@ -121,7 +122,7 @@ func (c *s3Client) PutObject(size int64, data io.Reader) *probe.Error {
|
|||||||
errResponse := minio.ToErrorResponse(err)
|
errResponse := minio.ToErrorResponse(err)
|
||||||
if errResponse != nil {
|
if errResponse != nil {
|
||||||
if errResponse.Code == "MethodNotAllowed" {
|
if errResponse.Code == "MethodNotAllowed" {
|
||||||
return probe.New(ObjectAlreadyExists{Object: object})
|
return probe.New(client.ObjectAlreadyExists{Object: object})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return probe.New(err)
|
return probe.New(err)
|
||||||
|
|||||||
137
share-main.go
Normal file
137
share-main.go
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
/*
|
||||||
|
* Minio Client (C) 2014, 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"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/minio/cli"
|
||||||
|
"github.com/minio/mc/pkg/client"
|
||||||
|
"github.com/minio/mc/pkg/console"
|
||||||
|
"github.com/minio/minio/pkg/probe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Help message.
|
||||||
|
var shareCmd = cli.Command{
|
||||||
|
Name: "share",
|
||||||
|
Usage: "Share presigned URLs from cloud storage",
|
||||||
|
Action: runShareCmd,
|
||||||
|
CustomHelpTemplate: `NAME:
|
||||||
|
mc {{.Name}} - {{.Usage}}
|
||||||
|
|
||||||
|
USAGE:
|
||||||
|
mc {{.Name}} TARGET [TARGET...] {{if .Description}}
|
||||||
|
|
||||||
|
DESCRIPTION:
|
||||||
|
{{.Description}}{{end}}{{if .Flags}}
|
||||||
|
|
||||||
|
FLAGS:
|
||||||
|
{{range .Flags}}{{.}}
|
||||||
|
{{end}}{{ end }}
|
||||||
|
|
||||||
|
EXAMPLES:
|
||||||
|
1. Generate presigned url for an object with expiration of 10minutes
|
||||||
|
$ mc {{.Name}} https://s3.amazonaws.com/backup/2006-Mar-1/backup.tar.gz expire 10m
|
||||||
|
|
||||||
|
2. Generate presigned url for all objects at given path
|
||||||
|
$ mc {{.Name}} https://s3.amazonaws.com/backup... expire 20m
|
||||||
|
|
||||||
|
`,
|
||||||
|
}
|
||||||
|
|
||||||
|
// runShareCmd - is a handler for mc share command
|
||||||
|
func runShareCmd(ctx *cli.Context) {
|
||||||
|
if !ctx.Args().Present() || ctx.Args().First() == "help" {
|
||||||
|
cli.ShowCommandHelpAndExit(ctx, "share", 1) // last argument is exit code
|
||||||
|
}
|
||||||
|
args := ctx.Args()
|
||||||
|
config := mustGetMcConfig()
|
||||||
|
for _, arg := range args {
|
||||||
|
targetURL, err := getExpandedURL(arg, config.Aliases)
|
||||||
|
Fatal(err)
|
||||||
|
// if recursive strip off the "..."
|
||||||
|
newTargetURL := stripRecursiveURL(targetURL)
|
||||||
|
Fatal(doShareCmd(newTargetURL, isURLRecursive(targetURL)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// doShareCmd share files from target
|
||||||
|
func doShareCmd(targetURL string, recursive bool) *probe.Error {
|
||||||
|
clnt, err := target2Client(targetURL)
|
||||||
|
if err != nil {
|
||||||
|
return err.Trace()
|
||||||
|
}
|
||||||
|
err = doShare(clnt, recursive)
|
||||||
|
if err != nil {
|
||||||
|
return err.Trace()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func path2Bucket(u *client.URL) (bucketName string) {
|
||||||
|
pathSplits := strings.SplitN(u.Path, "?", 2)
|
||||||
|
splits := strings.SplitN(pathSplits[0], string(u.Separator), 3)
|
||||||
|
switch len(splits) {
|
||||||
|
case 0, 1:
|
||||||
|
bucketName = ""
|
||||||
|
case 2:
|
||||||
|
bucketName = splits[1]
|
||||||
|
case 3:
|
||||||
|
bucketName = splits[1]
|
||||||
|
}
|
||||||
|
return bucketName
|
||||||
|
}
|
||||||
|
|
||||||
|
func doShare(clnt client.Client, recursive bool) *probe.Error {
|
||||||
|
var err *probe.Error
|
||||||
|
for contentCh := range clnt.List(recursive) {
|
||||||
|
if contentCh.Err != nil {
|
||||||
|
switch contentCh.Err.ToError().(type) {
|
||||||
|
// handle this specifically for filesystem
|
||||||
|
case client.ISBrokenSymlink:
|
||||||
|
Error(contentCh.Err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if os.IsNotExist(contentCh.Err.ToError()) || os.IsPermission(contentCh.Err.ToError()) {
|
||||||
|
Error(contentCh.Err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
err = contentCh.Err
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err.Trace()
|
||||||
|
}
|
||||||
|
targetParser := clnt.URL()
|
||||||
|
targetParser.Path = path2Bucket(targetParser) + string(targetParser.Separator) + contentCh.Content.Name
|
||||||
|
newClnt, err := url2Client(targetParser.String())
|
||||||
|
if err != nil {
|
||||||
|
return err.Trace()
|
||||||
|
}
|
||||||
|
// TODO enable expiry
|
||||||
|
expire := time.Duration(1000) * time.Second
|
||||||
|
presignedURL, err := newClnt.PresignedGetObject(time.Duration(1000)*time.Second, 0, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err.Trace()
|
||||||
|
}
|
||||||
|
console.PrintC(fmt.Sprintf("Succesfully generated shared URL with expiry %s, please copy: %s\n", expire, presignedURL))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user