storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/cmd/gateway/s3/gateway-s3.go (about)

     1  /*
     2   * MinIO Cloud Storage, (C) 2017-2020 MinIO, Inc.
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *     http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  
    17  package s3
    18  
    19  import (
    20  	"context"
    21  	"encoding/json"
    22  	"io"
    23  	"math/rand"
    24  	"net/http"
    25  	"net/url"
    26  	"strings"
    27  	"time"
    28  
    29  	"github.com/minio/cli"
    30  	miniogo "github.com/minio/minio-go/v7"
    31  	"github.com/minio/minio-go/v7/pkg/credentials"
    32  	"github.com/minio/minio-go/v7/pkg/encrypt"
    33  	"github.com/minio/minio-go/v7/pkg/s3utils"
    34  	"github.com/minio/minio-go/v7/pkg/tags"
    35  
    36  	minio "storj.io/minio/cmd"
    37  	xhttp "storj.io/minio/cmd/http"
    38  	"storj.io/minio/cmd/logger"
    39  	"storj.io/minio/pkg/auth"
    40  	"storj.io/minio/pkg/bucket/policy"
    41  	"storj.io/minio/pkg/madmin"
    42  )
    43  
    44  func init() {
    45  	const s3GatewayTemplate = `NAME:
    46    {{.HelpName}} - {{.Usage}}
    47  
    48  USAGE:
    49    {{.HelpName}} {{if .VisibleFlags}}[FLAGS]{{end}} [ENDPOINT]
    50  {{if .VisibleFlags}}
    51  FLAGS:
    52    {{range .VisibleFlags}}{{.}}
    53    {{end}}{{end}}
    54  ENDPOINT:
    55    s3 server endpoint. Default ENDPOINT is https://s3.amazonaws.com
    56  
    57  EXAMPLES:
    58    1. Start minio gateway server for AWS S3 backend
    59       {{.Prompt}} {{.EnvVarSetCommand}} MINIO_ROOT_USER{{.AssignmentOperator}}accesskey
    60       {{.Prompt}} {{.EnvVarSetCommand}} MINIO_ROOT_PASSWORD{{.AssignmentOperator}}secretkey
    61       {{.Prompt}} {{.HelpName}}
    62  
    63    2. Start minio gateway server for AWS S3 backend with edge caching enabled
    64       {{.Prompt}} {{.EnvVarSetCommand}} MINIO_ROOT_USER{{.AssignmentOperator}}accesskey
    65       {{.Prompt}} {{.EnvVarSetCommand}} MINIO_ROOT_PASSWORD{{.AssignmentOperator}}secretkey
    66       {{.Prompt}} {{.EnvVarSetCommand}} MINIO_CACHE_DRIVES{{.AssignmentOperator}}"/mnt/drive1,/mnt/drive2,/mnt/drive3,/mnt/drive4"
    67       {{.Prompt}} {{.EnvVarSetCommand}} MINIO_CACHE_EXCLUDE{{.AssignmentOperator}}"bucket1/*,*.png"
    68       {{.Prompt}} {{.EnvVarSetCommand}} MINIO_CACHE_QUOTA{{.AssignmentOperator}}90
    69       {{.Prompt}} {{.EnvVarSetCommand}} MINIO_CACHE_AFTER{{.AssignmentOperator}}3
    70       {{.Prompt}} {{.EnvVarSetCommand}} MINIO_CACHE_WATERMARK_LOW{{.AssignmentOperator}}75
    71       {{.Prompt}} {{.EnvVarSetCommand}} MINIO_CACHE_WATERMARK_HIGH{{.AssignmentOperator}}85
    72       {{.Prompt}} {{.HelpName}}
    73  `
    74  
    75  	minio.RegisterGatewayCommand(cli.Command{
    76  		Name:               minio.S3BackendGateway,
    77  		Usage:              "Amazon Simple Storage Service (S3)",
    78  		Action:             s3GatewayMain,
    79  		CustomHelpTemplate: s3GatewayTemplate,
    80  		HideHelpCommand:    true,
    81  	})
    82  }
    83  
    84  // Handler for 'minio gateway s3' command line.
    85  func s3GatewayMain(ctx *cli.Context) {
    86  	args := ctx.Args()
    87  	if !ctx.Args().Present() {
    88  		args = cli.Args{"https://s3.amazonaws.com"}
    89  	}
    90  
    91  	serverAddr := ctx.GlobalString("address")
    92  	if serverAddr == "" || serverAddr == ":"+minio.GlobalMinioDefaultPort {
    93  		serverAddr = ctx.String("address")
    94  	}
    95  	// Validate gateway arguments.
    96  	logger.FatalIf(minio.ValidateGatewayArguments(serverAddr, args.First()), "Invalid argument")
    97  
    98  	// Start the gateway..
    99  	minio.StartGateway(ctx, &S3{args.First()})
   100  }
   101  
   102  // S3 implements Gateway.
   103  type S3 struct {
   104  	host string
   105  }
   106  
   107  // Name implements Gateway interface.
   108  func (g *S3) Name() string {
   109  	return minio.S3BackendGateway
   110  }
   111  
   112  const letterBytes = "abcdefghijklmnopqrstuvwxyz01234569"
   113  const (
   114  	letterIdxBits = 6                    // 6 bits to represent a letter index
   115  	letterIdxMask = 1<<letterIdxBits - 1 // All 1-bits, as many as letterIdxBits
   116  	letterIdxMax  = 63 / letterIdxBits   // # of letter indices fitting in 63 bits
   117  )
   118  
   119  // randString generates random names and prepends them with a known prefix.
   120  func randString(n int, src rand.Source, prefix string) string {
   121  	b := make([]byte, n)
   122  	// A rand.Int63() generates 63 random bits, enough for letterIdxMax letters!
   123  	for i, cache, remain := n-1, src.Int63(), letterIdxMax; i >= 0; {
   124  		if remain == 0 {
   125  			cache, remain = src.Int63(), letterIdxMax
   126  		}
   127  		if idx := int(cache & letterIdxMask); idx < len(letterBytes) {
   128  			b[i] = letterBytes[idx]
   129  			i--
   130  		}
   131  		cache >>= letterIdxBits
   132  		remain--
   133  	}
   134  	return prefix + string(b[0:30-len(prefix)])
   135  }
   136  
   137  // Chains all credential types, in the following order:
   138  //  - AWS env vars (i.e. AWS_ACCESS_KEY_ID)
   139  //  - AWS creds file (i.e. AWS_SHARED_CREDENTIALS_FILE or ~/.aws/credentials)
   140  //  - Static credentials provided by user (i.e. MINIO_ROOT_USER/MINIO_ACCESS_KEY)
   141  var defaultProviders = []credentials.Provider{
   142  	&credentials.EnvAWS{},
   143  	&credentials.FileAWSCredentials{},
   144  	&credentials.EnvMinio{},
   145  }
   146  
   147  // Chains all credential types, in the following order:
   148  //  - AWS env vars (i.e. AWS_ACCESS_KEY_ID)
   149  //  - AWS creds file (i.e. AWS_SHARED_CREDENTIALS_FILE or ~/.aws/credentials)
   150  //  - IAM profile based credentials. (performs an HTTP
   151  //    call to a pre-defined endpoint, only valid inside
   152  //    configured ec2 instances)
   153  //  - Static credentials provided by user (i.e. MINIO_ROOT_USER/MINIO_ACCESS_KEY)
   154  var defaultAWSCredProviders = []credentials.Provider{
   155  	&credentials.EnvAWS{},
   156  	&credentials.FileAWSCredentials{},
   157  	&credentials.IAM{
   158  		Client: &http.Client{
   159  			Transport: minio.NewGatewayHTTPTransport(),
   160  		},
   161  	},
   162  	&credentials.EnvMinio{},
   163  }
   164  
   165  // newS3 - Initializes a new client by auto probing S3 server signature.
   166  func newS3(urlStr string, tripper http.RoundTripper) (*miniogo.Core, error) {
   167  	if urlStr == "" {
   168  		urlStr = "https://s3.amazonaws.com"
   169  	}
   170  
   171  	u, err := url.Parse(urlStr)
   172  	if err != nil {
   173  		return nil, err
   174  	}
   175  
   176  	// Override default params if the host is provided
   177  	endpoint, secure, err := minio.ParseGatewayEndpoint(urlStr)
   178  	if err != nil {
   179  		return nil, err
   180  	}
   181  
   182  	var creds *credentials.Credentials
   183  	if s3utils.IsAmazonEndpoint(*u) {
   184  		// If we see an Amazon S3 endpoint, then we use more ways to fetch backend credentials.
   185  		// Specifically IAM style rotating credentials are only supported with AWS S3 endpoint.
   186  		creds = credentials.NewChainCredentials(defaultAWSCredProviders)
   187  
   188  	} else {
   189  		creds = credentials.NewChainCredentials(defaultProviders)
   190  	}
   191  
   192  	options := &miniogo.Options{
   193  		Creds:        creds,
   194  		Secure:       secure,
   195  		Region:       s3utils.GetRegionFromURL(*u),
   196  		BucketLookup: miniogo.BucketLookupAuto,
   197  		Transport:    tripper,
   198  	}
   199  
   200  	clnt, err := miniogo.New(endpoint, options)
   201  	if err != nil {
   202  		return nil, err
   203  	}
   204  
   205  	return &miniogo.Core{Client: clnt}, nil
   206  }
   207  
   208  // NewGatewayLayer returns s3 ObjectLayer.
   209  func (g *S3) NewGatewayLayer(creds auth.Credentials) (minio.ObjectLayer, error) {
   210  	metrics := minio.NewMetrics()
   211  
   212  	t := &minio.MetricsTransport{
   213  		Transport: minio.NewGatewayHTTPTransport(),
   214  		Metrics:   metrics,
   215  	}
   216  
   217  	// creds are ignored here, since S3 gateway implements chaining
   218  	// all credentials.
   219  	clnt, err := newS3(g.host, t)
   220  	if err != nil {
   221  		return nil, err
   222  	}
   223  
   224  	probeBucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "probe-bucket-sign-")
   225  
   226  	// Check if the provided keys are valid.
   227  	if _, err = clnt.BucketExists(context.Background(), probeBucketName); err != nil {
   228  		if miniogo.ToErrorResponse(err).Code != "AccessDenied" {
   229  			return nil, err
   230  		}
   231  	}
   232  
   233  	s := s3Objects{
   234  		Client:  clnt,
   235  		Metrics: metrics,
   236  		HTTPClient: &http.Client{
   237  			Transport: t,
   238  		},
   239  	}
   240  
   241  	// Enables single encryption of KMS is configured.
   242  	if minio.GlobalKMS != nil {
   243  		encS := s3EncObjects{s}
   244  
   245  		// Start stale enc multipart uploads cleanup routine.
   246  		go encS.cleanupStaleEncMultipartUploads(minio.GlobalContext,
   247  			minio.GlobalStaleUploadsCleanupInterval, minio.GlobalStaleUploadsExpiry)
   248  
   249  		return &encS, nil
   250  	}
   251  	return &s, nil
   252  }
   253  
   254  // Production - s3 gateway is production ready.
   255  func (g *S3) Production() bool {
   256  	return true
   257  }
   258  
   259  // s3Objects implements gateway for MinIO and S3 compatible object storage servers.
   260  type s3Objects struct {
   261  	minio.GatewayUnsupported
   262  	Client     *miniogo.Core
   263  	HTTPClient *http.Client
   264  	Metrics    *minio.BackendMetrics
   265  }
   266  
   267  // GetMetrics returns this gateway's metrics
   268  func (l *s3Objects) GetMetrics(ctx context.Context) (*minio.BackendMetrics, error) {
   269  	return l.Metrics, nil
   270  }
   271  
   272  // Shutdown saves any gateway metadata to disk
   273  // if necessary and reload upon next restart.
   274  func (l *s3Objects) Shutdown(ctx context.Context) error {
   275  	return nil
   276  }
   277  
   278  // StorageInfo is not relevant to S3 backend.
   279  func (l *s3Objects) StorageInfo(ctx context.Context) (si minio.StorageInfo, _ []error) {
   280  	si.Backend.Type = madmin.Gateway
   281  	host := l.Client.EndpointURL().Host
   282  	if l.Client.EndpointURL().Port() == "" {
   283  		host = l.Client.EndpointURL().Host + ":" + l.Client.EndpointURL().Scheme
   284  	}
   285  	si.Backend.GatewayOnline = minio.IsBackendOnline(ctx, host)
   286  	return si, nil
   287  }
   288  
   289  // MakeBucket creates a new container on S3 backend.
   290  func (l *s3Objects) MakeBucketWithLocation(ctx context.Context, bucket string, opts minio.BucketOptions) error {
   291  	if opts.LockEnabled || opts.VersioningEnabled {
   292  		return minio.NotImplemented{}
   293  	}
   294  
   295  	// Verify if bucket name is valid.
   296  	// We are using a separate helper function here to validate bucket
   297  	// names instead of IsValidBucketName() because there is a possibility
   298  	// that certains users might have buckets which are non-DNS compliant
   299  	// in us-east-1 and we might severely restrict them by not allowing
   300  	// access to these buckets.
   301  	// Ref - http://docs.aws.amazon.com/AmazonS3/latest/dev/BucketRestrictions.html
   302  	if s3utils.CheckValidBucketName(bucket) != nil {
   303  		return minio.BucketNameInvalid{Bucket: bucket}
   304  	}
   305  	err := l.Client.MakeBucket(ctx, bucket, miniogo.MakeBucketOptions{Region: opts.Location})
   306  	if err != nil {
   307  		return minio.ErrorRespToObjectError(err, bucket)
   308  	}
   309  	return err
   310  }
   311  
   312  // GetBucketInfo gets bucket metadata..
   313  func (l *s3Objects) GetBucketInfo(ctx context.Context, bucket string) (bi minio.BucketInfo, e error) {
   314  	buckets, err := l.Client.ListBuckets(ctx)
   315  	if err != nil {
   316  		// Listbuckets may be disallowed, proceed to check if
   317  		// bucket indeed exists, if yes return success.
   318  		var ok bool
   319  		if ok, err = l.Client.BucketExists(ctx, bucket); err != nil {
   320  			return bi, minio.ErrorRespToObjectError(err, bucket)
   321  		}
   322  		if !ok {
   323  			return bi, minio.BucketNotFound{Bucket: bucket}
   324  		}
   325  		return minio.BucketInfo{
   326  			Name:    bi.Name,
   327  			Created: time.Now().UTC(),
   328  		}, nil
   329  	}
   330  
   331  	for _, bi := range buckets {
   332  		if bi.Name != bucket {
   333  			continue
   334  		}
   335  
   336  		return minio.BucketInfo{
   337  			Name:    bi.Name,
   338  			Created: bi.CreationDate,
   339  		}, nil
   340  	}
   341  
   342  	return bi, minio.BucketNotFound{Bucket: bucket}
   343  }
   344  
   345  // ListBuckets lists all S3 buckets
   346  func (l *s3Objects) ListBuckets(ctx context.Context) ([]minio.BucketInfo, error) {
   347  	buckets, err := l.Client.ListBuckets(ctx)
   348  	if err != nil {
   349  		return nil, minio.ErrorRespToObjectError(err)
   350  	}
   351  
   352  	b := make([]minio.BucketInfo, len(buckets))
   353  	for i, bi := range buckets {
   354  		b[i] = minio.BucketInfo{
   355  			Name:    bi.Name,
   356  			Created: bi.CreationDate,
   357  		}
   358  	}
   359  
   360  	return b, err
   361  }
   362  
   363  // DeleteBucket deletes a bucket on S3
   364  func (l *s3Objects) DeleteBucket(ctx context.Context, bucket string, forceDelete bool) error {
   365  	err := l.Client.RemoveBucket(ctx, bucket)
   366  	if err != nil {
   367  		return minio.ErrorRespToObjectError(err, bucket)
   368  	}
   369  	return nil
   370  }
   371  
   372  // ListObjects lists all blobs in S3 bucket filtered by prefix
   373  func (l *s3Objects) ListObjects(ctx context.Context, bucket string, prefix string, marker string, delimiter string, maxKeys int) (loi minio.ListObjectsInfo, e error) {
   374  	result, err := l.Client.ListObjects(bucket, prefix, marker, delimiter, maxKeys)
   375  	if err != nil {
   376  		return loi, minio.ErrorRespToObjectError(err, bucket)
   377  	}
   378  
   379  	return minio.FromMinioClientListBucketResult(bucket, result), nil
   380  }
   381  
   382  // ListObjectsV2 lists all blobs in S3 bucket filtered by prefix
   383  func (l *s3Objects) ListObjectsV2(ctx context.Context, bucket, prefix, continuationToken, delimiter string, maxKeys int, fetchOwner bool, startAfter string) (loi minio.ListObjectsV2Info, e error) {
   384  	result, err := l.Client.ListObjectsV2(bucket, prefix, continuationToken, fetchOwner, delimiter, maxKeys)
   385  	if err != nil {
   386  		return loi, minio.ErrorRespToObjectError(err, bucket)
   387  	}
   388  
   389  	return minio.FromMinioClientListBucketV2Result(bucket, result), nil
   390  }
   391  
   392  // GetObjectNInfo - returns object info and locked object ReadCloser
   393  func (l *s3Objects) GetObjectNInfo(ctx context.Context, bucket, object string, rs *minio.HTTPRangeSpec, h http.Header, lockType minio.LockType, opts minio.ObjectOptions) (gr *minio.GetObjectReader, err error) {
   394  	var objInfo minio.ObjectInfo
   395  	objInfo, err = l.GetObjectInfo(ctx, bucket, object, opts)
   396  	if err != nil {
   397  		return nil, minio.ErrorRespToObjectError(err, bucket, object)
   398  	}
   399  
   400  	fn, off, length, err := minio.NewGetObjectReader(rs, objInfo, opts)
   401  	if err != nil {
   402  		return nil, minio.ErrorRespToObjectError(err, bucket, object)
   403  	}
   404  
   405  	pr, pw := io.Pipe()
   406  	go func() {
   407  		err := l.getObject(ctx, bucket, object, off, length, pw, objInfo.ETag, opts)
   408  		pw.CloseWithError(err)
   409  	}()
   410  
   411  	// Setup cleanup function to cause the above go-routine to
   412  	// exit in case of partial read
   413  	pipeCloser := func() { pr.Close() }
   414  	return fn(pr, h, opts.CheckPrecondFn, pipeCloser)
   415  }
   416  
   417  // GetObject reads an object from S3. Supports additional
   418  // parameters like offset and length which are synonymous with
   419  // HTTP Range requests.
   420  //
   421  // startOffset indicates the starting read location of the object.
   422  // length indicates the total length of the object.
   423  func (l *s3Objects) getObject(ctx context.Context, bucket string, key string, startOffset int64, length int64, writer io.Writer, etag string, o minio.ObjectOptions) error {
   424  	if length < 0 && length != -1 {
   425  		return minio.ErrorRespToObjectError(minio.InvalidRange{}, bucket, key)
   426  	}
   427  
   428  	opts := miniogo.GetObjectOptions{}
   429  	opts.ServerSideEncryption = o.ServerSideEncryption
   430  
   431  	if startOffset >= 0 && length >= 0 {
   432  		if err := opts.SetRange(startOffset, startOffset+length-1); err != nil {
   433  			return minio.ErrorRespToObjectError(err, bucket, key)
   434  		}
   435  	}
   436  
   437  	if etag != "" {
   438  		opts.SetMatchETag(etag)
   439  	}
   440  
   441  	object, _, _, err := l.Client.GetObject(ctx, bucket, key, opts)
   442  	if err != nil {
   443  		return minio.ErrorRespToObjectError(err, bucket, key)
   444  	}
   445  	defer object.Close()
   446  	if _, err := io.Copy(writer, object); err != nil {
   447  		return minio.ErrorRespToObjectError(err, bucket, key)
   448  	}
   449  	return nil
   450  }
   451  
   452  // GetObjectInfo reads object info and replies back ObjectInfo
   453  func (l *s3Objects) GetObjectInfo(ctx context.Context, bucket string, object string, opts minio.ObjectOptions) (objInfo minio.ObjectInfo, err error) {
   454  	oi, err := l.Client.StatObject(ctx, bucket, object, miniogo.StatObjectOptions{
   455  		ServerSideEncryption: opts.ServerSideEncryption,
   456  	})
   457  	if err != nil {
   458  		return minio.ObjectInfo{}, minio.ErrorRespToObjectError(err, bucket, object)
   459  	}
   460  
   461  	return minio.FromMinioClientObjectInfo(bucket, oi), nil
   462  }
   463  
   464  // PutObject creates a new object with the incoming data,
   465  func (l *s3Objects) PutObject(ctx context.Context, bucket string, object string, r *minio.PutObjReader, opts minio.ObjectOptions) (objInfo minio.ObjectInfo, err error) {
   466  	data := r.Reader
   467  	var tagMap map[string]string
   468  	if tagstr, ok := opts.UserDefined[xhttp.AmzObjectTagging]; ok && tagstr != "" {
   469  		tagObj, err := tags.ParseObjectTags(tagstr)
   470  		if err != nil {
   471  			return objInfo, minio.ErrorRespToObjectError(err, bucket, object)
   472  		}
   473  		tagMap = tagObj.ToMap()
   474  		delete(opts.UserDefined, xhttp.AmzObjectTagging)
   475  	}
   476  	putOpts := miniogo.PutObjectOptions{
   477  		UserMetadata:         opts.UserDefined,
   478  		ServerSideEncryption: opts.ServerSideEncryption,
   479  		UserTags:             tagMap,
   480  	}
   481  	ui, err := l.Client.PutObject(ctx, bucket, object, data, data.Size(), data.MD5Base64String(), data.SHA256HexString(), putOpts)
   482  	if err != nil {
   483  		return objInfo, minio.ErrorRespToObjectError(err, bucket, object)
   484  	}
   485  	// On success, populate the key & metadata so they are present in the notification
   486  	oi := miniogo.ObjectInfo{
   487  		ETag:     ui.ETag,
   488  		Size:     ui.Size,
   489  		Key:      object,
   490  		Metadata: minio.ToMinioClientObjectInfoMetadata(opts.UserDefined),
   491  	}
   492  
   493  	return minio.FromMinioClientObjectInfo(bucket, oi), nil
   494  }
   495  
   496  // CopyObject copies an object from source bucket to a destination bucket.
   497  func (l *s3Objects) CopyObject(ctx context.Context, srcBucket string, srcObject string, dstBucket string, dstObject string, srcInfo minio.ObjectInfo, srcOpts, dstOpts minio.ObjectOptions) (objInfo minio.ObjectInfo, err error) {
   498  	if srcOpts.CheckPrecondFn != nil && srcOpts.CheckPrecondFn(srcInfo) {
   499  		return minio.ObjectInfo{}, minio.PreConditionFailed{}
   500  	}
   501  	// Set this header such that following CopyObject() always sets the right metadata on the destination.
   502  	// metadata input is already a trickled down value from interpreting x-amz-metadata-directive at
   503  	// handler layer. So what we have right now is supposed to be applied on the destination object anyways.
   504  	// So preserve it by adding "REPLACE" directive to save all the metadata set by CopyObject API.
   505  	srcInfo.UserDefined["x-amz-metadata-directive"] = "REPLACE"
   506  	srcInfo.UserDefined["x-amz-copy-source-if-match"] = srcInfo.ETag
   507  	header := make(http.Header)
   508  	if srcOpts.ServerSideEncryption != nil {
   509  		encrypt.SSECopy(srcOpts.ServerSideEncryption).Marshal(header)
   510  	}
   511  
   512  	if dstOpts.ServerSideEncryption != nil {
   513  		dstOpts.ServerSideEncryption.Marshal(header)
   514  	}
   515  
   516  	for k, v := range header {
   517  		srcInfo.UserDefined[k] = v[0]
   518  	}
   519  
   520  	if _, err = l.Client.CopyObject(ctx, srcBucket, srcObject, dstBucket, dstObject, srcInfo.UserDefined, miniogo.CopySrcOptions{}, miniogo.PutObjectOptions{}); err != nil {
   521  		return objInfo, minio.ErrorRespToObjectError(err, srcBucket, srcObject)
   522  	}
   523  	return l.GetObjectInfo(ctx, dstBucket, dstObject, dstOpts)
   524  }
   525  
   526  // DeleteObject deletes a blob in bucket
   527  func (l *s3Objects) DeleteObject(ctx context.Context, bucket string, object string, opts minio.ObjectOptions) (minio.ObjectInfo, error) {
   528  	err := l.Client.RemoveObject(ctx, bucket, object, miniogo.RemoveObjectOptions{})
   529  	if err != nil {
   530  		return minio.ObjectInfo{}, minio.ErrorRespToObjectError(err, bucket, object)
   531  	}
   532  
   533  	return minio.ObjectInfo{
   534  		Bucket: bucket,
   535  		Name:   object,
   536  	}, nil
   537  }
   538  
   539  func (l *s3Objects) DeleteObjects(ctx context.Context, bucket string, objects []minio.ObjectToDelete, opts minio.ObjectOptions) ([]minio.DeletedObject, []error) {
   540  	errs := make([]error, len(objects))
   541  	dobjects := make([]minio.DeletedObject, len(objects))
   542  	for idx, object := range objects {
   543  		_, errs[idx] = l.DeleteObject(ctx, bucket, object.ObjectName, opts)
   544  		if errs[idx] == nil {
   545  			dobjects[idx] = minio.DeletedObject{
   546  				ObjectName: object.ObjectName,
   547  			}
   548  		}
   549  	}
   550  	return dobjects, errs
   551  }
   552  
   553  // ListMultipartUploads lists all multipart uploads.
   554  func (l *s3Objects) ListMultipartUploads(ctx context.Context, bucket string, prefix string, keyMarker string, uploadIDMarker string, delimiter string, maxUploads int) (lmi minio.ListMultipartsInfo, e error) {
   555  	result, err := l.Client.ListMultipartUploads(ctx, bucket, prefix, keyMarker, uploadIDMarker, delimiter, maxUploads)
   556  	if err != nil {
   557  		return lmi, err
   558  	}
   559  
   560  	return minio.FromMinioClientListMultipartsInfo(result), nil
   561  }
   562  
   563  // NewMultipartUpload upload object in multiple parts
   564  func (l *s3Objects) NewMultipartUpload(ctx context.Context, bucket string, object string, o minio.ObjectOptions) (uploadID string, err error) {
   565  	var tagMap map[string]string
   566  	if tagStr, ok := o.UserDefined[xhttp.AmzObjectTagging]; ok {
   567  		tagObj, err := tags.Parse(tagStr, true)
   568  		if err != nil {
   569  			return uploadID, minio.ErrorRespToObjectError(err, bucket, object)
   570  		}
   571  		tagMap = tagObj.ToMap()
   572  		delete(o.UserDefined, xhttp.AmzObjectTagging)
   573  	}
   574  	// Create PutObject options
   575  	opts := miniogo.PutObjectOptions{
   576  		UserMetadata:         o.UserDefined,
   577  		ServerSideEncryption: o.ServerSideEncryption,
   578  		UserTags:             tagMap,
   579  	}
   580  	uploadID, err = l.Client.NewMultipartUpload(ctx, bucket, object, opts)
   581  	if err != nil {
   582  		return uploadID, minio.ErrorRespToObjectError(err, bucket, object)
   583  	}
   584  	return uploadID, nil
   585  }
   586  
   587  // PutObjectPart puts a part of object in bucket
   588  func (l *s3Objects) PutObjectPart(ctx context.Context, bucket string, object string, uploadID string, partID int, r *minio.PutObjReader, opts minio.ObjectOptions) (pi minio.PartInfo, e error) {
   589  	data := r.Reader
   590  	info, err := l.Client.PutObjectPart(ctx, bucket, object, uploadID, partID, data, data.Size(), data.MD5Base64String(), data.SHA256HexString(), opts.ServerSideEncryption)
   591  	if err != nil {
   592  		return pi, minio.ErrorRespToObjectError(err, bucket, object)
   593  	}
   594  
   595  	return minio.FromMinioClientObjectPart(info), nil
   596  }
   597  
   598  // CopyObjectPart creates a part in a multipart upload by copying
   599  // existing object or a part of it.
   600  func (l *s3Objects) CopyObjectPart(ctx context.Context, srcBucket, srcObject, destBucket, destObject, uploadID string,
   601  	partID int, startOffset, length int64, srcInfo minio.ObjectInfo, srcOpts, dstOpts minio.ObjectOptions) (p minio.PartInfo, err error) {
   602  	if srcOpts.CheckPrecondFn != nil && srcOpts.CheckPrecondFn(srcInfo) {
   603  		return minio.PartInfo{}, minio.PreConditionFailed{}
   604  	}
   605  	srcInfo.UserDefined = map[string]string{
   606  		"x-amz-copy-source-if-match": srcInfo.ETag,
   607  	}
   608  	header := make(http.Header)
   609  	if srcOpts.ServerSideEncryption != nil {
   610  		encrypt.SSECopy(srcOpts.ServerSideEncryption).Marshal(header)
   611  	}
   612  
   613  	if dstOpts.ServerSideEncryption != nil {
   614  		dstOpts.ServerSideEncryption.Marshal(header)
   615  	}
   616  	for k, v := range header {
   617  		srcInfo.UserDefined[k] = v[0]
   618  	}
   619  
   620  	completePart, err := l.Client.CopyObjectPart(ctx, srcBucket, srcObject, destBucket, destObject,
   621  		uploadID, partID, startOffset, length, srcInfo.UserDefined)
   622  	if err != nil {
   623  		return p, minio.ErrorRespToObjectError(err, srcBucket, srcObject)
   624  	}
   625  	p.PartNumber = completePart.PartNumber
   626  	p.ETag = completePart.ETag
   627  	return p, nil
   628  }
   629  
   630  // GetMultipartInfo returns multipart info of the uploadId of the object
   631  func (l *s3Objects) GetMultipartInfo(ctx context.Context, bucket, object, uploadID string, opts minio.ObjectOptions) (result minio.MultipartInfo, err error) {
   632  	result.Bucket = bucket
   633  	result.Object = object
   634  	result.UploadID = uploadID
   635  	return result, nil
   636  }
   637  
   638  // ListObjectParts returns all object parts for specified object in specified bucket
   639  func (l *s3Objects) ListObjectParts(ctx context.Context, bucket string, object string, uploadID string, partNumberMarker int, maxParts int, opts minio.ObjectOptions) (lpi minio.ListPartsInfo, e error) {
   640  	result, err := l.Client.ListObjectParts(ctx, bucket, object, uploadID, partNumberMarker, maxParts)
   641  	if err != nil {
   642  		return lpi, err
   643  	}
   644  	lpi = minio.FromMinioClientListPartsInfo(result)
   645  	if lpi.IsTruncated && maxParts > len(lpi.Parts) {
   646  		partNumberMarker = lpi.NextPartNumberMarker
   647  		for {
   648  			result, err = l.Client.ListObjectParts(ctx, bucket, object, uploadID, partNumberMarker, maxParts)
   649  			if err != nil {
   650  				return lpi, err
   651  			}
   652  
   653  			nlpi := minio.FromMinioClientListPartsInfo(result)
   654  
   655  			partNumberMarker = nlpi.NextPartNumberMarker
   656  
   657  			lpi.Parts = append(lpi.Parts, nlpi.Parts...)
   658  			if !nlpi.IsTruncated {
   659  				break
   660  			}
   661  		}
   662  	}
   663  	return lpi, nil
   664  }
   665  
   666  // AbortMultipartUpload aborts a ongoing multipart upload
   667  func (l *s3Objects) AbortMultipartUpload(ctx context.Context, bucket string, object string, uploadID string, opts minio.ObjectOptions) error {
   668  	err := l.Client.AbortMultipartUpload(ctx, bucket, object, uploadID)
   669  	return minio.ErrorRespToObjectError(err, bucket, object)
   670  }
   671  
   672  // CompleteMultipartUpload completes ongoing multipart upload and finalizes object
   673  func (l *s3Objects) CompleteMultipartUpload(ctx context.Context, bucket string, object string, uploadID string, uploadedParts []minio.CompletePart, opts minio.ObjectOptions) (oi minio.ObjectInfo, e error) {
   674  	etag, err := l.Client.CompleteMultipartUpload(ctx, bucket, object, uploadID, minio.ToMinioClientCompleteParts(uploadedParts))
   675  	if err != nil {
   676  		return oi, minio.ErrorRespToObjectError(err, bucket, object)
   677  	}
   678  
   679  	return minio.ObjectInfo{Bucket: bucket, Name: object, ETag: strings.Trim(etag, "\"")}, nil
   680  }
   681  
   682  // SetBucketPolicy sets policy on bucket
   683  func (l *s3Objects) SetBucketPolicy(ctx context.Context, bucket string, bucketPolicy *policy.Policy) error {
   684  	data, err := json.Marshal(bucketPolicy)
   685  	if err != nil {
   686  		// This should not happen.
   687  		logger.LogIf(ctx, err)
   688  		return minio.ErrorRespToObjectError(err, bucket)
   689  	}
   690  
   691  	if err := l.Client.SetBucketPolicy(ctx, bucket, string(data)); err != nil {
   692  		return minio.ErrorRespToObjectError(err, bucket)
   693  	}
   694  
   695  	return nil
   696  }
   697  
   698  // GetBucketPolicy will get policy on bucket
   699  func (l *s3Objects) GetBucketPolicy(ctx context.Context, bucket string) (*policy.Policy, error) {
   700  	data, err := l.Client.GetBucketPolicy(ctx, bucket)
   701  	if err != nil {
   702  		return nil, minio.ErrorRespToObjectError(err, bucket)
   703  	}
   704  
   705  	bucketPolicy, err := policy.ParseConfig(strings.NewReader(data), bucket)
   706  	return bucketPolicy, minio.ErrorRespToObjectError(err, bucket)
   707  }
   708  
   709  // DeleteBucketPolicy deletes all policies on bucket
   710  func (l *s3Objects) DeleteBucketPolicy(ctx context.Context, bucket string) error {
   711  	if err := l.Client.SetBucketPolicy(ctx, bucket, ""); err != nil {
   712  		return minio.ErrorRespToObjectError(err, bucket, "")
   713  	}
   714  	return nil
   715  }
   716  
   717  // GetObjectTags gets the tags set on the object
   718  func (l *s3Objects) GetObjectTags(ctx context.Context, bucket string, object string, opts minio.ObjectOptions) (*tags.Tags, error) {
   719  	var err error
   720  	if _, err = l.GetObjectInfo(ctx, bucket, object, opts); err != nil {
   721  		return nil, minio.ErrorRespToObjectError(err, bucket, object)
   722  	}
   723  
   724  	t, err := l.Client.GetObjectTagging(ctx, bucket, object, miniogo.GetObjectTaggingOptions{})
   725  	if err != nil {
   726  		return nil, minio.ErrorRespToObjectError(err, bucket, object)
   727  	}
   728  
   729  	return t, nil
   730  }
   731  
   732  // PutObjectTags attaches the tags to the object
   733  func (l *s3Objects) PutObjectTags(ctx context.Context, bucket, object string, tagStr string, opts minio.ObjectOptions) (minio.ObjectInfo, error) {
   734  	tagObj, err := tags.Parse(tagStr, true)
   735  	if err != nil {
   736  		return minio.ObjectInfo{}, minio.ErrorRespToObjectError(err, bucket, object)
   737  	}
   738  	if err = l.Client.PutObjectTagging(ctx, bucket, object, tagObj, miniogo.PutObjectTaggingOptions{VersionID: opts.VersionID}); err != nil {
   739  		return minio.ObjectInfo{}, minio.ErrorRespToObjectError(err, bucket, object)
   740  	}
   741  
   742  	objInfo, err := l.GetObjectInfo(ctx, bucket, object, opts)
   743  	if err != nil {
   744  		return minio.ObjectInfo{}, minio.ErrorRespToObjectError(err, bucket, object)
   745  	}
   746  
   747  	return objInfo, nil
   748  }
   749  
   750  // DeleteObjectTags removes the tags attached to the object
   751  func (l *s3Objects) DeleteObjectTags(ctx context.Context, bucket, object string, opts minio.ObjectOptions) (minio.ObjectInfo, error) {
   752  	if err := l.Client.RemoveObjectTagging(ctx, bucket, object, miniogo.RemoveObjectTaggingOptions{}); err != nil {
   753  		return minio.ObjectInfo{}, minio.ErrorRespToObjectError(err, bucket, object)
   754  	}
   755  	objInfo, err := l.GetObjectInfo(ctx, bucket, object, opts)
   756  	if err != nil {
   757  		return minio.ObjectInfo{}, minio.ErrorRespToObjectError(err, bucket, object)
   758  	}
   759  
   760  	return objInfo, nil
   761  }
   762  
   763  // IsCompressionSupported returns whether compression is applicable for this layer.
   764  func (l *s3Objects) IsCompressionSupported() bool {
   765  	return false
   766  }
   767  
   768  // IsEncryptionSupported returns whether server side encryption is implemented for this layer.
   769  func (l *s3Objects) IsEncryptionSupported() bool {
   770  	return minio.GlobalKMS != nil || minio.GlobalGatewaySSE.IsSet()
   771  }
   772  
   773  func (l *s3Objects) IsTaggingSupported() bool {
   774  	return true
   775  }