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 }