diff options
Diffstat (limited to 'vendor/github.com/minio/minio-go/api.go')
-rw-r--r-- | vendor/github.com/minio/minio-go/api.go | 220 |
1 files changed, 127 insertions, 93 deletions
diff --git a/vendor/github.com/minio/minio-go/api.go b/vendor/github.com/minio/minio-go/api.go index a21c40e80..c04034a86 100644 --- a/vendor/github.com/minio/minio-go/api.go +++ b/vendor/github.com/minio/minio-go/api.go @@ -1,5 +1,6 @@ /* - * Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015, 2016 Minio, Inc. + * Minio Go Library for Amazon S3 Compatible Cloud Storage + * (C) 2015, 2016, 2017 Minio, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,6 +21,7 @@ import ( "bytes" "encoding/base64" "encoding/hex" + "errors" "fmt" "io" "io/ioutil" @@ -34,6 +36,7 @@ import ( "sync" "time" + "github.com/minio/minio-go/pkg/credentials" "github.com/minio/minio-go/pkg/s3signer" "github.com/minio/minio-go/pkg/s3utils" ) @@ -45,14 +48,11 @@ type Client struct { // Parsed endpoint url provided by the user. endpointURL url.URL - // AccessKeyID required for authorized requests. - accessKeyID string - // SecretAccessKey required for authorized requests. - secretAccessKey string - // Choose a signature type if necessary. - signature SignatureType - // Set to 'true' if Client has no access and secret keys. - anonymous bool + // Holds various credential providers. + credsProvider *credentials.Credentials + + // Custom signerType value overrides all credentials. + overrideSignerType credentials.SignatureType // User supplied. appInfo struct { @@ -74,6 +74,9 @@ type Client struct { // S3 specific accelerated endpoint. s3AccelerateEndpoint string + // Region endpoint + region string + // Random seed. random *rand.Rand } @@ -81,7 +84,7 @@ type Client struct { // Global constants. const ( libraryName = "minio-go" - libraryVersion = "2.0.4" + libraryVersion = "2.1.0" ) // User Agent should always following the below style. @@ -96,53 +99,67 @@ const ( // NewV2 - instantiate minio client with Amazon S3 signature version // '2' compatibility. func NewV2(endpoint string, accessKeyID, secretAccessKey string, secure bool) (*Client, error) { - clnt, err := privateNew(endpoint, accessKeyID, secretAccessKey, secure) + creds := credentials.NewStaticV2(accessKeyID, secretAccessKey, "") + clnt, err := privateNew(endpoint, creds, secure, "") if err != nil { return nil, err } - // Set to use signature version '2'. - clnt.signature = SignatureV2 + clnt.overrideSignerType = credentials.SignatureV2 return clnt, nil } // NewV4 - instantiate minio client with Amazon S3 signature version // '4' compatibility. func NewV4(endpoint string, accessKeyID, secretAccessKey string, secure bool) (*Client, error) { - clnt, err := privateNew(endpoint, accessKeyID, secretAccessKey, secure) + creds := credentials.NewStaticV4(accessKeyID, secretAccessKey, "") + clnt, err := privateNew(endpoint, creds, secure, "") if err != nil { return nil, err } - // Set to use signature version '4'. - clnt.signature = SignatureV4 + clnt.overrideSignerType = credentials.SignatureV4 return clnt, nil } -// New - instantiate minio client Client, adds automatic verification -// of signature. -func New(endpoint string, accessKeyID, secretAccessKey string, secure bool) (*Client, error) { - clnt, err := privateNew(endpoint, accessKeyID, secretAccessKey, secure) +// New - instantiate minio client, adds automatic verification of signature. +func New(endpoint, accessKeyID, secretAccessKey string, secure bool) (*Client, error) { + creds := credentials.NewStaticV4(accessKeyID, secretAccessKey, "") + clnt, err := privateNew(endpoint, creds, secure, "") if err != nil { return nil, err } // Google cloud storage should be set to signature V2, force it if not. if s3utils.IsGoogleEndpoint(clnt.endpointURL) { - clnt.signature = SignatureV2 + clnt.overrideSignerType = credentials.SignatureV2 } - // If Amazon S3 set to signature v2.n + // If Amazon S3 set to signature v4. if s3utils.IsAmazonEndpoint(clnt.endpointURL) { - clnt.signature = SignatureV4 + clnt.overrideSignerType = credentials.SignatureV4 } return clnt, nil } +// NewWithCredentials - instantiate minio client with credentials provider +// for retrieving credentials from various credentials provider such as +// IAM, File, Env etc. +func NewWithCredentials(endpoint string, creds *credentials.Credentials, secure bool, region string) (*Client, error) { + return privateNew(endpoint, creds, secure, region) +} + +// NewWithRegion - instantiate minio client, with region configured. Unlike New(), +// NewWithRegion avoids bucket-location lookup operations and it is slightly faster. +// Use this function when if your application deals with single region. +func NewWithRegion(endpoint, accessKeyID, secretAccessKey string, secure bool, region string) (*Client, error) { + creds := credentials.NewStaticV4(accessKeyID, secretAccessKey, "") + return privateNew(endpoint, creds, secure, region) +} + // lockedRandSource provides protected rand source, implements rand.Source interface. type lockedRandSource struct { lk sync.Mutex src rand.Source } -// Int63 returns a non-negative pseudo-random 63-bit integer as an -// int64. +// Int63 returns a non-negative pseudo-random 63-bit integer as an int64. func (r *lockedRandSource) Int63() (n int64) { r.lk.Lock() n = r.src.Int63() @@ -170,7 +187,7 @@ func redirectHeaders(req *http.Request, via []*http.Request) error { return nil } -func privateNew(endpoint, accessKeyID, secretAccessKey string, secure bool) (*Client, error) { +func privateNew(endpoint string, creds *credentials.Credentials, secure bool, region string) (*Client, error) { // construct endpoint. endpointURL, err := getEndpointURL(endpoint, secure) if err != nil { @@ -179,11 +196,9 @@ func privateNew(endpoint, accessKeyID, secretAccessKey string, secure bool) (*Cl // instantiate new Client. clnt := new(Client) - clnt.accessKeyID = accessKeyID - clnt.secretAccessKey = secretAccessKey - if clnt.accessKeyID == "" || clnt.secretAccessKey == "" { - clnt.anonymous = true - } + + // Save the credentials. + clnt.credsProvider = creds // Remember whether we are using https or not clnt.secure = secure @@ -197,7 +212,10 @@ func privateNew(endpoint, accessKeyID, secretAccessKey string, secure bool) (*Cl CheckRedirect: redirectHeaders, } - // Instantiae bucket location cache. + // Sets custom region, if region is empty bucket location cache is used automatically. + clnt.region = region + + // Instantiate bucket location cache. clnt.bucketLocCache = newBucketLocationCache() // Introduce a new locked random seed. @@ -299,12 +317,12 @@ var regSign = regexp.MustCompile("Signature=([[0-9a-f]+)") // Filter out signature value from Authorization header. func (c Client) filterSignature(req *http.Request) { - // For anonymous requests, no need to filter. - if c.anonymous { + origAuth := req.Header.Get("Authorization") + if origAuth != "" { return } - // Handle if Signature V2. - if c.signature.isV2() { + + if !strings.HasPrefix(origAuth, signV4Algorithm) { // Set a temporary redacted auth req.Header.Set("Authorization", "AWS **REDACTED**:**REDACTED**") return @@ -312,8 +330,6 @@ func (c Client) filterSignature(req *http.Request) { /// Signature V4 authorization header. - // Save the original auth. - origAuth := req.Header.Get("Authorization") // Strip out accessKeyID from: // Credential=<access-key-id>/<date>/<aws-region>/<aws-service>/aws4_request newAuth := regCred.ReplaceAllString(origAuth, "Credential=**REDACTED**/") @@ -323,6 +339,7 @@ func (c Client) filterSignature(req *http.Request) { // Set a temporary redacted auth req.Header.Set("Authorization", newAuth) + return } @@ -411,7 +428,7 @@ func (c Client) do(req *http.Request) (*http.Response, error) { return nil, &url.Error{ Op: urlErr.Op, URL: urlErr.URL, - Err: fmt.Errorf("Connection closed by foreign host %s. Retry again.", urlErr.URL), + Err: errors.New("Connection closed by foreign host " + urlErr.URL + ". Retry again."), } } return nil, err @@ -460,9 +477,13 @@ func (c Client) executeMethod(method string, metadata requestMetadata) (res *htt if metadata.contentBody != nil { // Check if body is seekable then it is retryable. bodySeeker, isRetryable = metadata.contentBody.(io.Seeker) + switch bodySeeker { + case os.Stdin, os.Stdout, os.Stderr: + isRetryable = false + } } - // Create a done channel to control 'ListObjects' go routine. + // Create a done channel to control 'newRetryTimer' go routine. doneCh := make(chan struct{}, 1) // Indicate to our routine to exit cleanly upon return. @@ -471,7 +492,7 @@ func (c Client) executeMethod(method string, metadata requestMetadata) (res *htt // Blank indentifier is kept here on purpose since 'range' without // blank identifiers is only supported since go1.4 // https://golang.org/doc/go1.4#forrange. - for _ = range c.newRetryTimer(MaxRetry, time.Second, time.Second*30, MaxJitter, doneCh) { + for _ = range c.newRetryTimer(MaxRetry, DefaultRetryUnit, DefaultRetryCap, MaxJitter, doneCh) { // Retry executes the following function body if request has an // error until maxRetries have been exhausted, retry attempts are // performed after waiting for a given period of time in a @@ -520,15 +541,22 @@ func (c Client) executeMethod(method string, metadata requestMetadata) (res *htt if err != nil { return nil, err } + // Save the body. errBodySeeker := bytes.NewReader(errBodyBytes) res.Body = ioutil.NopCloser(errBodySeeker) // For errors verify if its retryable otherwise fail quickly. errResponse := ToErrorResponse(httpRespToErrorResponse(res, metadata.bucketName, metadata.objectName)) - // Bucket region if set in error response, we can retry the - // request with the new region. - if errResponse.Region != "" { + + // Save the body back again. + errBodySeeker.Seek(0, 0) // Seek back to starting point. + res.Body = ioutil.NopCloser(errBodySeeker) + + // Bucket region if set in error response and the error + // code dictates invalid region, we can retry the request + // with the new region. + if res.StatusCode == http.StatusBadRequest && errResponse.Region != "" { c.bucketLocCache.Set(metadata.bucketName, errResponse.Region) continue // Retry. } @@ -543,10 +571,6 @@ func (c Client) executeMethod(method string, metadata requestMetadata) (res *htt continue // Retry. } - // Save the body back again. - errBodySeeker.Seek(0, 0) // Seek back to starting point. - res.Body = ioutil.NopCloser(errBodySeeker) - // For all other cases break out of the retry loop. break } @@ -588,39 +612,50 @@ func (c Client) newRequest(method string, metadata requestMetadata) (req *http.R } // Initialize a new HTTP request for the method. - req, err = http.NewRequest(method, targetURL.String(), nil) + req, err = http.NewRequest(method, targetURL.String(), metadata.contentBody) if err != nil { return nil, err } + // Get credentials from the configured credentials provider. + value, err := c.credsProvider.Get() + if err != nil { + return nil, err + } + + var ( + signerType = value.SignerType + accessKeyID = value.AccessKeyID + secretAccessKey = value.SecretAccessKey + sessionToken = value.SessionToken + ) + + // Custom signer set then override the behavior. + if c.overrideSignerType != credentials.SignatureDefault { + signerType = c.overrideSignerType + } + + // If signerType returned by credentials helper is anonymous, + // then do not sign regardless of signerType override. + if value.SignerType == credentials.SignatureAnonymous { + signerType = credentials.SignatureAnonymous + } + // Generate presign url if needed, return right here. if metadata.expires != 0 && metadata.presignURL { - if c.anonymous { - return nil, ErrInvalidArgument("Requests cannot be presigned with anonymous credentials.") + if signerType.IsAnonymous() { + return nil, ErrInvalidArgument("Presigned URLs cannot be generated with anonymous credentials.") } - if c.signature.isV2() { + if signerType.IsV2() { // Presign URL with signature v2. - req = s3signer.PreSignV2(*req, c.accessKeyID, c.secretAccessKey, metadata.expires) - } else { + req = s3signer.PreSignV2(*req, accessKeyID, secretAccessKey, metadata.expires) + } else if signerType.IsV4() { // Presign URL with signature v4. - req = s3signer.PreSignV4(*req, c.accessKeyID, c.secretAccessKey, location, metadata.expires) + req = s3signer.PreSignV4(*req, accessKeyID, secretAccessKey, sessionToken, location, metadata.expires) } return req, nil } - // Set content body if available. - if metadata.contentBody != nil { - req.Body = ioutil.NopCloser(metadata.contentBody) - } - - // FIXME: Enable this when Google Cloud Storage properly supports 100-continue. - // Skip setting 'expect' header for Google Cloud Storage, there - // are some known issues - https://github.com/restic/restic/issues/520 - if !s3utils.IsGoogleEndpoint(c.endpointURL) && c.s3AccelerateEndpoint == "" { - // Set 'Expect' header for the request. - req.Header.Set("Expect", "100-continue") - } - // Set 'User-Agent' header for the request. c.setUserAgent(req) @@ -634,37 +669,33 @@ func (c Client) newRequest(method string, metadata requestMetadata) (req *http.R req.ContentLength = metadata.contentLength } - // Set sha256 sum only for non anonymous credentials. - if !c.anonymous { - // set sha256 sum for signature calculation only with - // signature version '4'. - if c.signature.isV4() { - shaHeader := unsignedPayload - if !c.secure { - if metadata.contentSHA256Bytes == nil { - shaHeader = hex.EncodeToString(sum256([]byte{})) - } else { - shaHeader = hex.EncodeToString(metadata.contentSHA256Bytes) - } - } - req.Header.Set("X-Amz-Content-Sha256", shaHeader) - } - } - // set md5Sum for content protection. if metadata.contentMD5Bytes != nil { req.Header.Set("Content-Md5", base64.StdEncoding.EncodeToString(metadata.contentMD5Bytes)) } - // Sign the request for all authenticated requests. - if !c.anonymous { - if c.signature.isV2() { - // Add signature version '2' authorization header. - req = s3signer.SignV2(*req, c.accessKeyID, c.secretAccessKey) - } else if c.signature.isV4() { - // Add signature version '4' authorization header. - req = s3signer.SignV4(*req, c.accessKeyID, c.secretAccessKey, location) + // For anonymous requests just return. + if signerType.IsAnonymous() { + return req, nil + } + + switch { + case signerType.IsV2(): + // Add signature version '2' authorization header. + req = s3signer.SignV2(*req, accessKeyID, secretAccessKey) + case signerType.IsStreamingV4() && method == "PUT": + req = s3signer.StreamingSignV4(req, accessKeyID, + secretAccessKey, sessionToken, location, metadata.contentLength, time.Now().UTC()) + default: + // Set sha256 sum for signature calculation only with signature version '4'. + shaHeader := unsignedPayload + if len(metadata.contentSHA256Bytes) > 0 { + shaHeader = hex.EncodeToString(metadata.contentSHA256Bytes) } + req.Header.Set("X-Amz-Content-Sha256", shaHeader) + + // Add signature version '4' authorization header. + req = s3signer.SignV4(*req, accessKeyID, secretAccessKey, sessionToken, location) } // Return request. @@ -726,13 +757,16 @@ func (c Client) makeTargetURL(bucketName, objectName, bucketLocation string, que } } } + // If there are any query values, add them to the end. if len(queryValues) > 0 { urlStr = urlStr + "?" + s3utils.QueryEncode(queryValues) } + u, err := url.Parse(urlStr) if err != nil { return nil, err } + return u, nil } |