mirror of
https://github.com/minio/minio.git
synced 2025-01-25 21:53:16 -05:00
Allow CopyObject() in S3 gateway to support metadata (#5000)
Fixes #4924
This commit is contained in:
parent
53f3d2fd65
commit
89d528a4ed
@ -55,11 +55,11 @@ func (l *s3Objects) AnonPutObject(bucket string, object string, size int64, data
|
||||
|
||||
// AnonGetObject - Get object anonymously
|
||||
func (l *s3Objects) AnonGetObject(bucket string, key string, startOffset int64, length int64, writer io.Writer) error {
|
||||
r := minio.NewGetReqHeaders()
|
||||
if err := r.SetRange(startOffset, startOffset+length-1); err != nil {
|
||||
opts := minio.GetObjectOptions{}
|
||||
if err := opts.SetRange(startOffset, startOffset+length-1); err != nil {
|
||||
return s3ToObjectError(traceError(err), bucket, key)
|
||||
}
|
||||
object, _, err := l.anonClient.GetObject(bucket, key, r)
|
||||
object, _, err := l.anonClient.GetObject(bucket, key, opts)
|
||||
if err != nil {
|
||||
return s3ToObjectError(traceError(err), bucket, key)
|
||||
}
|
||||
@ -75,8 +75,7 @@ func (l *s3Objects) AnonGetObject(bucket string, key string, startOffset int64,
|
||||
|
||||
// AnonGetObjectInfo - Get object info anonymously
|
||||
func (l *s3Objects) AnonGetObjectInfo(bucket string, object string) (objInfo ObjectInfo, e error) {
|
||||
r := minio.NewHeadReqHeaders()
|
||||
oi, err := l.anonClient.StatObject(bucket, object, r)
|
||||
oi, err := l.anonClient.StatObject(bucket, object, minio.StatObjectOptions{})
|
||||
if err != nil {
|
||||
return objInfo, s3ToObjectError(traceError(err), bucket, object)
|
||||
}
|
||||
|
@ -19,7 +19,6 @@ package cmd
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"encoding/hex"
|
||||
|
||||
@ -290,22 +289,20 @@ func fromMinioClientListBucketResult(bucket string, result minio.ListBucketResul
|
||||
// startOffset indicates the starting read location of the object.
|
||||
// length indicates the total length of the object.
|
||||
func (l *s3Objects) GetObject(bucket string, key string, startOffset int64, length int64, writer io.Writer) error {
|
||||
r := minio.NewGetReqHeaders()
|
||||
|
||||
if length < 0 && length != -1 {
|
||||
return s3ToObjectError(traceError(errInvalidArgument), bucket, key)
|
||||
}
|
||||
|
||||
opts := minio.GetObjectOptions{}
|
||||
if startOffset >= 0 && length >= 0 {
|
||||
if err := r.SetRange(startOffset, startOffset+length-1); err != nil {
|
||||
if err := opts.SetRange(startOffset, startOffset+length-1); err != nil {
|
||||
return s3ToObjectError(traceError(err), bucket, key)
|
||||
}
|
||||
}
|
||||
object, _, err := l.Client.GetObject(bucket, key, r)
|
||||
object, _, err := l.Client.GetObject(bucket, key, opts)
|
||||
if err != nil {
|
||||
return s3ToObjectError(traceError(err), bucket, key)
|
||||
}
|
||||
|
||||
defer object.Close()
|
||||
|
||||
if _, err := io.Copy(writer, object); err != nil {
|
||||
@ -333,8 +330,7 @@ func fromMinioClientObjectInfo(bucket string, oi minio.ObjectInfo) ObjectInfo {
|
||||
|
||||
// GetObjectInfo reads object info and replies back ObjectInfo
|
||||
func (l *s3Objects) GetObjectInfo(bucket string, object string) (objInfo ObjectInfo, err error) {
|
||||
r := minio.NewHeadReqHeaders()
|
||||
oi, err := l.Client.StatObject(bucket, object, r)
|
||||
oi, err := l.Client.StatObject(bucket, object, minio.StatObjectOptions{})
|
||||
if err != nil {
|
||||
return ObjectInfo{}, s3ToObjectError(traceError(err), bucket, object)
|
||||
}
|
||||
@ -361,35 +357,17 @@ func (l *s3Objects) PutObject(bucket string, object string, data *HashReader, me
|
||||
return fromMinioClientObjectInfo(bucket, oi), nil
|
||||
}
|
||||
|
||||
// CopyObject copies a blob from source container to destination container.
|
||||
// CopyObject copies an object from source bucket to a destination bucket.
|
||||
func (l *s3Objects) CopyObject(srcBucket string, srcObject string, dstBucket string, dstObject string, metadata map[string]string) (objInfo ObjectInfo, err error) {
|
||||
// Source object
|
||||
src := minio.NewSourceInfo(srcBucket, srcObject, nil)
|
||||
|
||||
// Destination object
|
||||
var xamzMeta = map[string]string{}
|
||||
for key := range metadata {
|
||||
for _, prefix := range userMetadataKeyPrefixes {
|
||||
if strings.HasPrefix(key, prefix) {
|
||||
xamzMeta[key] = metadata[key]
|
||||
}
|
||||
}
|
||||
}
|
||||
dst, err := minio.NewDestinationInfo(dstBucket, dstObject, nil, xamzMeta)
|
||||
if err != nil {
|
||||
return objInfo, s3ToObjectError(traceError(err), dstBucket, dstObject)
|
||||
}
|
||||
|
||||
if err = l.Client.CopyObject(dst, src); err != nil {
|
||||
// Set this header such that following CopyObject() always sets the right metadata on the destination.
|
||||
// metadata input is already a trickled down value from interpreting x-amz-metadata-directive at
|
||||
// handler layer. So what we have right now is supposed to be applied on the destination object anyways.
|
||||
// So preserve it by adding "REPLACE" directive to save all the metadata set by CopyObject API.
|
||||
metadata["x-amz-metadata-directive"] = "REPLACE"
|
||||
if _, err = l.Client.CopyObject(srcBucket, srcObject, dstBucket, dstObject, metadata); err != nil {
|
||||
return objInfo, s3ToObjectError(traceError(err), srcBucket, srcObject)
|
||||
}
|
||||
|
||||
oi, err := l.GetObjectInfo(dstBucket, dstObject)
|
||||
if err != nil {
|
||||
return objInfo, s3ToObjectError(traceError(err), dstBucket, dstObject)
|
||||
}
|
||||
|
||||
return oi, nil
|
||||
return l.GetObjectInfo(dstBucket, dstObject)
|
||||
}
|
||||
|
||||
// DeleteObject deletes a blob in bucket
|
||||
|
@ -398,7 +398,9 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
|
||||
if err != nil {
|
||||
errorIf(err, "found invalid http request header")
|
||||
writeErrorResponse(w, ErrInternalError, r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
// Check if x-amz-metadata-directive was not set to REPLACE and source,
|
||||
// desination are same objects.
|
||||
if !isMetadataReplace(r.Header) && cpSrcDstSame {
|
||||
|
1
vendor/github.com/minio/minio-go/README.md
generated
vendored
1
vendor/github.com/minio/minio-go/README.md
generated
vendored
@ -224,6 +224,7 @@ The full API Reference is available here.
|
||||
### Full Examples : Encrypted Object Operations
|
||||
* [put-encrypted-object.go](https://github.com/minio/minio-go/blob/master/examples/s3/put-encrypted-object.go)
|
||||
* [get-encrypted-object.go](https://github.com/minio/minio-go/blob/master/examples/s3/get-encrypted-object.go)
|
||||
* [fput-encrypted-object.go](https://github.com/minio/minio-go/blob/master/examples/s3/fputencrypted-object.go)
|
||||
|
||||
### Full Examples : Presigned Operations
|
||||
* [presignedgetobject.go](https://github.com/minio/minio-go/blob/master/examples/s3/presignedgetobject.go)
|
||||
|
51
vendor/github.com/minio/minio-go/api-compose-object.go
generated
vendored
51
vendor/github.com/minio/minio-go/api-compose-object.go
generated
vendored
@ -244,11 +244,11 @@ func (s *SourceInfo) getProps(c Client) (size int64, etag string, userMeta map[s
|
||||
// Get object info - need size and etag here. Also, decryption
|
||||
// headers are added to the stat request if given.
|
||||
var objInfo ObjectInfo
|
||||
rh := NewGetReqHeaders()
|
||||
opts := StatObjectOptions{}
|
||||
for k, v := range s.decryptKey.getSSEHeaders(false) {
|
||||
rh.Set(k, v)
|
||||
opts.Set(k, v)
|
||||
}
|
||||
objInfo, err = c.statObject(s.bucket, s.object, rh)
|
||||
objInfo, err = c.statObject(s.bucket, s.object, opts)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Could not stat object - %s/%s: %v", s.bucket, s.object, err)
|
||||
} else {
|
||||
@ -266,6 +266,51 @@ func (s *SourceInfo) getProps(c Client) (size int64, etag string, userMeta map[s
|
||||
return
|
||||
}
|
||||
|
||||
// Low level implementation of CopyObject API, supports only upto 5GiB worth of copy.
|
||||
func (c Client) copyObjectDo(ctx context.Context, srcBucket, srcObject, destBucket, destObject string,
|
||||
metadata map[string]string) (ObjectInfo, error) {
|
||||
|
||||
// Build headers.
|
||||
headers := make(http.Header)
|
||||
|
||||
// Set all the metadata headers.
|
||||
for k, v := range metadata {
|
||||
headers.Set(k, v)
|
||||
}
|
||||
|
||||
// Set the source header
|
||||
headers.Set("x-amz-copy-source", s3utils.EncodePath(srcBucket+"/"+srcObject))
|
||||
|
||||
// Send upload-part-copy request
|
||||
resp, err := c.executeMethod(ctx, "PUT", requestMetadata{
|
||||
bucketName: destBucket,
|
||||
objectName: destObject,
|
||||
customHeader: headers,
|
||||
})
|
||||
defer closeResponse(resp)
|
||||
if err != nil {
|
||||
return ObjectInfo{}, err
|
||||
}
|
||||
|
||||
// Check if we got an error response.
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return ObjectInfo{}, httpRespToErrorResponse(resp, srcBucket, srcObject)
|
||||
}
|
||||
|
||||
cpObjRes := copyObjectResult{}
|
||||
err = xmlDecoder(resp.Body, &cpObjRes)
|
||||
if err != nil {
|
||||
return ObjectInfo{}, err
|
||||
}
|
||||
|
||||
objInfo := ObjectInfo{
|
||||
Key: destObject,
|
||||
ETag: strings.Trim(cpObjRes.ETag, "\""),
|
||||
LastModified: cpObjRes.LastModified,
|
||||
}
|
||||
return objInfo, nil
|
||||
}
|
||||
|
||||
// uploadPartCopy - helper function to create a part in a multipart
|
||||
// upload via an upload-part-copy request
|
||||
// https://docs.aws.amazon.com/AmazonS3/latest/API/mpUploadUploadPartCopy.html
|
||||
|
5
vendor/github.com/minio/minio-go/api-get-object-context.go
generated
vendored
5
vendor/github.com/minio/minio-go/api-get-object-context.go
generated
vendored
@ -19,6 +19,7 @@ package minio
|
||||
import "context"
|
||||
|
||||
// GetObjectWithContext - returns an seekable, readable object.
|
||||
func (c Client) GetObjectWithContext(ctx context.Context, bucketName, objectName string) (*Object, error) {
|
||||
return c.getObjectWithContext(ctx, bucketName, objectName)
|
||||
// The options can be used to specify the GET request further.
|
||||
func (c Client) GetObjectWithContext(ctx context.Context, bucketName, objectName string, opts GetObjectOptions) (*Object, error) {
|
||||
return c.getObjectWithContext(ctx, bucketName, objectName, opts)
|
||||
}
|
||||
|
28
vendor/github.com/minio/minio-go/api-get-object-file.go
generated
vendored
28
vendor/github.com/minio/minio-go/api-get-object-file.go
generated
vendored
@ -21,23 +21,34 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/minio/minio-go/pkg/encrypt"
|
||||
|
||||
"context"
|
||||
|
||||
"github.com/minio/minio-go/pkg/s3utils"
|
||||
)
|
||||
|
||||
// FGetObjectWithContext - download contents of an object to a local file.
|
||||
func (c Client) FGetObjectWithContext(ctx context.Context, bucketName, objectName, filePath string) error {
|
||||
return c.fGetObjectWithContext(ctx, bucketName, objectName, filePath)
|
||||
// The options can be used to specify the GET request further.
|
||||
func (c Client) FGetObjectWithContext(ctx context.Context, bucketName, objectName, filePath string, opts GetObjectOptions) error {
|
||||
return c.fGetObjectWithContext(ctx, bucketName, objectName, filePath, opts)
|
||||
}
|
||||
|
||||
// FGetObject - download contents of an object to a local file.
|
||||
func (c Client) FGetObject(bucketName, objectName, filePath string) error {
|
||||
return c.fGetObjectWithContext(context.Background(), bucketName, objectName, filePath)
|
||||
func (c Client) FGetObject(bucketName, objectName, filePath string, opts GetObjectOptions) error {
|
||||
return c.fGetObjectWithContext(context.Background(), bucketName, objectName, filePath, opts)
|
||||
}
|
||||
|
||||
// FGetEncryptedObject - Decrypt and store an object at filePath.
|
||||
func (c Client) FGetEncryptedObject(bucketName, objectName, filePath string, materials encrypt.Materials) error {
|
||||
if materials == nil {
|
||||
return ErrInvalidArgument("Unable to recognize empty encryption properties")
|
||||
}
|
||||
return c.FGetObject(bucketName, objectName, filePath, GetObjectOptions{Materials: materials})
|
||||
}
|
||||
|
||||
// fGetObjectWithContext - fgetObject wrapper function with context
|
||||
func (c Client) fGetObjectWithContext(ctx context.Context, bucketName, objectName, filePath string) error {
|
||||
func (c Client) fGetObjectWithContext(ctx context.Context, bucketName, objectName, filePath string, opts GetObjectOptions) error {
|
||||
// Input validation.
|
||||
if err := s3utils.CheckValidBucketName(bucketName); err != nil {
|
||||
return err
|
||||
@ -72,7 +83,7 @@ func (c Client) fGetObjectWithContext(ctx context.Context, bucketName, objectNam
|
||||
}
|
||||
|
||||
// Gather md5sum.
|
||||
objectStat, err := c.StatObject(bucketName, objectName)
|
||||
objectStat, err := c.StatObject(bucketName, objectName, StatObjectOptions{opts})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -94,13 +105,12 @@ func (c Client) fGetObjectWithContext(ctx context.Context, bucketName, objectNam
|
||||
|
||||
// Initialize get object request headers to set the
|
||||
// appropriate range offsets to read from.
|
||||
reqHeaders := NewGetReqHeaders()
|
||||
if st.Size() > 0 {
|
||||
reqHeaders.SetRange(st.Size(), 0)
|
||||
opts.SetRange(st.Size(), 0)
|
||||
}
|
||||
|
||||
// Seek to current position for incoming reader.
|
||||
objectReader, objectStat, err := c.getObject(ctx, bucketName, objectName, reqHeaders)
|
||||
objectReader, objectStat, err := c.getObject(ctx, bucketName, objectName, opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
88
vendor/github.com/minio/minio-go/api-get-object.go
generated
vendored
88
vendor/github.com/minio/minio-go/api-get-object.go
generated
vendored
@ -37,32 +37,16 @@ func (c Client) GetEncryptedObject(bucketName, objectName string, encryptMateria
|
||||
return nil, ErrInvalidArgument("Unable to recognize empty encryption properties")
|
||||
}
|
||||
|
||||
// Fetch encrypted object
|
||||
encReader, err := c.GetObject(bucketName, objectName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Stat object to get its encryption metadata
|
||||
st, err := encReader.Stat()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Setup object for decrytion, object is transparently
|
||||
// decrypted as the consumer starts reading.
|
||||
encryptMaterials.SetupDecryptMode(encReader, st.Metadata.Get(amzHeaderIV), st.Metadata.Get(amzHeaderKey))
|
||||
|
||||
// Success.
|
||||
return encryptMaterials, nil
|
||||
return c.GetObject(bucketName, objectName, GetObjectOptions{Materials: encryptMaterials})
|
||||
}
|
||||
|
||||
// GetObject - returns an seekable, readable object.
|
||||
func (c Client) GetObject(bucketName, objectName string) (*Object, error) {
|
||||
return c.getObjectWithContext(context.Background(), bucketName, objectName)
|
||||
func (c Client) GetObject(bucketName, objectName string, opts GetObjectOptions) (*Object, error) {
|
||||
return c.getObjectWithContext(context.Background(), bucketName, objectName, opts)
|
||||
}
|
||||
|
||||
// GetObject wrapper function that accepts a request context
|
||||
func (c Client) getObjectWithContext(ctx context.Context, bucketName, objectName string) (*Object, error) {
|
||||
func (c Client) getObjectWithContext(ctx context.Context, bucketName, objectName string, opts GetObjectOptions) (*Object, error) {
|
||||
// Input validation.
|
||||
if err := s3utils.CheckValidBucketName(bucketName); err != nil {
|
||||
return nil, err
|
||||
@ -108,34 +92,26 @@ func (c Client) getObjectWithContext(ctx context.Context, bucketName, objectName
|
||||
if req.isFirstReq {
|
||||
// First request is a Read/ReadAt.
|
||||
if req.isReadOp {
|
||||
reqHeaders := NewGetReqHeaders()
|
||||
// Differentiate between wanting the whole object and just a range.
|
||||
if req.isReadAt {
|
||||
// If this is a ReadAt request only get the specified range.
|
||||
// Range is set with respect to the offset and length of the buffer requested.
|
||||
// Do not set objectInfo from the first readAt request because it will not get
|
||||
// the whole object.
|
||||
reqHeaders.SetRange(req.Offset, req.Offset+int64(len(req.Buffer))-1)
|
||||
httpReader, objectInfo, err = c.getObject(ctx, bucketName, objectName, reqHeaders)
|
||||
} else {
|
||||
if req.Offset > 0 {
|
||||
reqHeaders.SetRange(req.Offset, 0)
|
||||
}
|
||||
|
||||
// First request is a Read request.
|
||||
httpReader, objectInfo, err = c.getObject(ctx, bucketName, objectName, reqHeaders)
|
||||
opts.SetRange(req.Offset, req.Offset+int64(len(req.Buffer))-1)
|
||||
} else if req.Offset > 0 {
|
||||
opts.SetRange(req.Offset, 0)
|
||||
}
|
||||
httpReader, objectInfo, err = c.getObject(ctx, bucketName, objectName, opts)
|
||||
if err != nil {
|
||||
resCh <- getResponse{
|
||||
Error: err,
|
||||
}
|
||||
resCh <- getResponse{Error: err}
|
||||
return
|
||||
}
|
||||
etag = objectInfo.ETag
|
||||
// Read at least firstReq.Buffer bytes, if not we have
|
||||
// reached our EOF.
|
||||
size, err := io.ReadFull(httpReader, req.Buffer)
|
||||
if err == io.ErrUnexpectedEOF {
|
||||
if size > 0 && err == io.ErrUnexpectedEOF {
|
||||
// If an EOF happens after reading some but not
|
||||
// all the bytes ReadFull returns ErrUnexpectedEOF
|
||||
err = io.EOF
|
||||
@ -150,7 +126,7 @@ func (c Client) getObjectWithContext(ctx context.Context, bucketName, objectName
|
||||
} else {
|
||||
// First request is a Stat or Seek call.
|
||||
// Only need to run a StatObject until an actual Read or ReadAt request comes through.
|
||||
objectInfo, err = c.StatObject(bucketName, objectName)
|
||||
objectInfo, err = c.statObject(bucketName, objectName, StatObjectOptions{opts})
|
||||
if err != nil {
|
||||
resCh <- getResponse{
|
||||
Error: err,
|
||||
@ -165,11 +141,10 @@ func (c Client) getObjectWithContext(ctx context.Context, bucketName, objectName
|
||||
}
|
||||
}
|
||||
} else if req.settingObjectInfo { // Request is just to get objectInfo.
|
||||
reqHeaders := NewGetReqHeaders()
|
||||
if etag != "" {
|
||||
reqHeaders.SetMatchETag(etag)
|
||||
opts.SetMatchETag(etag)
|
||||
}
|
||||
objectInfo, err := c.statObject(bucketName, objectName, reqHeaders)
|
||||
objectInfo, err := c.statObject(bucketName, objectName, StatObjectOptions{opts})
|
||||
if err != nil {
|
||||
resCh <- getResponse{
|
||||
Error: err,
|
||||
@ -189,9 +164,8 @@ func (c Client) getObjectWithContext(ctx context.Context, bucketName, objectName
|
||||
// new ones when they haven't been already.
|
||||
// All readAt requests are new requests.
|
||||
if req.DidOffsetChange || !req.beenRead {
|
||||
reqHeaders := NewGetReqHeaders()
|
||||
if etag != "" {
|
||||
reqHeaders.SetMatchETag(etag)
|
||||
opts.SetMatchETag(etag)
|
||||
}
|
||||
if httpReader != nil {
|
||||
// Close previously opened http reader.
|
||||
@ -200,16 +174,11 @@ func (c Client) getObjectWithContext(ctx context.Context, bucketName, objectName
|
||||
// If this request is a readAt only get the specified range.
|
||||
if req.isReadAt {
|
||||
// Range is set with respect to the offset and length of the buffer requested.
|
||||
reqHeaders.SetRange(req.Offset, req.Offset+int64(len(req.Buffer))-1)
|
||||
httpReader, _, err = c.getObject(ctx, bucketName, objectName, reqHeaders)
|
||||
} else {
|
||||
// Range is set with respect to the offset.
|
||||
if req.Offset > 0 {
|
||||
reqHeaders.SetRange(req.Offset, 0)
|
||||
}
|
||||
|
||||
httpReader, objectInfo, err = c.getObject(ctx, bucketName, objectName, reqHeaders)
|
||||
opts.SetRange(req.Offset, req.Offset+int64(len(req.Buffer))-1)
|
||||
} else if req.Offset > 0 { // Range is set with respect to the offset.
|
||||
opts.SetRange(req.Offset, 0)
|
||||
}
|
||||
httpReader, objectInfo, err = c.getObject(ctx, bucketName, objectName, opts)
|
||||
if err != nil {
|
||||
resCh <- getResponse{
|
||||
Error: err,
|
||||
@ -632,7 +601,7 @@ func newObject(reqCh chan<- getRequest, resCh <-chan getResponse, doneCh chan<-
|
||||
//
|
||||
// For more information about the HTTP Range header.
|
||||
// go to http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35.
|
||||
func (c Client) getObject(ctx context.Context, bucketName, objectName string, reqHeaders RequestHeaders) (io.ReadCloser, ObjectInfo, error) {
|
||||
func (c Client) getObject(ctx context.Context, bucketName, objectName string, opts GetObjectOptions) (io.ReadCloser, ObjectInfo, error) {
|
||||
// Validate input arguments.
|
||||
if err := s3utils.CheckValidBucketName(bucketName); err != nil {
|
||||
return nil, ObjectInfo{}, err
|
||||
@ -641,17 +610,11 @@ func (c Client) getObject(ctx context.Context, bucketName, objectName string, re
|
||||
return nil, ObjectInfo{}, err
|
||||
}
|
||||
|
||||
// Set all the necessary reqHeaders.
|
||||
customHeader := make(http.Header)
|
||||
for key, value := range reqHeaders.Header {
|
||||
customHeader[key] = value
|
||||
}
|
||||
|
||||
// Execute GET on objectName.
|
||||
resp, err := c.executeMethod(ctx, "GET", requestMetadata{
|
||||
bucketName: bucketName,
|
||||
objectName: objectName,
|
||||
customHeader: customHeader,
|
||||
customHeader: opts.Header(),
|
||||
contentSHA256Bytes: emptySHA256,
|
||||
})
|
||||
if err != nil {
|
||||
@ -698,6 +661,15 @@ func (c Client) getObject(ctx context.Context, bucketName, objectName string, re
|
||||
Metadata: extractObjMetadata(resp.Header),
|
||||
}
|
||||
|
||||
reader := resp.Body
|
||||
if opts.Materials != nil {
|
||||
err = opts.Materials.SetupDecryptMode(reader, objectStat.Metadata.Get(amzHeaderIV), objectStat.Metadata.Get(amzHeaderKey))
|
||||
if err != nil {
|
||||
return nil, ObjectInfo{}, err
|
||||
}
|
||||
reader = opts.Materials
|
||||
}
|
||||
|
||||
// do not close body here, caller will close
|
||||
return resp.Body, objectStat, nil
|
||||
return reader, objectStat, nil
|
||||
}
|
||||
|
@ -20,80 +20,94 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/minio/minio-go/pkg/encrypt"
|
||||
)
|
||||
|
||||
// RequestHeaders - implement methods for setting special
|
||||
// request headers for GET, HEAD object operations.
|
||||
// http://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectGET.html
|
||||
type RequestHeaders struct {
|
||||
http.Header
|
||||
// GetObjectOptions are used to specify additional headers or options
|
||||
// during GET requests.
|
||||
type GetObjectOptions struct {
|
||||
headers map[string]string
|
||||
|
||||
Materials encrypt.Materials
|
||||
}
|
||||
|
||||
// NewGetReqHeaders - initializes a new request headers for GET request.
|
||||
func NewGetReqHeaders() RequestHeaders {
|
||||
return RequestHeaders{
|
||||
Header: make(http.Header),
|
||||
}
|
||||
// StatObjectOptions are used to specify additional headers or options
|
||||
// during GET info/stat requests.
|
||||
type StatObjectOptions struct {
|
||||
GetObjectOptions
|
||||
}
|
||||
|
||||
// NewHeadReqHeaders - initializes a new request headers for HEAD request.
|
||||
func NewHeadReqHeaders() RequestHeaders {
|
||||
return RequestHeaders{
|
||||
Header: make(http.Header),
|
||||
// Header returns the http.Header representation of the GET options.
|
||||
func (o GetObjectOptions) Header() http.Header {
|
||||
headers := make(http.Header, len(o.headers))
|
||||
for k, v := range o.headers {
|
||||
headers.Set(k, v)
|
||||
}
|
||||
return headers
|
||||
}
|
||||
|
||||
// Set adds a key value pair to the options. The
|
||||
// key-value pair will be part of the HTTP GET request
|
||||
// headers.
|
||||
func (o *GetObjectOptions) Set(key, value string) {
|
||||
if o.headers == nil {
|
||||
o.headers = make(map[string]string)
|
||||
}
|
||||
o.headers[http.CanonicalHeaderKey(key)] = value
|
||||
}
|
||||
|
||||
// SetMatchETag - set match etag.
|
||||
func (c RequestHeaders) SetMatchETag(etag string) error {
|
||||
func (o *GetObjectOptions) SetMatchETag(etag string) error {
|
||||
if etag == "" {
|
||||
return ErrInvalidArgument("ETag cannot be empty.")
|
||||
}
|
||||
c.Set("If-Match", "\""+etag+"\"")
|
||||
o.Set("If-Match", "\""+etag+"\"")
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetMatchETagExcept - set match etag except.
|
||||
func (c RequestHeaders) SetMatchETagExcept(etag string) error {
|
||||
func (o *GetObjectOptions) SetMatchETagExcept(etag string) error {
|
||||
if etag == "" {
|
||||
return ErrInvalidArgument("ETag cannot be empty.")
|
||||
}
|
||||
c.Set("If-None-Match", "\""+etag+"\"")
|
||||
o.Set("If-None-Match", "\""+etag+"\"")
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetUnmodified - set unmodified time since.
|
||||
func (c RequestHeaders) SetUnmodified(modTime time.Time) error {
|
||||
func (o *GetObjectOptions) SetUnmodified(modTime time.Time) error {
|
||||
if modTime.IsZero() {
|
||||
return ErrInvalidArgument("Modified since cannot be empty.")
|
||||
}
|
||||
c.Set("If-Unmodified-Since", modTime.Format(http.TimeFormat))
|
||||
o.Set("If-Unmodified-Since", modTime.Format(http.TimeFormat))
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetModified - set modified time since.
|
||||
func (c RequestHeaders) SetModified(modTime time.Time) error {
|
||||
func (o *GetObjectOptions) SetModified(modTime time.Time) error {
|
||||
if modTime.IsZero() {
|
||||
return ErrInvalidArgument("Modified since cannot be empty.")
|
||||
}
|
||||
c.Set("If-Modified-Since", modTime.Format(http.TimeFormat))
|
||||
o.Set("If-Modified-Since", modTime.Format(http.TimeFormat))
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetRange - set the start and end offset of the object to be read.
|
||||
// See https://tools.ietf.org/html/rfc7233#section-3.1 for reference.
|
||||
func (c RequestHeaders) SetRange(start, end int64) error {
|
||||
func (o *GetObjectOptions) SetRange(start, end int64) error {
|
||||
switch {
|
||||
case start == 0 && end < 0:
|
||||
// Read last '-end' bytes. `bytes=-N`.
|
||||
c.Set("Range", fmt.Sprintf("bytes=%d", end))
|
||||
o.Set("Range", fmt.Sprintf("bytes=%d", end))
|
||||
case 0 < start && end == 0:
|
||||
// Read everything starting from offset
|
||||
// 'start'. `bytes=N-`.
|
||||
c.Set("Range", fmt.Sprintf("bytes=%d-", start))
|
||||
o.Set("Range", fmt.Sprintf("bytes=%d-", start))
|
||||
case 0 <= start && start <= end:
|
||||
// Read everything starting at 'start' till the
|
||||
// 'end'. `bytes=N-M`
|
||||
c.Set("Range", fmt.Sprintf("bytes=%d-%d", start, end))
|
||||
o.Set("Range", fmt.Sprintf("bytes=%d-%d", start, end))
|
||||
default:
|
||||
// All other cases such as
|
||||
// bytes=-3-
|
2
vendor/github.com/minio/minio-go/api-s3-datatypes.go
generated
vendored
2
vendor/github.com/minio/minio-go/api-s3-datatypes.go
generated
vendored
@ -128,7 +128,7 @@ type initiator struct {
|
||||
// copyObjectResult container for copy object response.
|
||||
type copyObjectResult struct {
|
||||
ETag string
|
||||
LastModified string // time string format "2006-01-02T15:04:05.000Z"
|
||||
LastModified time.Time // time string format "2006-01-02T15:04:05.000Z"
|
||||
}
|
||||
|
||||
// ObjectPart container for particular part of an object.
|
||||
|
14
vendor/github.com/minio/minio-go/api-stat.go
generated
vendored
14
vendor/github.com/minio/minio-go/api-stat.go
generated
vendored
@ -81,7 +81,7 @@ func extractObjMetadata(header http.Header) http.Header {
|
||||
}
|
||||
|
||||
// StatObject verifies if object exists and you have permission to access.
|
||||
func (c Client) StatObject(bucketName, objectName string) (ObjectInfo, error) {
|
||||
func (c Client) StatObject(bucketName, objectName string, opts StatObjectOptions) (ObjectInfo, error) {
|
||||
// Input validation.
|
||||
if err := s3utils.CheckValidBucketName(bucketName); err != nil {
|
||||
return ObjectInfo{}, err
|
||||
@ -89,12 +89,11 @@ func (c Client) StatObject(bucketName, objectName string) (ObjectInfo, error) {
|
||||
if err := s3utils.CheckValidObjectName(objectName); err != nil {
|
||||
return ObjectInfo{}, err
|
||||
}
|
||||
reqHeaders := NewHeadReqHeaders()
|
||||
return c.statObject(bucketName, objectName, reqHeaders)
|
||||
return c.statObject(bucketName, objectName, opts)
|
||||
}
|
||||
|
||||
// Lower level API for statObject supporting pre-conditions and range headers.
|
||||
func (c Client) statObject(bucketName, objectName string, reqHeaders RequestHeaders) (ObjectInfo, error) {
|
||||
func (c Client) statObject(bucketName, objectName string, opts StatObjectOptions) (ObjectInfo, error) {
|
||||
// Input validation.
|
||||
if err := s3utils.CheckValidBucketName(bucketName); err != nil {
|
||||
return ObjectInfo{}, err
|
||||
@ -103,17 +102,12 @@ func (c Client) statObject(bucketName, objectName string, reqHeaders RequestHead
|
||||
return ObjectInfo{}, err
|
||||
}
|
||||
|
||||
customHeader := make(http.Header)
|
||||
for k, v := range reqHeaders.Header {
|
||||
customHeader[k] = v
|
||||
}
|
||||
|
||||
// Execute HEAD on objectName.
|
||||
resp, err := c.executeMethod(context.Background(), "HEAD", requestMetadata{
|
||||
bucketName: bucketName,
|
||||
objectName: objectName,
|
||||
contentSHA256Bytes: emptySHA256,
|
||||
customHeader: customHeader,
|
||||
customHeader: opts.Header(),
|
||||
})
|
||||
defer closeResponse(resp)
|
||||
if err != nil {
|
||||
|
32
vendor/github.com/minio/minio-go/core.go
generated
vendored
32
vendor/github.com/minio/minio-go/core.go
generated
vendored
@ -19,6 +19,7 @@ package minio
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/minio/minio-go/pkg/policy"
|
||||
)
|
||||
@ -53,9 +54,30 @@ func (c Core) ListObjectsV2(bucketName, objectPrefix, continuationToken string,
|
||||
return c.listObjectsV2Query(bucketName, objectPrefix, continuationToken, fetchOwner, delimiter, maxkeys)
|
||||
}
|
||||
|
||||
// CopyObject - copies an object from source object to destination object on server side.
|
||||
func (c Core) CopyObject(sourceBucket, sourceObject, destBucket, destObject string, metadata map[string]string) (ObjectInfo, error) {
|
||||
return c.copyObjectDo(context.Background(), sourceBucket, sourceObject, destBucket, destObject, metadata)
|
||||
}
|
||||
|
||||
// PutObject - Upload object. Uploads using single PUT call.
|
||||
func (c Core) PutObject(bucket, object string, data io.Reader, size int64, md5Sum, sha256Sum []byte, metadata map[string]string) (ObjectInfo, error) {
|
||||
return c.putObjectDo(context.Background(), bucket, object, data, md5Sum, sha256Sum, size, PutObjectOptions{UserMetadata: metadata})
|
||||
opts := PutObjectOptions{}
|
||||
m := make(map[string]string)
|
||||
for k, v := range metadata {
|
||||
if strings.ToLower(k) == "content-encoding" {
|
||||
opts.ContentEncoding = v
|
||||
} else if strings.ToLower(k) == "content-disposition" {
|
||||
opts.ContentDisposition = v
|
||||
} else if strings.ToLower(k) == "content-type" {
|
||||
opts.ContentType = v
|
||||
} else if strings.ToLower(k) == "cache-control" {
|
||||
opts.CacheControl = v
|
||||
} else {
|
||||
m[k] = metadata[k]
|
||||
}
|
||||
}
|
||||
opts.UserMetadata = m
|
||||
return c.putObjectDo(context.Background(), bucket, object, data, md5Sum, sha256Sum, size, opts)
|
||||
}
|
||||
|
||||
// NewMultipartUpload - Initiates new multipart upload and returns the new uploadID.
|
||||
@ -111,12 +133,12 @@ func (c Core) PutBucketPolicy(bucket string, bucketPolicy policy.BucketAccessPol
|
||||
// GetObject is a lower level API implemented to support reading
|
||||
// partial objects and also downloading objects with special conditions
|
||||
// matching etag, modtime etc.
|
||||
func (c Core) GetObject(bucketName, objectName string, reqHeaders RequestHeaders) (io.ReadCloser, ObjectInfo, error) {
|
||||
return c.getObject(context.Background(), bucketName, objectName, reqHeaders)
|
||||
func (c Core) GetObject(bucketName, objectName string, opts GetObjectOptions) (io.ReadCloser, ObjectInfo, error) {
|
||||
return c.getObject(context.Background(), bucketName, objectName, opts)
|
||||
}
|
||||
|
||||
// StatObject is a lower level API implemented to support special
|
||||
// conditions matching etag, modtime on a request.
|
||||
func (c Core) StatObject(bucketName, objectName string, reqHeaders RequestHeaders) (ObjectInfo, error) {
|
||||
return c.statObject(bucketName, objectName, reqHeaders)
|
||||
func (c Core) StatObject(bucketName, objectName string, opts StatObjectOptions) (ObjectInfo, error) {
|
||||
return c.statObject(bucketName, objectName, opts)
|
||||
}
|
||||
|
5889
vendor/github.com/minio/minio-go/functional_tests.go
generated
vendored
5889
vendor/github.com/minio/minio-go/functional_tests.go
generated
vendored
File diff suppressed because it is too large
Load Diff
6
vendor/vendor.json
vendored
6
vendor/vendor.json
vendored
@ -336,10 +336,10 @@
|
||||
"revisionTime": "2016-02-29T08:42:30-08:00"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "mqxOM3CsubB09O0nDEe4efu0JLQ=",
|
||||
"checksumSHA1": "EkdIh5Mk2bRiARtdoqUfnBuyndk=",
|
||||
"path": "github.com/minio/minio-go",
|
||||
"revision": "414c6b6a2e97428776cd831d9745589ebcf873e5",
|
||||
"revisionTime": "2017-09-27T19:03:45Z"
|
||||
"revision": "9690dc6c40e6ef271727848c04f974e801212ac1",
|
||||
"revisionTime": "2017-10-02T19:34:27Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "5juljGXPkBWENR2Os7dlnPQER48=",
|
||||
|
Loading…
x
Reference in New Issue
Block a user