2
2
mirror of https://github.com/octoleo/restic.git synced 2024-06-07 19:40:49 +00:00
restic/Godeps/_workspace/src/github.com/minio/minio-go/api.go

499 lines
14 KiB
Go
Raw Normal View History

/*
* 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 (
2015-12-28 20:23:53 +00:00
"encoding/base64"
"encoding/hex"
2015-12-30 11:19:19 +00:00
"fmt"
"io"
"net/http"
2015-12-30 11:19:19 +00:00
"net/http/httputil"
"net/url"
2015-12-30 11:19:19 +00:00
"os"
2016-01-07 19:23:38 +00:00
"regexp"
"runtime"
2015-12-30 11:19:19 +00:00
"strings"
"time"
)
2015-12-28 20:23:53 +00:00
// Client implements Amazon S3 compatible methods.
type Client struct {
/// Standard options.
2016-01-07 19:23:38 +00:00
// 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
2015-12-28 20:23:53 +00:00
// User supplied.
appInfo struct {
appName string
appVersion string
}
2015-12-28 20:23:53 +00:00
endpointURL *url.URL
2015-12-28 20:23:53 +00:00
// Needs allocation.
httpClient *http.Client
bucketLocCache *bucketLocationCache
2015-12-30 11:19:19 +00:00
// Advanced functionality
isTraceEnabled bool
traceOutput io.Writer
}
2015-12-28 20:23:53 +00:00
// Global constants.
2015-12-28 15:19:34 +00:00
const (
2015-12-28 20:23:53 +00:00
libraryName = "minio-go"
libraryVersion = "0.2.5"
2015-12-28 15:19:34 +00:00
)
2015-12-28 20:23:53 +00:00
// User Agent should always following the below style.
// Please open an issue to discuss any new changes here.
//
// Minio (OS; ARCH) LIB/VER APP/VER
const (
2015-12-28 20:23:53 +00:00
libraryUserAgentPrefix = "Minio (" + runtime.GOOS + "; " + runtime.GOARCH + ") "
libraryUserAgent = libraryUserAgentPrefix + libraryName + "/" + libraryVersion
)
2016-01-07 19:23:38 +00:00
// NewV2 - instantiate minio client with Amazon S3 signature version
// '2' compatiblity.
2015-12-28 20:23:53 +00:00
func NewV2(endpoint string, accessKeyID, secretAccessKey string, insecure bool) (CloudStorageClient, error) {
clnt, err := privateNew(endpoint, accessKeyID, secretAccessKey, insecure)
if err != nil {
return nil, err
}
2015-12-28 20:23:53 +00:00
// Set to use signature version '2'.
clnt.signature = SignatureV2
return clnt, nil
}
2016-01-07 19:23:38 +00:00
// NewV4 - instantiate minio client with Amazon S3 signature version
// '4' compatibility.
2015-12-28 20:23:53 +00:00
func NewV4(endpoint string, accessKeyID, secretAccessKey string, insecure bool) (CloudStorageClient, error) {
clnt, err := privateNew(endpoint, accessKeyID, secretAccessKey, insecure)
if err != nil {
return nil, err
}
2015-12-28 20:23:53 +00:00
// Set to use signature version '4'.
clnt.signature = SignatureV4
return clnt, nil
}
2016-01-07 19:23:38 +00:00
// New - instantiate minio client Client, adds automatic verification
// of signature.
2015-12-28 20:23:53 +00:00
func New(endpoint string, accessKeyID, secretAccessKey string, insecure bool) (CloudStorageClient, error) {
clnt, err := privateNew(endpoint, accessKeyID, secretAccessKey, insecure)
if err != nil {
return nil, err
2015-12-28 15:19:34 +00:00
}
2016-01-07 19:23:38 +00:00
// Google cloud storage should be set to signature V2, force it if
// not.
2015-12-28 20:23:53 +00:00
if isGoogleEndpoint(clnt.endpointURL) {
clnt.signature = SignatureV2
2015-12-28 15:19:34 +00:00
}
2015-12-28 20:23:53 +00:00
// If Amazon S3 set to signature v2.
if isAmazonEndpoint(clnt.endpointURL) {
clnt.signature = SignatureV4
2015-12-28 15:19:34 +00:00
}
2015-12-28 20:23:53 +00:00
return clnt, nil
2015-12-28 15:19:34 +00:00
}
2015-12-28 20:23:53 +00:00
func privateNew(endpoint, accessKeyID, secretAccessKey string, insecure bool) (*Client, error) {
// construct endpoint.
endpointURL, err := getEndpointURL(endpoint, insecure)
if err != nil {
return nil, err
}
2015-12-28 20:23:53 +00:00
// instantiate new Client.
clnt := new(Client)
clnt.accessKeyID = accessKeyID
clnt.secretAccessKey = secretAccessKey
if clnt.accessKeyID == "" || clnt.secretAccessKey == "" {
clnt.anonymous = true
}
2015-12-28 20:23:53 +00:00
// Save endpoint URL, user agent for future uses.
clnt.endpointURL = endpointURL
2015-12-28 20:23:53 +00:00
// Instantiate http client and bucket location cache.
clnt.httpClient = &http.Client{}
clnt.bucketLocCache = newBucketLocationCache()
2015-12-28 20:23:53 +00:00
// Return.
return clnt, nil
}
2015-12-28 20:23:53 +00:00
// SetAppInfo - add application details to user agent.
func (c *Client) SetAppInfo(appName string, appVersion string) {
2016-01-07 19:23:38 +00:00
// if app name and version is not set, we do not a new user
// agent.
2015-12-28 20:23:53 +00:00
if appName != "" && appVersion != "" {
c.appInfo = struct {
appName string
appVersion string
}{}
c.appInfo.appName = appName
c.appInfo.appVersion = appVersion
}
}
2015-12-28 20:23:53 +00:00
// SetCustomTransport - set new custom transport.
func (c *Client) SetCustomTransport(customHTTPTransport http.RoundTripper) {
2016-01-07 19:23:38 +00:00
// Set this to override default transport
// ``http.DefaultTransport``.
2015-12-28 20:23:53 +00:00
//
2016-01-07 19:23:38 +00:00
// This transport is usually needed for debugging OR to add your
// own custom TLS certificates on the client transport, for custom
// CA's and certs which are not part of standard certificate
// authority follow this example :-
2015-12-28 20:23:53 +00:00
//
// tr := &http.Transport{
// TLSClientConfig: &tls.Config{RootCAs: pool},
// DisableCompression: true,
// }
// api.SetTransport(tr)
//
if c.httpClient != nil {
c.httpClient.Transport = customHTTPTransport
}
}
2015-12-30 11:19:19 +00:00
// TraceOn - enable HTTP tracing.
func (c *Client) TraceOn(outputStream io.Writer) error {
// if outputStream is nil then default to os.Stdout.
if outputStream == nil {
outputStream = os.Stdout
}
// Sets a new output stream.
c.traceOutput = outputStream
// Enable tracing.
c.isTraceEnabled = true
return nil
}
// TraceOff - disable HTTP tracing.
func (c *Client) TraceOff() {
// Disable tracing.
c.isTraceEnabled = false
}
2016-01-07 19:23:38 +00:00
// requestMetadata - is container for all the values to make a
// request.
2015-12-28 20:23:53 +00:00
type requestMetadata struct {
// If set newRequest presigns the URL.
presignURL bool
2015-12-28 20:23:53 +00:00
// User supplied.
bucketName string
objectName string
queryValues url.Values
customHeader http.Header
expires int64
2015-12-28 20:23:53 +00:00
// Generated by our internal code.
contentBody io.ReadCloser
contentLength int64
2016-01-07 19:23:38 +00:00
contentSHA256Bytes []byte
2015-12-28 20:23:53 +00:00
contentMD5Bytes []byte
}
2016-01-07 19:23:38 +00:00
// Filter out signature value from Authorization header.
func (c Client) filterSignature(req *http.Request) {
// For anonymous requests return here.
if c.anonymous {
return
}
// Handle if Signature V2.
if c.signature.isV2() {
// Set a temporary redacted auth
req.Header.Set("Authorization", "AWS **REDACTED**:**REDACTED**")
return
}
/// 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
regCred := regexp.MustCompile("Credential=([A-Z0-9]+)/")
newAuth := regCred.ReplaceAllString(origAuth, "Credential=**REDACTED**/")
// Strip out 256-bit signature from: Signature=<256-bit signature>
regSign := regexp.MustCompile("Signature=([[0-9a-f]+)")
newAuth = regSign.ReplaceAllString(newAuth, "Signature=**REDACTED**")
// Set a temporary redacted auth
req.Header.Set("Authorization", newAuth)
return
}
2015-12-30 11:19:19 +00:00
// dumpHTTP - dump HTTP request and response.
func (c Client) dumpHTTP(req *http.Request, resp *http.Response) error {
// Starts http dump.
_, err := fmt.Fprintln(c.traceOutput, "---------START-HTTP---------")
if err != nil {
return err
}
2016-01-07 19:23:38 +00:00
// Filter out Signature field from Authorization header.
c.filterSignature(req)
2015-12-30 11:19:19 +00:00
// Only display request header.
reqTrace, err := httputil.DumpRequestOut(req, false)
if err != nil {
return err
}
// Write request to trace output.
_, err = fmt.Fprint(c.traceOutput, string(reqTrace))
if err != nil {
return err
}
// Only display response header.
2016-01-07 19:23:38 +00:00
var respTrace []byte
2015-12-30 11:19:19 +00:00
2016-01-07 19:23:38 +00:00
// For errors we make sure to dump response body as well.
if resp.StatusCode != http.StatusOK &&
resp.StatusCode != http.StatusPartialContent &&
resp.StatusCode != http.StatusNoContent {
respTrace, err = httputil.DumpResponse(resp, true)
if err != nil {
return err
}
} else {
respTrace, err = httputil.DumpResponse(resp, false)
if err != nil {
return err
}
}
2015-12-30 11:19:19 +00:00
// Write response to trace output.
_, err = fmt.Fprint(c.traceOutput, strings.TrimSuffix(string(respTrace), "\r\n"))
if err != nil {
return err
}
// Ends the http dump.
_, err = fmt.Fprintln(c.traceOutput, "---------END-HTTP---------")
if err != nil {
return err
}
// Returns success.
return nil
}
// do - execute http request.
func (c Client) do(req *http.Request) (*http.Response, error) {
// execute the request.
resp, err := c.httpClient.Do(req)
if err != nil {
return resp, err
}
// If trace is enabled, dump http request and response.
if c.isTraceEnabled {
err = c.dumpHTTP(req, resp)
if err != nil {
return nil, err
}
}
return resp, nil
}
// newRequest - instantiate a new HTTP request for a given method.
2015-12-28 20:23:53 +00:00
func (c Client) newRequest(method string, metadata requestMetadata) (*http.Request, error) {
// If no method is supplied default to 'POST'.
if method == "" {
method = "POST"
}
2015-12-28 20:23:53 +00:00
// construct a new target URL.
targetURL, err := c.makeTargetURL(metadata.bucketName, metadata.objectName, metadata.queryValues)
if err != nil {
return nil, err
}
2015-12-28 20:23:53 +00:00
// get a new HTTP request for the method.
req, err := http.NewRequest(method, targetURL.String(), nil)
if err != nil {
2015-12-28 20:23:53 +00:00
return nil, err
}
2015-12-28 20:23:53 +00:00
// Gather location only if bucketName is present.
location := "us-east-1" // Default all other requests to "us-east-1".
if metadata.bucketName != "" {
location, err = c.getBucketLocation(metadata.bucketName)
if err != nil {
return nil, err
2015-12-28 15:19:34 +00:00
}
}
2015-12-28 20:23:53 +00:00
// If presigned request, return quickly.
if metadata.expires != 0 {
if c.anonymous {
return nil, ErrInvalidArgument("Requests cannot be presigned with anonymous credentials.")
}
2015-12-28 20:23:53 +00:00
if c.signature.isV2() {
// Presign URL with signature v2.
req = PreSignV2(*req, c.accessKeyID, c.secretAccessKey, metadata.expires)
} else {
// Presign URL with signature v4.
req = PreSignV4(*req, c.accessKeyID, c.secretAccessKey, location, metadata.expires)
}
2015-12-28 20:23:53 +00:00
return req, nil
}
2015-12-28 20:23:53 +00:00
// Set content body if available.
if metadata.contentBody != nil {
req.Body = metadata.contentBody
}
2015-12-28 20:23:53 +00:00
// set UserAgent for the request.
c.setUserAgent(req)
2015-12-28 20:23:53 +00:00
// Set all headers.
for k, v := range metadata.customHeader {
req.Header.Set(k, v[0])
}
2015-12-28 20:23:53 +00:00
// set incoming content-length.
if metadata.contentLength > 0 {
req.ContentLength = metadata.contentLength
}
2015-12-28 20:23:53 +00:00
// Set sha256 sum only for non anonymous credentials.
if !c.anonymous {
2016-01-07 19:23:38 +00:00
// set sha256 sum for signature calculation only with
// signature version '4'.
2015-12-28 20:23:53 +00:00
if c.signature.isV4() {
req.Header.Set("X-Amz-Content-Sha256", hex.EncodeToString(sum256([]byte{})))
2016-01-07 19:23:38 +00:00
if metadata.contentSHA256Bytes != nil {
req.Header.Set("X-Amz-Content-Sha256", hex.EncodeToString(metadata.contentSHA256Bytes))
}
}
}
2015-12-28 20:23:53 +00:00
// set md5Sum for content protection.
if metadata.contentMD5Bytes != nil {
req.Header.Set("Content-MD5", base64.StdEncoding.EncodeToString(metadata.contentMD5Bytes))
}
2015-12-28 20:23:53 +00:00
// Sign the request if not anonymous.
if !c.anonymous {
if c.signature.isV2() {
// Add signature version '2' authorization header.
req = SignV2(*req, c.accessKeyID, c.secretAccessKey)
} else if c.signature.isV4() {
// Add signature version '4' authorization header.
req = SignV4(*req, c.accessKeyID, c.secretAccessKey, location)
}
}
2015-12-28 20:23:53 +00:00
// return request.
return req, nil
}
2016-01-07 19:23:38 +00:00
// set User agent.
2015-12-28 20:23:53 +00:00
func (c Client) setUserAgent(req *http.Request) {
req.Header.Set("User-Agent", libraryUserAgent)
if c.appInfo.appName != "" && c.appInfo.appVersion != "" {
req.Header.Set("User-Agent", libraryUserAgent+" "+c.appInfo.appName+"/"+c.appInfo.appVersion)
}
}
2016-01-07 19:23:38 +00:00
// makeTargetURL make a new target url.
2015-12-28 20:23:53 +00:00
func (c Client) makeTargetURL(bucketName, objectName string, queryValues url.Values) (*url.URL, error) {
urlStr := c.endpointURL.Scheme + "://" + c.endpointURL.Host + "/"
2016-01-07 19:23:38 +00:00
// Make URL only if bucketName is available, otherwise use the
// endpoint URL.
2015-12-28 20:23:53 +00:00
if bucketName != "" {
// If endpoint supports virtual host style use that always.
2016-01-07 19:23:38 +00:00
// Currently only S3 and Google Cloud Storage would support
// this.
2015-12-28 20:23:53 +00:00
if isVirtualHostSupported(c.endpointURL) {
urlStr = c.endpointURL.Scheme + "://" + bucketName + "." + c.endpointURL.Host + "/"
if objectName != "" {
urlStr = urlStr + urlEncodePath(objectName)
}
2015-12-28 20:23:53 +00:00
} else {
// If not fall back to using path style.
urlStr = urlStr + bucketName
if objectName != "" {
urlStr = urlStr + "/" + urlEncodePath(objectName)
}
}
}
2015-12-28 20:23:53 +00:00
// If there are any query values, add them to the end.
if len(queryValues) > 0 {
urlStr = urlStr + "?" + queryValues.Encode()
}
2015-12-28 20:23:53 +00:00
u, err := url.Parse(urlStr)
if err != nil {
return nil, err
}
2015-12-28 20:23:53 +00:00
return u, nil
}
2015-12-28 20:23:53 +00:00
// CloudStorageClient - Cloud Storage Client interface.
type CloudStorageClient interface {
// Bucket Read/Write/Stat operations.
MakeBucket(bucketName string, cannedACL BucketACL, location string) error
BucketExists(bucketName string) error
RemoveBucket(bucketName string) error
SetBucketACL(bucketName string, cannedACL BucketACL) error
GetBucketACL(bucketName string) (BucketACL, error)
2016-01-07 19:23:38 +00:00
ListBuckets() ([]BucketInfo, error)
ListObjects(bucket, prefix string, recursive bool, doneCh <-chan struct{}) <-chan ObjectInfo
ListIncompleteUploads(bucket, prefix string, recursive bool, doneCh <-chan struct{}) <-chan ObjectMultipartInfo
2015-12-28 20:23:53 +00:00
// Object Read/Write/Stat operations.
2016-01-07 19:23:38 +00:00
GetObject(bucketName, objectName string) (reader *Object, err error)
PutObject(bucketName, objectName string, reader io.Reader, contentType string) (n int64, err error)
StatObject(bucketName, objectName string) (ObjectInfo, error)
2015-12-28 20:23:53 +00:00
RemoveObject(bucketName, objectName string) error
RemoveIncompleteUpload(bucketName, objectName string) error
2015-12-28 20:23:53 +00:00
// File to Object API.
FPutObject(bucketName, objectName, filePath, contentType string) (n int64, err error)
FGetObject(bucketName, objectName, filePath string) error
2015-12-28 20:23:53 +00:00
// Presigned operations.
PresignedGetObject(bucketName, objectName string, expires time.Duration) (presignedURL string, err error)
PresignedPutObject(bucketName, objectName string, expires time.Duration) (presignedURL string, err error)
PresignedPostPolicy(*PostPolicy) (formData map[string]string, err error)
// Application info.
SetAppInfo(appName, appVersion string)
2015-12-28 20:23:53 +00:00
// Set custom transport.
SetCustomTransport(customTransport http.RoundTripper)
2015-12-30 11:19:19 +00:00
// HTTP tracing methods.
TraceOn(traceOutput io.Writer) error
TraceOff()
}