From a1de9cec589d20c521659f5db51106a7bb16ff7d Mon Sep 17 00:00:00 2001 From: Harshavardhana Date: Fri, 8 May 2020 13:44:44 -0700 Subject: [PATCH] cleanup object-lock/bucket tagging for gateways (#9548) This PR is to ensure that we call the relevant object layer APIs for necessary S3 API level functionalities allowing gateway implementations to return proper errors as NotImplemented{} This allows for all our tests in mint to behave appropriately and can be handled appropriately as well. --- cmd/api-errors.go | 4 + cmd/benchmark-utils_test.go | 10 +- cmd/bucket-handlers.go | 122 ++++++-------------- cmd/bucket-meta.go | 14 +-- cmd/bucket-object-lock.go | 125 +++++++++++++++++++- cmd/bucket-policy-handlers_test.go | 2 +- cmd/bucket-quota.go | 2 +- cmd/bucket-tagging.go | 61 ++++++++++ cmd/daily-lifecycle-ops.go | 2 +- cmd/fs-v1-metadata_test.go | 4 +- cmd/fs-v1-multipart_test.go | 14 +-- cmd/fs-v1.go | 84 ++++++++++---- cmd/fs-v1_test.go | 18 +-- cmd/gateway-unsupported.go | 30 +++++ cmd/gateway/azure/gateway-azure.go | 6 +- cmd/gateway/azure/gateway-azure_test.go | 3 +- cmd/gateway/gcs/gateway-gcs.go | 6 +- cmd/gateway/hdfs/gateway-hdfs.go | 6 +- cmd/gateway/nas/gateway-nas.go | 11 ++ cmd/gateway/s3/gateway-s3.go | 6 +- cmd/globals.go | 8 +- cmd/notification.go | 2 +- cmd/object-api-common.go | 15 +-- cmd/object-api-deleteobject_test.go | 2 +- cmd/object-api-errors.go | 22 +++- cmd/object-api-getobject_test.go | 6 +- cmd/object-api-getobjectinfo_test.go | 2 +- cmd/object-api-interface.go | 14 ++- cmd/object-api-listobjects_test.go | 4 +- cmd/object-api-multipart_test.go | 22 ++-- cmd/object-api-putobject_test.go | 12 +- cmd/object-handlers.go | 8 +- cmd/object_api_suite_test.go | 36 +++--- cmd/peer-rest-server.go | 6 +- cmd/post-policy_test.go | 4 +- cmd/server-main.go | 15 ++- cmd/test-utils_test.go | 2 +- cmd/web-handlers.go | 6 +- cmd/web-handlers_test.go | 26 ++--- cmd/xl-sets.go | 32 +++++- cmd/xl-v1-bucket.go | 29 ++++- cmd/xl-v1-common_test.go | 2 +- cmd/xl-v1-healing-common_test.go | 4 +- cmd/xl-v1-healing_test.go | 8 +- cmd/xl-v1-multipart_test.go | 2 +- cmd/xl-v1-object_test.go | 16 +-- cmd/xl-zones.go | 144 ++++++++++++++++-------- mint/run/core/awscli/test.sh | 41 +++---- pkg/bucket/object/lock/lock.go | 36 ------ 49 files changed, 681 insertions(+), 375 deletions(-) create mode 100644 cmd/bucket-tagging.go diff --git a/cmd/api-errors.go b/cmd/api-errors.go index 99f45505b..9911d7b71 100644 --- a/cmd/api-errors.go +++ b/cmd/api-errors.go @@ -1808,6 +1808,10 @@ func toAPIErrorCode(ctx context.Context, err error) (apiErr APIErrorCode) { apiErr = ErrNoSuchLifecycleConfiguration case BucketSSEConfigNotFound: apiErr = ErrNoSuchBucketSSEConfig + case BucketTaggingNotFound: + apiErr = ErrBucketTaggingNotFound + case BucketObjectLockConfigNotFound: + apiErr = ErrObjectLockConfigurationNotFound case BucketQuotaConfigNotFound: apiErr = ErrAdminNoSuchQuotaConfiguration case BucketQuotaExceeded: diff --git a/cmd/benchmark-utils_test.go b/cmd/benchmark-utils_test.go index 72d899cde..c13772872 100644 --- a/cmd/benchmark-utils_test.go +++ b/cmd/benchmark-utils_test.go @@ -35,7 +35,7 @@ func runPutObjectBenchmark(b *testing.B, obj ObjectLayer, objSize int) { // obtains random bucket name. bucket := getRandomBucketName() // create bucket. - err = obj.MakeBucketWithLocation(context.Background(), bucket, "") + err = obj.MakeBucketWithLocation(context.Background(), bucket, "", false) if err != nil { b.Fatal(err) } @@ -76,7 +76,7 @@ func runPutObjectPartBenchmark(b *testing.B, obj ObjectLayer, partSize int) { object := getRandomObjectName() // create bucket. - err = obj.MakeBucketWithLocation(context.Background(), bucket, "") + err = obj.MakeBucketWithLocation(context.Background(), bucket, "", false) if err != nil { b.Fatal(err) } @@ -181,7 +181,7 @@ func runGetObjectBenchmark(b *testing.B, obj ObjectLayer, objSize int) { // obtains random bucket name. bucket := getRandomBucketName() // create bucket. - err := obj.MakeBucketWithLocation(context.Background(), bucket, "") + err := obj.MakeBucketWithLocation(context.Background(), bucket, "", false) if err != nil { b.Fatal(err) } @@ -278,7 +278,7 @@ func runPutObjectBenchmarkParallel(b *testing.B, obj ObjectLayer, objSize int) { // obtains random bucket name. bucket := getRandomBucketName() // create bucket. - err := obj.MakeBucketWithLocation(context.Background(), bucket, "") + err := obj.MakeBucketWithLocation(context.Background(), bucket, "", false) if err != nil { b.Fatal(err) } @@ -322,7 +322,7 @@ func runGetObjectBenchmarkParallel(b *testing.B, obj ObjectLayer, objSize int) { // obtains random bucket name. bucket := getRandomBucketName() // create bucket. - err := obj.MakeBucketWithLocation(context.Background(), bucket, "") + err := obj.MakeBucketWithLocation(context.Background(), bucket, "", false) if err != nil { b.Fatal(err) } diff --git a/cmd/bucket-handlers.go b/cmd/bucket-handlers.go index ce3659f64..66ae6c884 100644 --- a/cmd/bucket-handlers.go +++ b/cmd/bucket-handlers.go @@ -292,16 +292,6 @@ func (api objectAPIHandlers) ListBucketsHandler(w http.ResponseWriter, r *http.R writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) return } - - for i := range bucketsInfo { - meta, err := loadBucketMetadata(ctx, objectAPI, bucketsInfo[i].Name) - if err == nil { - bucketsInfo[i].Created = meta.Created - } - if err != errMetaDataConverted { - logger.LogIf(ctx, err) - } - } } if s3Error == ErrAccessDenied { @@ -409,7 +399,7 @@ func (api objectAPIHandlers) DeleteMultipleObjectsHandler(w http.ResponseWriter, continue } - if _, ok := globalBucketObjectLockConfig.Get(bucket); ok { + if _, ok := globalBucketObjectLockSys.Get(bucket); ok { if err := enforceRetentionBypassForDelete(ctx, r, bucket, object.ObjectName, getObjectInfoFn); err != ErrNone { dErrs[index] = err continue @@ -537,14 +527,7 @@ func (api objectAPIHandlers) PutBucketHandler(w http.ResponseWriter, r *http.Req if err != nil { if err == dns.ErrNoEntriesFound { // Proceed to creating a bucket. - if err = objectAPI.MakeBucketWithLocation(ctx, bucket, location); err != nil { - writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) - return - } - - meta := newBucketMetadata(bucket) - meta.LockEnabled = objectLockEnabled - if err := meta.save(ctx, objectAPI); err != nil { + if err = objectAPI.MakeBucketWithLocation(ctx, bucket, location, objectLockEnabled); err != nil { writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) return } @@ -579,24 +562,16 @@ func (api objectAPIHandlers) PutBucketHandler(w http.ResponseWriter, r *http.Req } // Proceed to creating a bucket. - err := objectAPI.MakeBucketWithLocation(ctx, bucket, location) + err := objectAPI.MakeBucketWithLocation(ctx, bucket, location, objectLockEnabled) if err != nil { writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) return } - if !globalIsGateway { - meta := newBucketMetadata(bucket) - meta.LockEnabled = objectLockEnabled - if err := meta.save(ctx, objectAPI); err != nil { - writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) - return - } - if objectLockEnabled { - ret := &objectlock.Retention{} - globalBucketObjectLockConfig.Set(bucket, ret) - globalNotificationSys.PutBucketObjectLockConfig(ctx, bucket, ret) - } + if objectLockEnabled && !globalIsGateway { + ret := &objectlock.Retention{} + globalBucketObjectLockSys.Set(bucket, ret) + globalNotificationSys.PutBucketObjectLockConfig(ctx, bucket, ret) } // Make sure to add Location information here only for bucket @@ -933,7 +908,7 @@ func (api objectAPIHandlers) DeleteBucketHandler(w http.ResponseWriter, r *http. } } - if _, ok := globalBucketObjectLockConfig.Get(bucket); ok && forceDelete { + if _, ok := globalBucketObjectLockSys.Get(bucket); ok && forceDelete { writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMethodNotAllowed), r.URL, guessIsBrowserReq(r)) return } @@ -949,15 +924,12 @@ func (api objectAPIHandlers) DeleteBucketHandler(w http.ResponseWriter, r *http. if globalDNSConfig != nil { if err := globalDNSConfig.Delete(bucket); err != nil { // Deleting DNS entry failed, attempt to create the bucket again. - objectAPI.MakeBucketWithLocation(ctx, bucket, "") + objectAPI.MakeBucketWithLocation(ctx, bucket, "", false) writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) return } } - // Delete metadata, only log errors. - logger.LogIf(ctx, newBucketMetadata(bucket).delete(ctx, objectAPI)) - globalNotificationSys.DeleteBucket(ctx, bucket) // Write success response. @@ -1041,6 +1013,7 @@ func (api objectAPIHandlers) PutBucketObjectLockConfigHandler(w http.ResponseWri writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL, guessIsBrowserReq(r)) return } + config, err := objectlock.ParseObjectLockConfig(r.Body) if err != nil { apiErr := errorCodes.ToAPIErr(ErrMalformedXML) @@ -1049,32 +1022,17 @@ func (api objectAPIHandlers) PutBucketObjectLockConfigHandler(w http.ResponseWri return } - meta, err := loadBucketMetadata(ctx, objectAPI, bucket) - if err != nil { + if err = objectAPI.SetBucketObjectLockConfig(ctx, bucket, config); err != nil { writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) return } - if !meta.LockEnabled { - writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrObjectLockConfigurationNotAllowed), r.URL, guessIsBrowserReq(r)) - return - } - data, err := xml.Marshal(config) - if err != nil { - writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) - return - } - configFile := path.Join(bucketConfigPrefix, bucket, objectLockConfig) - if err = saveConfig(ctx, objectAPI, configFile, data); err != nil { - writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) - return - } if config.Rule != nil { retention := config.ToRetention() - globalBucketObjectLockConfig.Set(bucket, retention) + globalBucketObjectLockSys.Set(bucket, retention) globalNotificationSys.PutBucketObjectLockConfig(ctx, bucket, retention) } else { - globalBucketObjectLockConfig.Set(bucket, &objectlock.Retention{}) + globalBucketObjectLockSys.Set(bucket, &objectlock.Retention{}) globalNotificationSys.PutBucketObjectLockConfig(ctx, bucket, &objectlock.Retention{}) } @@ -1106,28 +1064,16 @@ func (api objectAPIHandlers) GetBucketObjectLockConfigHandler(w http.ResponseWri return } - meta, err := loadBucketMetadata(ctx, objectAPI, bucket) - if err != nil && err != errMetaDataConverted { + lkCfg, err := objectAPI.GetBucketObjectLockConfig(ctx, bucket) + if err != nil { writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) return } - if !meta.LockEnabled { - writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrObjectLockConfigurationNotAllowed), r.URL, guessIsBrowserReq(r)) - return - } - configFile := path.Join(bucketConfigPrefix, bucket, objectLockConfig) - configData, err := readConfig(ctx, objectAPI, configFile) + configData, err := xml.Marshal(lkCfg) if err != nil { - if err != errConfigNotFound { - writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) - return - } - - if configData, err = xml.Marshal(objectlock.NewObjectLockConfig()); err != nil { - writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) - return - } + writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) + return } // Write success response. @@ -1154,6 +1100,7 @@ func (api objectAPIHandlers) PutBucketTaggingHandler(w http.ResponseWriter, r *h writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL, guessIsBrowserReq(r)) return } + tags, err := tags.ParseBucketXML(io.LimitReader(r.Body, r.ContentLength)) if err != nil { apiErr := errorCodes.ToAPIErr(ErrMalformedXML) @@ -1161,13 +1108,8 @@ func (api objectAPIHandlers) PutBucketTaggingHandler(w http.ResponseWriter, r *h writeErrorResponse(ctx, w, apiErr, r.URL, guessIsBrowserReq(r)) return } - data, err := xml.Marshal(tags) - if err != nil { - writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) - return - } - configFile := path.Join(bucketConfigPrefix, bucket, bucketTaggingConfigFile) - if err = saveConfig(ctx, objectAPI, configFile, data); err != nil { + + if err = objectAPI.SetBucketTagging(ctx, bucket, tags); err != nil { writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) return } @@ -1191,21 +1133,22 @@ func (api objectAPIHandlers) GetBucketTaggingHandler(w http.ResponseWriter, r *h writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL, guessIsBrowserReq(r)) return } + // check if user has permissions to perform this operation if s3Error := checkRequestAuthType(ctx, r, policy.GetBucketTaggingAction, bucket, ""); s3Error != ErrNone { writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL, guessIsBrowserReq(r)) return } - configFile := path.Join(bucketConfigPrefix, bucket, bucketTaggingConfigFile) - configData, err := readConfig(ctx, objectAPI, configFile) + + t, err := objectAPI.GetBucketTagging(ctx, bucket) if err != nil { - var aerr APIError - if err == errConfigNotFound { - aerr = errorCodes.ToAPIErr(ErrBucketTaggingNotFound) - } else { - aerr = toAPIError(ctx, err) - } - writeErrorResponse(ctx, w, aerr, r.URL, guessIsBrowserReq(r)) + writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) + return + } + + configData, err := xml.Marshal(t) + if err != nil { + writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) return } @@ -1234,8 +1177,7 @@ func (api objectAPIHandlers) DeleteBucketTaggingHandler(w http.ResponseWriter, r return } - configFile := path.Join(bucketConfigPrefix, bucket, bucketTaggingConfigFile) - if err := deleteConfig(ctx, objectAPI, configFile); err != nil && err != errConfigNotFound { + if err := objectAPI.DeleteBucketTagging(ctx, bucket); err != nil { writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) return } diff --git a/cmd/bucket-meta.go b/cmd/bucket-meta.go index c830cd26e..0336167e4 100644 --- a/cmd/bucket-meta.go +++ b/cmd/bucket-meta.go @@ -154,14 +154,12 @@ func (b *bucketMetadata) save(ctx context.Context, o ObjectLayer) error { return saveConfig(ctx, o, configFile, data) } -// delete the config metadata. +// removeBucketMeta bucket metadata. // If config does not exist no error is returned. -func (b bucketMetadata) delete(ctx context.Context, o ObjectLayer) error { - configFile := path.Join(bucketConfigPrefix, b.Name, bucketMetadataFile) - err := deleteConfig(ctx, o, configFile) - if err == errConfigNotFound { - // We don't care - err = nil +func removeBucketMeta(ctx context.Context, obj ObjectLayer, bucket string) error { + configFile := path.Join(bucketConfigPrefix, bucket, bucketMetadataFile) + if err := deleteConfig(ctx, obj, configFile); err != nil && err != errConfigNotFound { + return err } - return err + return nil } diff --git a/cmd/bucket-object-lock.go b/cmd/bucket-object-lock.go index 552bff319..f5811e84c 100644 --- a/cmd/bucket-object-lock.go +++ b/cmd/bucket-object-lock.go @@ -19,10 +19,12 @@ package cmd import ( "bytes" "context" + "encoding/xml" "errors" "math" "net/http" "path" + "sync" "github.com/minio/minio/cmd/logger" "github.com/minio/minio/pkg/auth" @@ -30,6 +32,54 @@ import ( "github.com/minio/minio/pkg/bucket/policy" ) +// BucketObjectLockSys - map of bucket and retention configuration. +type BucketObjectLockSys struct { + sync.RWMutex + retentionMap map[string]*objectlock.Retention +} + +// Set - set retention configuration. +func (sys *BucketObjectLockSys) Set(bucketName string, retention *objectlock.Retention) { + if globalIsGateway { + // no-op + return + } + + sys.Lock() + sys.retentionMap[bucketName] = retention + sys.Unlock() +} + +// Get - Get retention configuration. +func (sys *BucketObjectLockSys) Get(bucketName string) (r *objectlock.Retention, ok bool) { + if globalIsGateway { + // When gateway is enabled, no cached value + // is used to validate bucket object lock configuration + objAPI := newObjectLayerWithoutSafeModeFn() + if objAPI == nil { + return + } + + lc, err := objAPI.GetBucketObjectLockConfig(GlobalContext, bucketName) + if err != nil { + return + } + return lc.ToRetention(), true + } + + sys.RLock() + defer sys.RUnlock() + r, ok = sys.retentionMap[bucketName] + return r, ok +} + +// Remove - removes retention sysuration. +func (sys *BucketObjectLockSys) Remove(bucketName string) { + sys.Lock() + delete(sys.retentionMap, bucketName) + sys.Unlock() +} + // Similar to enforceRetentionBypassForDelete but for WebUI func enforceRetentionBypassForDeleteWeb(ctx context.Context, r *http.Request, bucket, object string, getObjectInfoFn GetObjectInfoFn, govBypassPerms bool) APIErrorCode { opts, err := getOpts(ctx, r, bucket, object) @@ -295,7 +345,7 @@ func checkPutObjectLockAllowed(ctx context.Context, r *http.Request, bucket, obj retentionRequested := objectlock.IsObjectLockRetentionRequested(r.Header) legalHoldRequested := objectlock.IsObjectLockLegalHoldRequested(r.Header) - retentionCfg, isWORMBucket := globalBucketObjectLockConfig.Get(bucket) + retentionCfg, isWORMBucket := globalBucketObjectLockSys.Get(bucket) if !isWORMBucket { if legalHoldRequested || retentionRequested { return mode, retainDate, legalHold, ErrInvalidBucketObjectLockConfiguration @@ -378,7 +428,74 @@ func checkPutObjectLockAllowed(ctx context.Context, r *http.Request, bucket, obj return mode, retainDate, legalHold, ErrNone } -func initBucketObjectLockConfig(buckets []BucketInfo, objAPI ObjectLayer) error { +func readBucketObjectLockConfig(ctx context.Context, objAPI ObjectLayer, bucket string) (*objectlock.Config, error) { + meta, err := loadBucketMetadata(ctx, objAPI, bucket) + if err != nil && err != errMetaDataConverted { + return nil, toObjectErr(err, bucket) + } + if !meta.LockEnabled { + return nil, BucketObjectLockConfigNotFound{Bucket: bucket} + } + configFile := path.Join(bucketConfigPrefix, bucket, objectLockConfig) + configData, err := readConfig(ctx, objAPI, configFile) + if err != nil { + if err != errConfigNotFound { + return nil, toObjectErr(err, bucket) + } + return objectlock.NewObjectLockConfig(), nil + } + cfg, err := objectlock.ParseObjectLockConfig(bytes.NewReader(configData)) + if err != nil { + return nil, toObjectErr(err, bucket) + } + return cfg, nil +} + +func saveBucketObjectLockConfig(ctx context.Context, objAPI ObjectLayer, bucket string, config *objectlock.Config) error { + meta, err := loadBucketMetadata(ctx, objAPI, bucket) + if err != nil && err != errMetaDataConverted { + return toObjectErr(err, bucket) + } + if !meta.LockEnabled { + return BucketObjectLockConfigNotFound{Bucket: bucket} + } + + data, err := xml.Marshal(config) + if err != nil { + return toObjectErr(err, bucket) + } + + configFile := path.Join(bucketConfigPrefix, bucket, objectLockConfig) + if err = saveConfig(ctx, objAPI, configFile, data); err != nil { + return toObjectErr(err, bucket) + } + return nil +} + +// NewBucketObjectLockSys returns initialized BucketObjectLockSys +func NewBucketObjectLockSys() *BucketObjectLockSys { + return &BucketObjectLockSys{ + retentionMap: make(map[string]*objectlock.Retention), + } +} + +// Init - initializes bucket object lock config system for all buckets +func (sys *BucketObjectLockSys) Init(buckets []BucketInfo, objAPI ObjectLayer) error { + if objAPI == nil { + return errServerNotInitialized + } + + // In gateway mode, we always fetch the bucket object lock configuration from the gateway backend. + // So, this is a no-op for gateway servers. + if globalIsGateway { + return nil + } + + // Load BucketObjectLockSys once during boot. + return sys.load(buckets, objAPI) +} + +func (sys *BucketObjectLockSys) load(buckets []BucketInfo, objAPI ObjectLayer) error { for _, bucket := range buckets { ctx := logger.SetReqInfo(GlobalContext, &logger.ReqInfo{BucketName: bucket.Name}) meta, err := loadBucketMetadata(ctx, objAPI, bucket.Name) @@ -398,7 +515,7 @@ func initBucketObjectLockConfig(buckets []BucketInfo, objAPI ObjectLayer) error configData, err := readConfig(ctx, objAPI, configFile) if err != nil { if errors.Is(err, errConfigNotFound) { - globalBucketObjectLockConfig.Set(bucket.Name, &objectlock.Retention{}) + globalBucketObjectLockSys.Set(bucket.Name, &objectlock.Retention{}) continue } return err @@ -412,7 +529,7 @@ func initBucketObjectLockConfig(buckets []BucketInfo, objAPI ObjectLayer) error if config.Rule != nil { retention = config.ToRetention() } - globalBucketObjectLockConfig.Set(bucket.Name, retention) + globalBucketObjectLockSys.Set(bucket.Name, retention) } return nil } diff --git a/cmd/bucket-policy-handlers_test.go b/cmd/bucket-policy-handlers_test.go index 63b66520a..91c1b5cdf 100644 --- a/cmd/bucket-policy-handlers_test.go +++ b/cmd/bucket-policy-handlers_test.go @@ -102,7 +102,7 @@ func testPutBucketPolicyHandler(obj ObjectLayer, instanceType, bucketName string credentials auth.Credentials, t *testing.T) { bucketName1 := fmt.Sprintf("%s-1", bucketName) - if err := obj.MakeBucketWithLocation(GlobalContext, bucketName1, ""); err != nil { + if err := obj.MakeBucketWithLocation(GlobalContext, bucketName1, "", false); err != nil { t.Fatal(err) } diff --git a/cmd/bucket-quota.go b/cmd/bucket-quota.go index 9a9eea476..d5d7380dd 100644 --- a/cmd/bucket-quota.go +++ b/cmd/bucket-quota.go @@ -198,7 +198,7 @@ func enforceFIFOQuota(ctx context.Context, objectAPI ObjectLayer) error { if cfg.Type != madmin.FIFOQuota { continue } - _, bucketHasLockConfig := globalBucketObjectLockConfig.Get(bucket) + _, bucketHasLockConfig := globalBucketObjectLockSys.Get(bucket) dataUsageInfo, err := loadDataUsageFromBackend(ctx, objectAPI) if err != nil { diff --git a/cmd/bucket-tagging.go b/cmd/bucket-tagging.go new file mode 100644 index 000000000..b65264e93 --- /dev/null +++ b/cmd/bucket-tagging.go @@ -0,0 +1,61 @@ +/* + * MinIO Cloud Storage, (C) 2020 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cmd + +import ( + "context" + "encoding/xml" + "path" + + "github.com/minio/minio-go/v6/pkg/tags" +) + +func saveBucketTagging(ctx context.Context, objAPI ObjectLayer, bucket string, t *tags.Tags) error { + data, err := xml.Marshal(t) + if err != nil { + return toObjectErr(err, bucket) + } + configFile := path.Join(bucketConfigPrefix, bucket, bucketTaggingConfigFile) + if err := saveConfig(ctx, objAPI, configFile, data); err != nil { + return toObjectErr(err, bucket) + } + return nil +} + +func deleteBucketTagging(ctx context.Context, objAPI ObjectLayer, bucket string) error { + configFile := path.Join(bucketConfigPrefix, bucket, bucketTaggingConfigFile) + if err := deleteConfig(ctx, objAPI, configFile); err != nil && err != errConfigNotFound { + return toObjectErr(err, bucket) + } + return nil +} + +func readBucketTagging(ctx context.Context, objAPI ObjectLayer, bucket string) (*tags.Tags, error) { + configFile := path.Join(bucketConfigPrefix, bucket, bucketTaggingConfigFile) + configData, err := readConfig(ctx, objAPI, configFile) + if err != nil { + if err == errConfigNotFound { + return nil, BucketTaggingNotFound{Bucket: bucket} + } + return nil, toObjectErr(err, bucket) + } + t := &tags.Tags{} + if err = xml.Unmarshal(configData, t); err != nil { + return nil, toObjectErr(err, bucket) + } + return t, nil +} diff --git a/cmd/daily-lifecycle-ops.go b/cmd/daily-lifecycle-ops.go index 5d44ae15a..c3074cb51 100644 --- a/cmd/daily-lifecycle-ops.go +++ b/cmd/daily-lifecycle-ops.go @@ -63,7 +63,7 @@ func lifecycleRound(ctx context.Context, objAPI ObjectLayer) error { continue } - _, bucketHasLockConfig := globalBucketObjectLockConfig.Get(bucket.Name) + _, bucketHasLockConfig := globalBucketObjectLockSys.Get(bucket.Name) // Calculate the common prefix of all lifecycle rules var prefixes []string diff --git a/cmd/fs-v1-metadata_test.go b/cmd/fs-v1-metadata_test.go index ee82a1eb2..1d8cb7ba5 100644 --- a/cmd/fs-v1-metadata_test.go +++ b/cmd/fs-v1-metadata_test.go @@ -53,7 +53,7 @@ func TestReadFSMetadata(t *testing.T) { bucketName := "bucket" objectName := "object" - if err := obj.MakeBucketWithLocation(GlobalContext, bucketName, ""); err != nil { + if err := obj.MakeBucketWithLocation(GlobalContext, bucketName, "", false); err != nil { t.Fatal("Unexpected err: ", err) } if _, err := obj.PutObject(GlobalContext, bucketName, objectName, mustGetPutObjReader(t, bytes.NewReader([]byte("abcd")), int64(len("abcd")), "", ""), ObjectOptions{}); err != nil { @@ -88,7 +88,7 @@ func TestWriteFSMetadata(t *testing.T) { bucketName := "bucket" objectName := "object" - if err := obj.MakeBucketWithLocation(GlobalContext, bucketName, ""); err != nil { + if err := obj.MakeBucketWithLocation(GlobalContext, bucketName, "", false); err != nil { t.Fatal("Unexpected err: ", err) } if _, err := obj.PutObject(GlobalContext, bucketName, objectName, mustGetPutObjReader(t, bytes.NewReader([]byte("abcd")), int64(len("abcd")), "", ""), ObjectOptions{}); err != nil { diff --git a/cmd/fs-v1-multipart_test.go b/cmd/fs-v1-multipart_test.go index 35a11021c..91d947f55 100644 --- a/cmd/fs-v1-multipart_test.go +++ b/cmd/fs-v1-multipart_test.go @@ -40,7 +40,7 @@ func TestFSCleanupMultipartUploadsInRoutine(t *testing.T) { // Create a context we can cancel. ctx, cancel := context.WithCancel(GlobalContext) - obj.MakeBucketWithLocation(ctx, bucketName, "") + obj.MakeBucketWithLocation(ctx, bucketName, "", false) uploadID, err := obj.NewMultipartUpload(ctx, bucketName, objectName, ObjectOptions{}) if err != nil { @@ -81,7 +81,7 @@ func TestNewMultipartUploadFaultyDisk(t *testing.T) { bucketName := "bucket" objectName := "object" - if err := obj.MakeBucketWithLocation(GlobalContext, bucketName, ""); err != nil { + if err := obj.MakeBucketWithLocation(GlobalContext, bucketName, "", false); err != nil { t.Fatal("Cannot create bucket, err: ", err) } @@ -106,7 +106,7 @@ func TestPutObjectPartFaultyDisk(t *testing.T) { data := []byte("12345") dataLen := int64(len(data)) - if err := obj.MakeBucketWithLocation(GlobalContext, bucketName, ""); err != nil { + if err := obj.MakeBucketWithLocation(GlobalContext, bucketName, "", false); err != nil { t.Fatal("Cannot create bucket, err: ", err) } @@ -139,7 +139,7 @@ func TestCompleteMultipartUploadFaultyDisk(t *testing.T) { objectName := "object" data := []byte("12345") - if err := obj.MakeBucketWithLocation(GlobalContext, bucketName, ""); err != nil { + if err := obj.MakeBucketWithLocation(GlobalContext, bucketName, "", false); err != nil { t.Fatal("Cannot create bucket, err: ", err) } @@ -172,7 +172,7 @@ func TestCompleteMultipartUpload(t *testing.T) { objectName := "object" data := []byte("12345") - if err := obj.MakeBucketWithLocation(GlobalContext, bucketName, ""); err != nil { + if err := obj.MakeBucketWithLocation(GlobalContext, bucketName, "", false); err != nil { t.Fatal("Cannot create bucket, err: ", err) } @@ -204,7 +204,7 @@ func TestAbortMultipartUpload(t *testing.T) { objectName := "object" data := []byte("12345") - if err := obj.MakeBucketWithLocation(GlobalContext, bucketName, ""); err != nil { + if err := obj.MakeBucketWithLocation(GlobalContext, bucketName, "", false); err != nil { t.Fatal("Cannot create bucket, err: ", err) } @@ -235,7 +235,7 @@ func TestListMultipartUploadsFaultyDisk(t *testing.T) { bucketName := "bucket" objectName := "object" - if err := obj.MakeBucketWithLocation(GlobalContext, bucketName, ""); err != nil { + if err := obj.MakeBucketWithLocation(GlobalContext, bucketName, "", false); err != nil { t.Fatal("Cannot create bucket, err: ", err) } diff --git a/cmd/fs-v1.go b/cmd/fs-v1.go index 5337b9fff..e482f991b 100644 --- a/cmd/fs-v1.go +++ b/cmd/fs-v1.go @@ -41,6 +41,7 @@ import ( "github.com/minio/minio/cmd/logger" bucketsse "github.com/minio/minio/pkg/bucket/encryption" "github.com/minio/minio/pkg/bucket/lifecycle" + objectlock "github.com/minio/minio/pkg/bucket/object/lock" "github.com/minio/minio/pkg/bucket/policy" "github.com/minio/minio/pkg/color" "github.com/minio/minio/pkg/lock" @@ -314,12 +315,17 @@ func (fs *FSObjects) statBucketDir(ctx context.Context, bucket string) (os.FileI // MakeBucketWithLocation - create a new bucket, returns if it // already exists. -func (fs *FSObjects) MakeBucketWithLocation(ctx context.Context, bucket, location string) error { +func (fs *FSObjects) MakeBucketWithLocation(ctx context.Context, bucket, location string, lockEnabled bool) error { + if lockEnabled { + return NotImplemented{} + } + bucketLock := fs.NewNSLock(ctx, bucket, "") if err := bucketLock.GetLock(globalObjectTimeout); err != nil { return err } defer bucketLock.Unlock() + // Verify if bucket is valid. if s3utils.CheckValidBucketNameStrict(bucket) != nil { return BucketNameInvalid{Bucket: bucket} @@ -339,6 +345,12 @@ func (fs *FSObjects) MakeBucketWithLocation(ctx context.Context, bucket, locatio return toObjectErr(err, bucket) } + meta := newBucketMetadata(bucket) + meta.LockEnabled = false + if err := meta.save(ctx, fs); err != nil { + return toObjectErr(err, bucket) + } + return nil } @@ -381,7 +393,7 @@ func (fs *FSObjects) ListBuckets(ctx context.Context) ([]BucketInfo, error) { }() var bucketInfos []BucketInfo - entries, err := readDir((fs.fsPath)) + entries, err := readDir(fs.fsPath) if err != nil { logger.LogIf(ctx, errDiskNotFound) return nil, toObjectErr(errDiskNotFound) @@ -402,10 +414,17 @@ func (fs *FSObjects) ListBuckets(ctx context.Context) ([]BucketInfo, error) { // Ignore any errors returned here. continue } + var created = fi.ModTime() + meta, err := loadBucketMetadata(ctx, fs, fi.Name()) + if err == nil { + created = meta.Created + } + if err != errMetaDataConverted { + logger.LogIf(ctx, err) + } bucketInfos = append(bucketInfos, BucketInfo{ - Name: fi.Name(), - // As os.Stat() doesnt carry CreatedTime, use ModTime() as CreatedTime. - Created: fi.ModTime(), + Name: fi.Name(), + Created: created, }) } @@ -644,12 +663,12 @@ func (fs *FSObjects) GetObject(ctx context.Context, bucket, object string, offse } // Lock the object before reading. - objectLock := fs.NewNSLock(ctx, bucket, object) - if err := objectLock.GetRLock(globalObjectTimeout); err != nil { + lk := fs.NewNSLock(ctx, bucket, object) + if err := lk.GetRLock(globalObjectTimeout); err != nil { logger.LogIf(ctx, err) return err } - defer objectLock.RUnlock() + defer lk.RUnlock() atomic.AddInt64(&fs.activeIOCount, 1) defer func() { @@ -818,11 +837,11 @@ func (fs *FSObjects) getObjectInfo(ctx context.Context, bucket, object string) ( // getObjectInfoWithLock - reads object metadata and replies back ObjectInfo. func (fs *FSObjects) getObjectInfoWithLock(ctx context.Context, bucket, object string) (oi ObjectInfo, e error) { // Lock the object before reading. - objectLock := fs.NewNSLock(ctx, bucket, object) - if err := objectLock.GetRLock(globalObjectTimeout); err != nil { + lk := fs.NewNSLock(ctx, bucket, object) + if err := lk.GetRLock(globalObjectTimeout); err != nil { return oi, err } - defer objectLock.RUnlock() + defer lk.RUnlock() if err := checkGetObjArgs(ctx, bucket, object); err != nil { return oi, err @@ -849,14 +868,14 @@ func (fs *FSObjects) GetObjectInfo(ctx context.Context, bucket, object string, o oi, err := fs.getObjectInfoWithLock(ctx, bucket, object) if err == errCorruptedFormat || err == io.EOF { - objectLock := fs.NewNSLock(ctx, bucket, object) - if err = objectLock.GetLock(globalObjectTimeout); err != nil { + lk := fs.NewNSLock(ctx, bucket, object) + if err = lk.GetLock(globalObjectTimeout); err != nil { return oi, toObjectErr(err, bucket, object) } fsMetaPath := pathJoin(fs.fsPath, minioMetaBucket, bucketMetaPrefix, bucket, object, fs.metaJSONFile) err = fs.createFsJSON(object, fsMetaPath) - objectLock.Unlock() + lk.Unlock() if err != nil { return oi, toObjectErr(err, bucket, object) } @@ -896,12 +915,12 @@ func (fs *FSObjects) PutObject(ctx context.Context, bucket string, object string } // Lock the object. - objectLock := fs.NewNSLock(ctx, bucket, object) - if err := objectLock.GetLock(globalObjectTimeout); err != nil { + lk := fs.NewNSLock(ctx, bucket, object) + if err := lk.GetLock(globalObjectTimeout); err != nil { logger.LogIf(ctx, err) return objInfo, err } - defer objectLock.Unlock() + defer lk.Unlock() defer ObjectPathUpdated(path.Join(bucket, object)) atomic.AddInt64(&fs.activeIOCount, 1) @@ -1051,11 +1070,11 @@ func (fs *FSObjects) DeleteObjects(ctx context.Context, bucket string, objects [ // and there are no rollbacks supported. func (fs *FSObjects) DeleteObject(ctx context.Context, bucket, object string) error { // Acquire a write lock before deleting the object. - objectLock := fs.NewNSLock(ctx, bucket, object) - if err := objectLock.GetLock(globalOperationTimeout); err != nil { + lk := fs.NewNSLock(ctx, bucket, object) + if err := lk.GetLock(globalOperationTimeout); err != nil { return err } - defer objectLock.Unlock() + defer lk.Unlock() if err := checkDelObjArgs(ctx, bucket, object); err != nil { return err @@ -1364,6 +1383,31 @@ func (fs *FSObjects) DeleteBucketSSEConfig(ctx context.Context, bucket string) e return removeBucketSSEConfig(ctx, fs, bucket) } +// SetBucketObjectLockConfig enables/clears default object lock configuration +func (fs *FSObjects) SetBucketObjectLockConfig(ctx context.Context, bucket string, config *objectlock.Config) error { + return saveBucketObjectLockConfig(ctx, fs, bucket, config) +} + +// GetBucketObjectLockConfig - returns current defaults for object lock configuration +func (fs *FSObjects) GetBucketObjectLockConfig(ctx context.Context, bucket string) (*objectlock.Config, error) { + return readBucketObjectLockConfig(ctx, fs, bucket) +} + +// SetBucketTagging sets bucket tags on given bucket +func (fs *FSObjects) SetBucketTagging(ctx context.Context, bucket string, t *tags.Tags) error { + return saveBucketTagging(ctx, fs, bucket, t) +} + +// GetBucketTagging get bucket tags set on given bucket +func (fs *FSObjects) GetBucketTagging(ctx context.Context, bucket string) (*tags.Tags, error) { + return readBucketTagging(ctx, fs, bucket) +} + +// DeleteBucketTagging delete bucket tags set if any. +func (fs *FSObjects) DeleteBucketTagging(ctx context.Context, bucket string) error { + return deleteBucketTagging(ctx, fs, bucket) +} + // ListObjectsV2 lists all blobs in bucket filtered by prefix func (fs *FSObjects) ListObjectsV2(ctx context.Context, bucket, prefix, continuationToken, delimiter string, maxKeys int, fetchOwner bool, startAfter string) (result ListObjectsV2Info, err error) { marker := continuationToken diff --git a/cmd/fs-v1_test.go b/cmd/fs-v1_test.go index f0b5dfb1a..7d398dc49 100644 --- a/cmd/fs-v1_test.go +++ b/cmd/fs-v1_test.go @@ -36,7 +36,7 @@ func TestFSParentDirIsObject(t *testing.T) { bucketName := "testbucket" objectName := "object" - if err = obj.MakeBucketWithLocation(GlobalContext, bucketName, ""); err != nil { + if err = obj.MakeBucketWithLocation(GlobalContext, bucketName, "", false); err != nil { t.Fatal(err) } objectContent := "12345" @@ -124,7 +124,7 @@ func TestFSShutdown(t *testing.T) { fs := obj.(*FSObjects) objectContent := "12345" - obj.MakeBucketWithLocation(GlobalContext, bucketName, "") + obj.MakeBucketWithLocation(GlobalContext, bucketName, "", false) obj.PutObject(GlobalContext, bucketName, objectName, mustGetPutObjReader(t, bytes.NewReader([]byte(objectContent)), int64(len(objectContent)), "", ""), ObjectOptions{}) return fs, disk } @@ -155,12 +155,12 @@ func TestFSGetBucketInfo(t *testing.T) { fs := obj.(*FSObjects) bucketName := "bucket" - err := obj.MakeBucketWithLocation(GlobalContext, "a", "") + err := obj.MakeBucketWithLocation(GlobalContext, "a", "", false) if !isSameType(err, BucketNameInvalid{}) { t.Fatal("BucketNameInvalid error not returned") } - err = obj.MakeBucketWithLocation(GlobalContext, bucketName, "") + err = obj.MakeBucketWithLocation(GlobalContext, bucketName, "", false) if err != nil { t.Fatal(err) } @@ -199,7 +199,7 @@ func TestFSPutObject(t *testing.T) { bucketName := "bucket" objectName := "1/2/3/4/object" - if err := obj.MakeBucketWithLocation(GlobalContext, bucketName, ""); err != nil { + if err := obj.MakeBucketWithLocation(GlobalContext, bucketName, "", false); err != nil { t.Fatal(err) } @@ -267,7 +267,7 @@ func TestFSDeleteObject(t *testing.T) { bucketName := "bucket" objectName := "object" - obj.MakeBucketWithLocation(GlobalContext, bucketName, "") + obj.MakeBucketWithLocation(GlobalContext, bucketName, "", false) obj.PutObject(GlobalContext, bucketName, objectName, mustGetPutObjReader(t, bytes.NewReader([]byte("abcd")), int64(len("abcd")), "", ""), ObjectOptions{}) // Test with invalid bucket name @@ -311,7 +311,7 @@ func TestFSDeleteBucket(t *testing.T) { fs := obj.(*FSObjects) bucketName := "bucket" - err := obj.MakeBucketWithLocation(GlobalContext, bucketName, "") + err := obj.MakeBucketWithLocation(GlobalContext, bucketName, "", false) if err != nil { t.Fatal("Unexpected error: ", err) } @@ -330,7 +330,7 @@ func TestFSDeleteBucket(t *testing.T) { t.Fatal("Unexpected error: ", err) } - obj.MakeBucketWithLocation(GlobalContext, bucketName, "") + obj.MakeBucketWithLocation(GlobalContext, bucketName, "", false) // Delete bucket should get error disk not found. os.RemoveAll(disk) @@ -351,7 +351,7 @@ func TestFSListBuckets(t *testing.T) { fs := obj.(*FSObjects) bucketName := "bucket" - if err := obj.MakeBucketWithLocation(GlobalContext, bucketName, ""); err != nil { + if err := obj.MakeBucketWithLocation(GlobalContext, bucketName, "", false); err != nil { t.Fatal("Unexpected error: ", err) } diff --git a/cmd/gateway-unsupported.go b/cmd/gateway-unsupported.go index a90f5f511..be840ed42 100644 --- a/cmd/gateway-unsupported.go +++ b/cmd/gateway-unsupported.go @@ -25,6 +25,7 @@ import ( "github.com/minio/minio-go/v6/pkg/tags" bucketsse "github.com/minio/minio/pkg/bucket/encryption" "github.com/minio/minio/pkg/bucket/lifecycle" + objectlock "github.com/minio/minio/pkg/bucket/object/lock" "github.com/minio/minio/pkg/bucket/policy" "github.com/minio/minio/pkg/madmin" @@ -198,6 +199,35 @@ func (a GatewayUnsupported) GetMetrics(ctx context.Context) (*Metrics, error) { return &Metrics{}, NotImplemented{} } +// SetBucketTagging - not implemented +func (a GatewayUnsupported) SetBucketTagging(ctx context.Context, bucket string, t *tags.Tags) error { + logger.LogIf(ctx, NotImplemented{}) + return NotImplemented{} +} + +// GetBucketObjectLockConfig - not implemented +func (a GatewayUnsupported) GetBucketObjectLockConfig(ctx context.Context, bucket string) (*objectlock.Config, error) { + logger.LogIf(ctx, NotImplemented{}) + return nil, NotImplemented{} +} + +// SetBucketObjectLockConfig - not implemented +func (a GatewayUnsupported) SetBucketObjectLockConfig(ctx context.Context, bucket string, _ *objectlock.Config) error { + logger.LogIf(ctx, NotImplemented{}) + return NotImplemented{} +} + +// GetBucketTagging - not implemented +func (a GatewayUnsupported) GetBucketTagging(ctx context.Context, bucket string) (*tags.Tags, error) { + return nil, NotImplemented{} +} + +// DeleteBucketTagging - not implemented. +func (a GatewayUnsupported) DeleteBucketTagging(ctx context.Context, bucket string) error { + logger.LogIf(ctx, NotImplemented{}) + return NotImplemented{} +} + // PutObjectTag - not implemented. func (a GatewayUnsupported) PutObjectTag(ctx context.Context, bucket, object string, tags string) error { logger.LogIf(ctx, NotImplemented{}) diff --git a/cmd/gateway/azure/gateway-azure.go b/cmd/gateway/azure/gateway-azure.go index 5b2c89d72..46a7361e0 100644 --- a/cmd/gateway/azure/gateway-azure.go +++ b/cmd/gateway/azure/gateway-azure.go @@ -544,7 +544,11 @@ func (a *azureObjects) StorageInfo(ctx context.Context, _ bool) (si minio.Storag } // MakeBucketWithLocation - Create a new container on azure backend. -func (a *azureObjects) MakeBucketWithLocation(ctx context.Context, bucket, location string) error { +func (a *azureObjects) MakeBucketWithLocation(ctx context.Context, bucket, location string, lockEnabled bool) error { + if lockEnabled { + return minio.NotImplemented{} + } + // Verify if bucket (container-name) is valid. // IsValidBucketName has same restrictions as container names mentioned // in azure documentation, so we will simply use the same function here. diff --git a/cmd/gateway/azure/gateway-azure_test.go b/cmd/gateway/azure/gateway-azure_test.go index c7813dc9c..449aa7d63 100644 --- a/cmd/gateway/azure/gateway-azure_test.go +++ b/cmd/gateway/azure/gateway-azure_test.go @@ -19,13 +19,14 @@ package azure import ( "encoding/base64" "fmt" - "github.com/dustin/go-humanize" "net/http" "os" "reflect" "strconv" "testing" + "github.com/dustin/go-humanize" + "github.com/Azure/azure-storage-blob-go/azblob" minio "github.com/minio/minio/cmd" ) diff --git a/cmd/gateway/gcs/gateway-gcs.go b/cmd/gateway/gcs/gateway-gcs.go index add995056..eb747395a 100644 --- a/cmd/gateway/gcs/gateway-gcs.go +++ b/cmd/gateway/gcs/gateway-gcs.go @@ -421,7 +421,11 @@ func (l *gcsGateway) StorageInfo(ctx context.Context, _ bool) (si minio.StorageI } // MakeBucketWithLocation - Create a new container on GCS backend. -func (l *gcsGateway) MakeBucketWithLocation(ctx context.Context, bucket, location string) error { +func (l *gcsGateway) MakeBucketWithLocation(ctx context.Context, bucket, location string, lockEnabled bool) error { + if lockEnabled { + return minio.NotImplemented{} + } + bkt := l.client.Bucket(bucket) // we'll default to the us multi-region in case of us-east-1 diff --git a/cmd/gateway/hdfs/gateway-hdfs.go b/cmd/gateway/hdfs/gateway-hdfs.go index a7b011d83..b25c40249 100644 --- a/cmd/gateway/hdfs/gateway-hdfs.go +++ b/cmd/gateway/hdfs/gateway-hdfs.go @@ -281,7 +281,11 @@ func (n *hdfsObjects) DeleteBucket(ctx context.Context, bucket string, forceDele return hdfsToObjectErr(ctx, n.clnt.Remove(minio.PathJoin(hdfsSeparator, bucket)), bucket) } -func (n *hdfsObjects) MakeBucketWithLocation(ctx context.Context, bucket, location string) error { +func (n *hdfsObjects) MakeBucketWithLocation(ctx context.Context, bucket, location string, lockEnabled bool) error { + if lockEnabled { + return minio.NotImplemented{} + } + if !hdfsIsValidBucketName(bucket) { return minio.BucketNameInvalid{Bucket: bucket} } diff --git a/cmd/gateway/nas/gateway-nas.go b/cmd/gateway/nas/gateway-nas.go index ad4c1b433..e257c234e 100644 --- a/cmd/gateway/nas/gateway-nas.go +++ b/cmd/gateway/nas/gateway-nas.go @@ -22,6 +22,7 @@ import ( "github.com/minio/cli" minio "github.com/minio/minio/cmd" "github.com/minio/minio/pkg/auth" + objectlock "github.com/minio/minio/pkg/bucket/object/lock" ) const ( @@ -121,6 +122,16 @@ type nasObjects struct { minio.ObjectLayer } +// GetBucketObjectLockConfig - not implemented +func (n *nasObjects) GetBucketObjectLockConfig(ctx context.Context, bucket string) (*objectlock.Config, error) { + return nil, minio.NotImplemented{} +} + +// SetBucketObjectLockConfig - not implemented +func (n *nasObjects) SetBucketObjectLockConfig(ctx context.Context, bucket string, _ *objectlock.Config) error { + return minio.NotImplemented{} +} + // IsReady returns whether the layer is ready to take requests. func (n *nasObjects) IsReady(ctx context.Context) bool { sinfo := n.ObjectLayer.StorageInfo(ctx, false) diff --git a/cmd/gateway/s3/gateway-s3.go b/cmd/gateway/s3/gateway-s3.go index 70f3c12b8..77f0f4e4b 100644 --- a/cmd/gateway/s3/gateway-s3.go +++ b/cmd/gateway/s3/gateway-s3.go @@ -281,7 +281,11 @@ func (l *s3Objects) StorageInfo(ctx context.Context, _ bool) (si minio.StorageIn } // MakeBucket creates a new container on S3 backend. -func (l *s3Objects) MakeBucketWithLocation(ctx context.Context, bucket, location string) error { +func (l *s3Objects) MakeBucketWithLocation(ctx context.Context, bucket, location string, lockEnabled bool) error { + if lockEnabled { + return minio.NotImplemented{} + } + // Verify if bucket name is valid. // We are using a separate helper function here to validate bucket // names instead of IsValidBucketName() because there is a possibility diff --git a/cmd/globals.go b/cmd/globals.go index a2bb93743..bd5163284 100644 --- a/cmd/globals.go +++ b/cmd/globals.go @@ -35,7 +35,6 @@ import ( "github.com/minio/minio/cmd/crypto" xhttp "github.com/minio/minio/cmd/http" "github.com/minio/minio/pkg/auth" - objectlock "github.com/minio/minio/pkg/bucket/object/lock" "github.com/minio/minio/pkg/certs" "github.com/minio/minio/pkg/event" @@ -216,10 +215,9 @@ var ( globalOperationTimeout = newDynamicTimeout(10*time.Minute /*30*/, 600*time.Second) // default timeout for general ops globalHealingTimeout = newDynamicTimeout(30*time.Minute /*1*/, 30*time.Minute) // timeout for healing related ops - globalBucketObjectLockConfig = objectlock.NewBucketObjectLockConfig() - - globalBucketQuotaSys *BucketQuotaSys - globalBucketStorageCache bucketStorageCache + globalBucketObjectLockSys *BucketObjectLockSys + globalBucketQuotaSys *BucketQuotaSys + globalBucketStorageCache bucketStorageCache // Disk cache drives globalCacheConfig cache.Config diff --git a/cmd/notification.go b/cmd/notification.go index d1ed32a35..033536e74 100644 --- a/cmd/notification.go +++ b/cmd/notification.go @@ -566,7 +566,7 @@ func (sys *NotificationSys) SetBucketPolicy(ctx context.Context, bucketName stri // DeleteBucket - calls DeleteBucket RPC call on all peers. func (sys *NotificationSys) DeleteBucket(ctx context.Context, bucketName string) { globalNotificationSys.RemoveNotification(bucketName) - globalBucketObjectLockConfig.Remove(bucketName) + globalBucketObjectLockSys.Remove(bucketName) globalBucketQuotaSys.Remove(bucketName) globalPolicySys.Remove(bucketName) globalLifecycleSys.Remove(bucketName) diff --git a/cmd/object-api-common.go b/cmd/object-api-common.go index f3fa44320..56f09d38e 100644 --- a/cmd/object-api-common.go +++ b/cmd/object-api-common.go @@ -85,7 +85,10 @@ func deleteBucketMetadata(ctx context.Context, bucket string, objAPI ObjectLayer removePolicyConfig(ctx, objAPI, bucket) // Delete notification config, if present - ignore any errors. - removeNotificationConfig(ctx, objAPI, bucket) + logger.LogIf(ctx, removeNotificationConfig(ctx, objAPI, bucket)) + + // Delete bucket meta config, if present - ignore any errors. + logger.LogIf(ctx, removeBucketMeta(ctx, objAPI, bucket)) } // Depending on the disk type network or local, initialize storage API. @@ -144,13 +147,11 @@ func cleanupDir(ctx context.Context, storage StorageAPI, volume, dirPath string) // Removes notification.xml for a given bucket, only used during DeleteBucket. func removeNotificationConfig(ctx context.Context, objAPI ObjectLayer, bucket string) error { - // Verify bucket is valid. - if !IsValidBucketName(bucket) { - return BucketNameInvalid{Bucket: bucket} - } - ncPath := path.Join(bucketConfigPrefix, bucket, bucketNotificationConfig) - return objAPI.DeleteObject(ctx, minioMetaBucket, ncPath) + if err := deleteConfig(ctx, objAPI, ncPath); err != nil && err != errConfigNotFound { + return err + } + return nil } func listObjectsNonSlash(ctx context.Context, bucket, prefix, marker, delimiter string, maxKeys int, tpool *TreeWalkPool, listDir ListDirFunc, getObjInfo func(context.Context, string, string) (ObjectInfo, error), getObjectInfoDirs ...func(context.Context, string, string) (ObjectInfo, error)) (loi ListObjectsInfo, err error) { diff --git a/cmd/object-api-deleteobject_test.go b/cmd/object-api-deleteobject_test.go index ca0a25170..c3844fad8 100644 --- a/cmd/object-api-deleteobject_test.go +++ b/cmd/object-api-deleteobject_test.go @@ -85,7 +85,7 @@ func testDeleteObject(obj ObjectLayer, instanceType string, t TestErrHandler) { for i, testCase := range testCases { - err := obj.MakeBucketWithLocation(context.Background(), testCase.bucketName, "") + err := obj.MakeBucketWithLocation(context.Background(), testCase.bucketName, "", false) if err != nil { t.Fatalf("%s : %s", instanceType, err.Error()) } diff --git a/cmd/object-api-errors.go b/cmd/object-api-errors.go index 7487cb929..fdc09c90b 100644 --- a/cmd/object-api-errors.go +++ b/cmd/object-api-errors.go @@ -255,21 +255,35 @@ func (e InvalidMarkerPrefixCombination) Error() string { type BucketPolicyNotFound GenericError func (e BucketPolicyNotFound) Error() string { - return "No bucket policy found for bucket: " + e.Bucket + return "No bucket policy configuration found for bucket: " + e.Bucket } // BucketLifecycleNotFound - no bucket lifecycle found. type BucketLifecycleNotFound GenericError func (e BucketLifecycleNotFound) Error() string { - return "No bucket life cycle found for bucket : " + e.Bucket + return "No bucket lifecycle configuration found for bucket : " + e.Bucket } -// BucketSSEConfigNotFound - no bucket encryption config found +// BucketSSEConfigNotFound - no bucket encryption found type BucketSSEConfigNotFound GenericError func (e BucketSSEConfigNotFound) Error() string { - return "No bucket encryption found for bucket: " + e.Bucket + return "No bucket encryption configuration found for bucket: " + e.Bucket +} + +// BucketTaggingNotFound - no bucket tags found +type BucketTaggingNotFound GenericError + +func (e BucketTaggingNotFound) Error() string { + return "No bucket tags found for bucket: " + e.Bucket +} + +// BucketObjectLockConfigNotFound - no bucket object lock config found +type BucketObjectLockConfigNotFound GenericError + +func (e BucketObjectLockConfigNotFound) Error() string { + return "No bucket object lock configuration found for bucket: " + e.Bucket } // BucketQuotaConfigNotFound - no bucket quota config found. diff --git a/cmd/object-api-getobject_test.go b/cmd/object-api-getobject_test.go index 17ae84178..3cded55fd 100644 --- a/cmd/object-api-getobject_test.go +++ b/cmd/object-api-getobject_test.go @@ -42,7 +42,7 @@ func testGetObject(obj ObjectLayer, instanceType string, t TestErrHandler) { emptyDirName := "test-empty-dir/" // create bucket. - err := obj.MakeBucketWithLocation(context.Background(), bucketName, "") + err := obj.MakeBucketWithLocation(context.Background(), bucketName, "", false) // Stop the test if creation of the bucket fails. if err != nil { t.Fatalf("%s : %s", instanceType, err.Error()) @@ -194,7 +194,7 @@ func testGetObjectPermissionDenied(obj ObjectLayer, instanceType string, disks [ // Setup for the tests. bucketName := getRandomBucketName() // create bucket. - err := obj.MakeBucketWithLocation(context.Background(), bucketName, "") + err := obj.MakeBucketWithLocation(context.Background(), bucketName, "", false) // Stop the test if creation of the bucket fails. if err != nil { t.Fatalf("%s : %s", instanceType, err.Error()) @@ -304,7 +304,7 @@ func testGetObjectDiskNotFound(obj ObjectLayer, instanceType string, disks []str bucketName := getRandomBucketName() objectName := "test-object" // create bucket. - err := obj.MakeBucketWithLocation(context.Background(), bucketName, "") + err := obj.MakeBucketWithLocation(context.Background(), bucketName, "", false) // Stop the test if creation of the bucket fails. if err != nil { t.Fatalf("%s : %s", instanceType, err.Error()) diff --git a/cmd/object-api-getobjectinfo_test.go b/cmd/object-api-getobjectinfo_test.go index 4c87fcaef..ba44d9d1f 100644 --- a/cmd/object-api-getobjectinfo_test.go +++ b/cmd/object-api-getobjectinfo_test.go @@ -30,7 +30,7 @@ func TestGetObjectInfo(t *testing.T) { // Testing GetObjectInfo(). func testGetObjectInfo(obj ObjectLayer, instanceType string, t TestErrHandler) { // This bucket is used for testing getObjectInfo operations. - err := obj.MakeBucketWithLocation(context.Background(), "test-getobjectinfo", "") + err := obj.MakeBucketWithLocation(context.Background(), "test-getobjectinfo", "", false) if err != nil { t.Fatalf("%s : %s", instanceType, err.Error()) } diff --git a/cmd/object-api-interface.go b/cmd/object-api-interface.go index a8bb6fbda..2b8938e45 100644 --- a/cmd/object-api-interface.go +++ b/cmd/object-api-interface.go @@ -1,5 +1,5 @@ /* - * MinIO Cloud Storage, (C) 2016 MinIO, Inc. + * MinIO Cloud Storage, (C) 2016-2020 MinIO, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,6 +25,7 @@ import ( "github.com/minio/minio-go/v6/pkg/tags" bucketsse "github.com/minio/minio/pkg/bucket/encryption" "github.com/minio/minio/pkg/bucket/lifecycle" + objectlock "github.com/minio/minio/pkg/bucket/object/lock" "github.com/minio/minio/pkg/bucket/policy" "github.com/minio/minio/pkg/madmin" @@ -64,7 +65,7 @@ type ObjectLayer interface { StorageInfo(ctx context.Context, local bool) StorageInfo // local queries only local disks // Bucket operations. - MakeBucketWithLocation(ctx context.Context, bucket string, location string) error + MakeBucketWithLocation(ctx context.Context, bucket string, location string, lockEnabled bool) error GetBucketInfo(ctx context.Context, bucket string) (bucketInfo BucketInfo, err error) ListBuckets(ctx context.Context) (buckets []BucketInfo, err error) DeleteBucket(ctx context.Context, bucket string, forceDelete bool) error @@ -130,6 +131,15 @@ type ObjectLayer interface { GetBucketSSEConfig(context.Context, string) (*bucketsse.BucketSSEConfig, error) DeleteBucketSSEConfig(context.Context, string) error + // Bucket locking configuration operations + SetBucketObjectLockConfig(context.Context, string, *objectlock.Config) error + GetBucketObjectLockConfig(context.Context, string) (*objectlock.Config, error) + + // Bucket tagging operations + SetBucketTagging(context.Context, string, *tags.Tags) error + GetBucketTagging(context.Context, string) (*tags.Tags, error) + DeleteBucketTagging(context.Context, string) error + // Backend related metrics GetMetrics(ctx context.Context) (*Metrics, error) diff --git a/cmd/object-api-listobjects_test.go b/cmd/object-api-listobjects_test.go index 2879db660..46623206b 100644 --- a/cmd/object-api-listobjects_test.go +++ b/cmd/object-api-listobjects_test.go @@ -49,7 +49,7 @@ func testListObjects(obj ObjectLayer, instanceType string, t1 TestErrHandler) { "test-bucket-single-object", } for _, bucket := range testBuckets { - err := obj.MakeBucketWithLocation(context.Background(), bucket, "") + err := obj.MakeBucketWithLocation(context.Background(), bucket, "", false) if err != nil { t.Fatalf("%s : %s", instanceType, err.Error()) } @@ -669,7 +669,7 @@ func BenchmarkListObjects(b *testing.B) { bucket := "ls-benchmark-bucket" // Create a bucket. - err = obj.MakeBucketWithLocation(context.Background(), bucket, "") + err = obj.MakeBucketWithLocation(context.Background(), bucket, "", false) if err != nil { b.Fatal(err) } diff --git a/cmd/object-api-multipart_test.go b/cmd/object-api-multipart_test.go index 26f194bd9..c1546ab42 100644 --- a/cmd/object-api-multipart_test.go +++ b/cmd/object-api-multipart_test.go @@ -55,7 +55,7 @@ func testObjectNewMultipartUpload(obj ObjectLayer, instanceType string, t TestEr } // Create bucket before intiating NewMultipartUpload. - err = obj.MakeBucketWithLocation(context.Background(), bucket, "") + err = obj.MakeBucketWithLocation(context.Background(), bucket, "", false) if err != nil { // failed to create newbucket, abort. t.Fatalf("%s : %s", instanceType, err.Error()) @@ -89,7 +89,7 @@ func testObjectAbortMultipartUpload(obj ObjectLayer, instanceType string, t Test object := "minio-object" opts := ObjectOptions{} // Create bucket before intiating NewMultipartUpload. - err := obj.MakeBucketWithLocation(context.Background(), bucket, "") + err := obj.MakeBucketWithLocation(context.Background(), bucket, "", false) if err != nil { // failed to create newbucket, abort. t.Fatalf("%s : %s", instanceType, err.Error()) @@ -135,7 +135,7 @@ func testObjectAPIIsUploadIDExists(obj ObjectLayer, instanceType string, t TestE object := "minio-object" // Create bucket before intiating NewMultipartUpload. - err := obj.MakeBucketWithLocation(context.Background(), bucket, "") + err := obj.MakeBucketWithLocation(context.Background(), bucket, "", false) if err != nil { // Failed to create newbucket, abort. t.Fatalf("%s : %s", instanceType, err.Error()) @@ -166,7 +166,7 @@ func testObjectAPIPutObjectPart(obj ObjectLayer, instanceType string, t TestErrH object := "minio-object" opts := ObjectOptions{} // Create bucket before intiating NewMultipartUpload. - err := obj.MakeBucketWithLocation(context.Background(), bucket, "") + err := obj.MakeBucketWithLocation(context.Background(), bucket, "", false) if err != nil { // Failed to create newbucket, abort. t.Fatalf("%s : %s", instanceType, err.Error()) @@ -178,7 +178,7 @@ func testObjectAPIPutObjectPart(obj ObjectLayer, instanceType string, t TestErrH t.Fatalf("%s : %s", instanceType, err.Error()) } // Creating a dummy bucket for tests. - err = obj.MakeBucketWithLocation(context.Background(), "unused-bucket", "") + err = obj.MakeBucketWithLocation(context.Background(), "unused-bucket", "", false) if err != nil { // Failed to create newbucket, abort. t.Fatalf("%s : %s", instanceType, err.Error()) @@ -302,7 +302,7 @@ func testListMultipartUploads(obj ObjectLayer, instanceType string, t TestErrHan // objectNames[0]. // uploadIds [0]. // Create bucket before initiating NewMultipartUpload. - err := obj.MakeBucketWithLocation(context.Background(), bucketNames[0], "") + err := obj.MakeBucketWithLocation(context.Background(), bucketNames[0], "", false) if err != nil { // Failed to create newbucket, abort. t.Fatalf("%s : %s", instanceType, err.Error()) @@ -320,7 +320,7 @@ func testListMultipartUploads(obj ObjectLayer, instanceType string, t TestErrHan // objectNames[0]. // uploadIds [1-3]. // Bucket to test for mutiple upload Id's for a given object. - err = obj.MakeBucketWithLocation(context.Background(), bucketNames[1], "") + err = obj.MakeBucketWithLocation(context.Background(), bucketNames[1], "", false) if err != nil { // Failed to create newbucket, abort. t.Fatalf("%s : %s", instanceType, err.Error()) @@ -341,7 +341,7 @@ func testListMultipartUploads(obj ObjectLayer, instanceType string, t TestErrHan // bucketnames[2]. // objectNames[0-2]. // uploadIds [4-9]. - err = obj.MakeBucketWithLocation(context.Background(), bucketNames[2], "") + err = obj.MakeBucketWithLocation(context.Background(), bucketNames[2], "", false) if err != nil { // Failed to create newbucket, abort. t.Fatalf("%s : %s", instanceType, err.Error()) @@ -1166,7 +1166,7 @@ func testListObjectPartsDiskNotFound(obj ObjectLayer, instanceType string, disks // objectNames[0]. // uploadIds [0]. // Create bucket before intiating NewMultipartUpload. - err := obj.MakeBucketWithLocation(context.Background(), bucketNames[0], "") + err := obj.MakeBucketWithLocation(context.Background(), bucketNames[0], "", false) if err != nil { // Failed to create newbucket, abort. t.Fatalf("%s : %s", instanceType, err.Error()) @@ -1411,7 +1411,7 @@ func testListObjectParts(obj ObjectLayer, instanceType string, t TestErrHandler) // objectNames[0]. // uploadIds [0]. // Create bucket before intiating NewMultipartUpload. - err := obj.MakeBucketWithLocation(context.Background(), bucketNames[0], "") + err := obj.MakeBucketWithLocation(context.Background(), bucketNames[0], "", false) if err != nil { // Failed to create newbucket, abort. t.Fatalf("%s : %s", instanceType, err.Error()) @@ -1657,7 +1657,7 @@ func testObjectCompleteMultipartUpload(obj ObjectLayer, instanceType string, t T // objectNames[0]. // uploadIds [0]. // Create bucket before intiating NewMultipartUpload. - err = obj.MakeBucketWithLocation(context.Background(), bucketNames[0], "") + err = obj.MakeBucketWithLocation(context.Background(), bucketNames[0], "", false) if err != nil { // Failed to create newbucket, abort. t.Fatalf("%s : %s", instanceType, err) diff --git a/cmd/object-api-putobject_test.go b/cmd/object-api-putobject_test.go index a636adea2..e09b8bc05 100644 --- a/cmd/object-api-putobject_test.go +++ b/cmd/object-api-putobject_test.go @@ -46,14 +46,14 @@ func testObjectAPIPutObject(obj ObjectLayer, instanceType string, t TestErrHandl object := "minio-object" // Create bucket. - err := obj.MakeBucketWithLocation(context.Background(), bucket, "") + err := obj.MakeBucketWithLocation(context.Background(), bucket, "", false) if err != nil { // Failed to create newbucket, abort. t.Fatalf("%s : %s", instanceType, err.Error()) } // Creating a dummy bucket for tests. - err = obj.MakeBucketWithLocation(context.Background(), "unused-bucket", "") + err = obj.MakeBucketWithLocation(context.Background(), "unused-bucket", "", false) if err != nil { // Failed to create newbucket, abort. t.Fatalf("%s : %s", instanceType, err.Error()) @@ -206,14 +206,14 @@ func testObjectAPIPutObjectDiskNotFound(obj ObjectLayer, instanceType string, di object := "minio-object" // Create bucket. - err := obj.MakeBucketWithLocation(context.Background(), bucket, "") + err := obj.MakeBucketWithLocation(context.Background(), bucket, "", false) if err != nil { // Failed to create newbucket, abort. t.Fatalf("%s : %s", instanceType, err.Error()) } // Creating a dummy bucket for tests. - err = obj.MakeBucketWithLocation(context.Background(), "unused-bucket", "") + err = obj.MakeBucketWithLocation(context.Background(), "unused-bucket", "", false) if err != nil { // Failed to create newbucket, abort. t.Fatalf("%s : %s", instanceType, err.Error()) @@ -318,7 +318,7 @@ func testObjectAPIPutObjectStaleFiles(obj ObjectLayer, instanceType string, disk object := "minio-object" // Create bucket. - err := obj.MakeBucketWithLocation(context.Background(), bucket, "") + err := obj.MakeBucketWithLocation(context.Background(), bucket, "", false) if err != nil { // Failed to create newbucket, abort. t.Fatalf("%s : %s", instanceType, err.Error()) @@ -352,7 +352,7 @@ func testObjectAPIMultipartPutObjectStaleFiles(obj ObjectLayer, instanceType str object := "minio-object" // Create bucket. - err := obj.MakeBucketWithLocation(context.Background(), bucket, "") + err := obj.MakeBucketWithLocation(context.Background(), bucket, "", false) if err != nil { // Failed to create newbucket, abort. t.Fatalf("%s : %s", instanceType, err.Error()) diff --git a/cmd/object-handlers.go b/cmd/object-handlers.go index a50f443f2..cd3335746 100644 --- a/cmd/object-handlers.go +++ b/cmd/object-handlers.go @@ -2593,7 +2593,7 @@ func (api objectAPIHandlers) DeleteObjectHandler(w http.ResponseWriter, r *http. } apiErr := ErrNone - if _, ok := globalBucketObjectLockConfig.Get(bucket); ok { + if _, ok := globalBucketObjectLockSys.Get(bucket); ok { apiErr = enforceRetentionBypassForDelete(ctx, r, bucket, object, getObjectInfo) if apiErr != ErrNone && apiErr != ErrNoSuchKey { writeErrorResponse(ctx, w, errorCodes.ToAPIErr(apiErr), r.URL, guessIsBrowserReq(r)) @@ -2657,7 +2657,7 @@ func (api objectAPIHandlers) PutObjectLegalHoldHandler(w http.ResponseWriter, r return } - if _, ok := globalBucketObjectLockConfig.Get(bucket); !ok { + if _, ok := globalBucketObjectLockSys.Get(bucket); !ok { writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidBucketObjectLockConfiguration), r.URL, guessIsBrowserReq(r)) return } @@ -2739,7 +2739,7 @@ func (api objectAPIHandlers) GetObjectLegalHoldHandler(w http.ResponseWriter, r getObjectInfo = api.CacheAPI().GetObjectInfo } - if _, ok := globalBucketObjectLockConfig.Get(bucket); !ok { + if _, ok := globalBucketObjectLockSys.Get(bucket); !ok { writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidBucketObjectLockConfiguration), r.URL, guessIsBrowserReq(r)) return } @@ -2816,7 +2816,7 @@ func (api objectAPIHandlers) PutObjectRetentionHandler(w http.ResponseWriter, r return } - if _, ok := globalBucketObjectLockConfig.Get(bucket); !ok { + if _, ok := globalBucketObjectLockSys.Get(bucket); !ok { writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidBucketObjectLockConfiguration), r.URL, guessIsBrowserReq(r)) return } diff --git a/cmd/object_api_suite_test.go b/cmd/object_api_suite_test.go index 36a30670b..cd8764397 100644 --- a/cmd/object_api_suite_test.go +++ b/cmd/object_api_suite_test.go @@ -77,7 +77,7 @@ func (s *ObjectLayerAPISuite) TestMakeBucket(t *testing.T) { // Tests validate bucket creation. func testMakeBucket(obj ObjectLayer, instanceType string, t TestErrHandler) { - err := obj.MakeBucketWithLocation(context.Background(), "bucket-unknown", "") + err := obj.MakeBucketWithLocation(context.Background(), "bucket-unknown", "", false) if err != nil { t.Fatalf("%s: %s", instanceType, err) } @@ -91,7 +91,7 @@ func (s *ObjectLayerAPISuite) TestMultipartObjectCreation(t *testing.T) { // Tests validate creation of part files during Multipart operation. func testMultipartObjectCreation(obj ObjectLayer, instanceType string, t TestErrHandler) { var opts ObjectOptions - err := obj.MakeBucketWithLocation(context.Background(), "bucket", "") + err := obj.MakeBucketWithLocation(context.Background(), "bucket", "", false) if err != nil { t.Fatalf("%s: %s", instanceType, err) } @@ -135,7 +135,7 @@ func (s *ObjectLayerAPISuite) TestMultipartObjectAbort(t *testing.T) { // Tests validate abortion of Multipart operation. func testMultipartObjectAbort(obj ObjectLayer, instanceType string, t TestErrHandler) { var opts ObjectOptions - err := obj.MakeBucketWithLocation(context.Background(), "bucket", "") + err := obj.MakeBucketWithLocation(context.Background(), "bucket", "", false) if err != nil { t.Fatalf("%s: %s", instanceType, err) } @@ -181,7 +181,7 @@ func (s *ObjectLayerAPISuite) TestMultipleObjectCreation(t *testing.T) { func testMultipleObjectCreation(obj ObjectLayer, instanceType string, t TestErrHandler) { objects := make(map[string][]byte) var opts ObjectOptions - err := obj.MakeBucketWithLocation(context.Background(), "bucket", "") + err := obj.MakeBucketWithLocation(context.Background(), "bucket", "", false) if err != nil { t.Fatalf("%s: %s", instanceType, err) } @@ -236,7 +236,7 @@ func (s *ObjectLayerAPISuite) TestPaging(t *testing.T) { // Tests validate creation of objects and the order of listing using various filters for ListObjects operation. func testPaging(obj ObjectLayer, instanceType string, t TestErrHandler) { - obj.MakeBucketWithLocation(context.Background(), "bucket", "") + obj.MakeBucketWithLocation(context.Background(), "bucket", "", false) result, err := obj.ListObjects(context.Background(), "bucket", "", "", "", 0) if err != nil { t.Fatalf("%s: %s", instanceType, err) @@ -440,7 +440,7 @@ func (s *ObjectLayerAPISuite) TestObjectOverwriteWorks(t *testing.T) { // Tests validate overwriting of an existing object. func testObjectOverwriteWorks(obj ObjectLayer, instanceType string, t TestErrHandler) { - err := obj.MakeBucketWithLocation(context.Background(), "bucket", "") + err := obj.MakeBucketWithLocation(context.Background(), "bucket", "", false) if err != nil { t.Fatalf("%s: %s", instanceType, err) } @@ -494,11 +494,11 @@ func (s *ObjectLayerAPISuite) TestBucketRecreateFails(t *testing.T) { // Tests validate that recreation of the bucket fails. func testBucketRecreateFails(obj ObjectLayer, instanceType string, t TestErrHandler) { - err := obj.MakeBucketWithLocation(context.Background(), "string", "") + err := obj.MakeBucketWithLocation(context.Background(), "string", "", false) if err != nil { t.Fatalf("%s: %s", instanceType, err) } - err = obj.MakeBucketWithLocation(context.Background(), "string", "") + err = obj.MakeBucketWithLocation(context.Background(), "string", "", false) if err == nil { t.Fatalf("%s: Expected error but found nil.", instanceType) } @@ -519,7 +519,7 @@ func testPutObject(obj ObjectLayer, instanceType string, t TestErrHandler) { length := int64(len(content)) readerEOF := newTestReaderEOF(content) readerNoEOF := newTestReaderNoEOF(content) - err := obj.MakeBucketWithLocation(context.Background(), "bucket", "") + err := obj.MakeBucketWithLocation(context.Background(), "bucket", "", false) if err != nil { t.Fatalf("%s: %s", instanceType, err) } @@ -559,7 +559,7 @@ func (s *ObjectLayerAPISuite) TestPutObjectInSubdir(t *testing.T) { // Tests validate PutObject with subdirectory prefix. func testPutObjectInSubdir(obj ObjectLayer, instanceType string, t TestErrHandler) { - err := obj.MakeBucketWithLocation(context.Background(), "bucket", "") + err := obj.MakeBucketWithLocation(context.Background(), "bucket", "", false) if err != nil { t.Fatalf("%s: %s", instanceType, err) } @@ -601,7 +601,7 @@ func testListBuckets(obj ObjectLayer, instanceType string, t TestErrHandler) { } // add one and test exists. - err = obj.MakeBucketWithLocation(context.Background(), "bucket1", "") + err = obj.MakeBucketWithLocation(context.Background(), "bucket1", "", false) if err != nil { t.Fatalf("%s: %s", instanceType, err) } @@ -615,7 +615,7 @@ func testListBuckets(obj ObjectLayer, instanceType string, t TestErrHandler) { } // add two and test exists. - err = obj.MakeBucketWithLocation(context.Background(), "bucket2", "") + err = obj.MakeBucketWithLocation(context.Background(), "bucket2", "", false) if err != nil { t.Fatalf("%s: %s", instanceType, err) } @@ -629,7 +629,7 @@ func testListBuckets(obj ObjectLayer, instanceType string, t TestErrHandler) { } // add three and test exists + prefix. - err = obj.MakeBucketWithLocation(context.Background(), "bucket22", "") + err = obj.MakeBucketWithLocation(context.Background(), "bucket22", "", false) if err != nil { t.Fatalf("%s: %s", instanceType, err) } @@ -653,11 +653,11 @@ func testListBucketsOrder(obj ObjectLayer, instanceType string, t TestErrHandler // if implementation contains a map, order of map keys will vary. // this ensures they return in the same order each time. // add one and test exists. - err := obj.MakeBucketWithLocation(context.Background(), "bucket1", "") + err := obj.MakeBucketWithLocation(context.Background(), "bucket1", "", false) if err != nil { t.Fatalf("%s: %s", instanceType, err) } - err = obj.MakeBucketWithLocation(context.Background(), "bucket2", "") + err = obj.MakeBucketWithLocation(context.Background(), "bucket2", "", false) if err != nil { t.Fatalf("%s: %s", instanceType, err) } @@ -706,7 +706,7 @@ func (s *ObjectLayerAPISuite) TestNonExistantObjectInBucket(t *testing.T) { // Tests validate that GetObject fails on a non-existent bucket as expected. func testNonExistantObjectInBucket(obj ObjectLayer, instanceType string, t TestErrHandler) { - err := obj.MakeBucketWithLocation(context.Background(), "bucket", "") + err := obj.MakeBucketWithLocation(context.Background(), "bucket", "", false) if err != nil { t.Fatalf("%s: %s", instanceType, err) } @@ -734,7 +734,7 @@ func (s *ObjectLayerAPISuite) TestGetDirectoryReturnsObjectNotFound(t *testing.T // Tests validate that GetObject on an existing directory fails as expected. func testGetDirectoryReturnsObjectNotFound(obj ObjectLayer, instanceType string, t TestErrHandler) { bucketName := "bucket" - err := obj.MakeBucketWithLocation(context.Background(), bucketName, "") + err := obj.MakeBucketWithLocation(context.Background(), bucketName, "", false) if err != nil { t.Fatalf("%s: %s", instanceType, err) } @@ -776,7 +776,7 @@ func (s *ObjectLayerAPISuite) TestContentType(t *testing.T) { // Test content-type. func testContentType(obj ObjectLayer, instanceType string, t TestErrHandler) { - err := obj.MakeBucketWithLocation(context.Background(), "bucket", "") + err := obj.MakeBucketWithLocation(context.Background(), "bucket", "", false) if err != nil { t.Fatalf("%s: %s", instanceType, err) } diff --git a/cmd/peer-rest-server.go b/cmd/peer-rest-server.go index 032999c92..fe7305954 100644 --- a/cmd/peer-rest-server.go +++ b/cmd/peer-rest-server.go @@ -598,7 +598,7 @@ func (s *peerRESTServer) DeleteBucketHandler(w http.ResponseWriter, r *http.Requ globalNotificationSys.RemoveNotification(bucketName) globalPolicySys.Remove(bucketName) - globalBucketObjectLockConfig.Remove(bucketName) + globalBucketObjectLockSys.Remove(bucketName) globalBucketQuotaSys.Remove(bucketName) globalLifecycleSys.Remove(bucketName) @@ -843,7 +843,7 @@ func (s *peerRESTServer) RemoveBucketObjectLockConfigHandler(w http.ResponseWrit return } - globalBucketObjectLockConfig.Remove(bucketName) + globalBucketObjectLockSys.Remove(bucketName) w.(http.Flusher).Flush() } @@ -873,7 +873,7 @@ func (s *peerRESTServer) PutBucketObjectLockConfigHandler(w http.ResponseWriter, return } - globalBucketObjectLockConfig.Set(bucketName, retention) + globalBucketObjectLockSys.Set(bucketName, retention) w.(http.Flusher).Flush() } diff --git a/cmd/post-policy_test.go b/cmd/post-policy_test.go index 078032494..6aadaa3d2 100644 --- a/cmd/post-policy_test.go +++ b/cmd/post-policy_test.go @@ -141,7 +141,7 @@ func testPostPolicyBucketHandler(obj ObjectLayer, instanceType string, t TestErr // objectNames[0]. // uploadIds [0]. // Create bucket before initiating NewMultipartUpload. - err := obj.MakeBucketWithLocation(context.Background(), bucketName, "") + err := obj.MakeBucketWithLocation(context.Background(), bucketName, "", false) if err != nil { // Failed to create newbucket, abort. t.Fatalf("%s : %s", instanceType, err.Error()) @@ -451,7 +451,7 @@ func testPostPolicyBucketHandlerRedirect(obj ObjectLayer, instanceType string, t curTime := UTCNow() curTimePlus5Min := curTime.Add(time.Minute * 5) - err = obj.MakeBucketWithLocation(context.Background(), bucketName, "") + err = obj.MakeBucketWithLocation(context.Background(), bucketName, "", false) if err != nil { // Failed to create newbucket, abort. t.Fatalf("%s : %s", instanceType, err.Error()) diff --git a/cmd/server-main.go b/cmd/server-main.go index 25662da00..ffed2f3e6 100644 --- a/cmd/server-main.go +++ b/cmd/server-main.go @@ -156,6 +156,9 @@ func newAllSubsystems() { // Create new bucket encryption subsystem globalBucketSSEConfigSys = NewBucketSSEConfigSys() + // Create new bucket object lock subsystem + globalBucketObjectLockSys = NewBucketObjectLockSys() + // Create new bucket quota subsystem globalBucketQuotaSys = NewBucketQuotaSys() } @@ -281,7 +284,7 @@ func initAllSubsystems(newObject ObjectLayer) (err error) { wquorum := &InsufficientWriteQuorum{} rquorum := &InsufficientReadQuorum{} for _, bucket := range buckets { - if err = newObject.MakeBucketWithLocation(GlobalContext, bucket.Name, ""); err != nil { + if err = newObject.MakeBucketWithLocation(GlobalContext, bucket.Name, "", false); err != nil { if errors.As(err, &wquorum) || errors.As(err, &rquorum) { // Retrun the error upwards for the caller to retry. return fmt.Errorf("Unable to heal bucket: %w", err) @@ -329,11 +332,6 @@ func initAllSubsystems(newObject ObjectLayer) (err error) { return fmt.Errorf("Unable to initialize policy system: %w", err) } - // Initialize bucket object lock. - if err = initBucketObjectLockConfig(buckets, newObject); err != nil { - return fmt.Errorf("Unable to initialize object lock system: %w", err) - } - // Initialize lifecycle system. if err = globalLifecycleSys.Init(buckets, newObject); err != nil { return fmt.Errorf("Unable to initialize lifecycle system: %w", err) @@ -344,6 +342,11 @@ func initAllSubsystems(newObject ObjectLayer) (err error) { return fmt.Errorf("Unable to initialize bucket encryption subsystem: %w", err) } + // Initialize bucket object lock. + if err = globalBucketObjectLockSys.Init(buckets, newObject); err != nil { + return fmt.Errorf("Unable to initialize object lock system: %w", err) + } + // Initialize bucket quota system. if err = globalBucketQuotaSys.Init(buckets, newObject); err != nil { return fmt.Errorf("Unable to initialize bucket quota system: %w", err) diff --git a/cmd/test-utils_test.go b/cmd/test-utils_test.go index 57f616c70..38e53c7e7 100644 --- a/cmd/test-utils_test.go +++ b/cmd/test-utils_test.go @@ -1640,7 +1640,7 @@ func initAPIHandlerTest(obj ObjectLayer, endpoints []string) (string, http.Handl bucketName := getRandomBucketName() // Create bucket. - err := obj.MakeBucketWithLocation(context.Background(), bucketName, "") + err := obj.MakeBucketWithLocation(context.Background(), bucketName, "", false) if err != nil { // failed to create newbucket, return err. return "", nil, err diff --git a/cmd/web-handlers.go b/cmd/web-handlers.go index 5a2acb227..be422f76b 100644 --- a/cmd/web-handlers.go +++ b/cmd/web-handlers.go @@ -172,7 +172,7 @@ func (web *webAPIHandlers) MakeBucket(r *http.Request, args *MakeBucketArgs, rep if _, err := globalDNSConfig.Get(args.BucketName); err != nil { if err == dns.ErrNoEntriesFound { // Proceed to creating a bucket. - if err = objectAPI.MakeBucketWithLocation(ctx, args.BucketName, globalServerRegion); err != nil { + if err = objectAPI.MakeBucketWithLocation(ctx, args.BucketName, globalServerRegion, false); err != nil { return toJSONError(ctx, err) } if err = globalDNSConfig.Put(args.BucketName); err != nil { @@ -188,7 +188,7 @@ func (web *webAPIHandlers) MakeBucket(r *http.Request, args *MakeBucketArgs, rep return toJSONError(ctx, errBucketAlreadyExists) } - if err := objectAPI.MakeBucketWithLocation(ctx, args.BucketName, globalServerRegion); err != nil { + if err := objectAPI.MakeBucketWithLocation(ctx, args.BucketName, globalServerRegion, false); err != nil { return toJSONError(ctx, err, args.BucketName) } @@ -261,7 +261,7 @@ func (web *webAPIHandlers) DeleteBucket(r *http.Request, args *RemoveBucketArgs, if globalDNSConfig != nil { if err := globalDNSConfig.Delete(args.BucketName); err != nil { // Deleting DNS entry failed, attempt to create the bucket again. - objectAPI.MakeBucketWithLocation(ctx, args.BucketName, "") + objectAPI.MakeBucketWithLocation(ctx, args.BucketName, "", false) return toJSONError(ctx, err) } } diff --git a/cmd/web-handlers_test.go b/cmd/web-handlers_test.go index c98c8e2e2..950d1c9a6 100644 --- a/cmd/web-handlers_test.go +++ b/cmd/web-handlers_test.go @@ -320,7 +320,7 @@ func testDeleteBucketWebHandler(obj ObjectLayer, instanceType string, t TestErrH bucketName := getRandomBucketName() var opts ObjectOptions - err = obj.MakeBucketWithLocation(context.Background(), bucketName, "") + err = obj.MakeBucketWithLocation(context.Background(), bucketName, "", false) if err != nil { t.Fatalf("failed to create bucket: %s (%s)", err.Error(), instanceType) } @@ -398,7 +398,7 @@ func testDeleteBucketWebHandler(obj ObjectLayer, instanceType string, t TestErrH continue } - err = obj.MakeBucketWithLocation(context.Background(), bucketName, "") + err = obj.MakeBucketWithLocation(context.Background(), bucketName, "", false) if err != nil { // failed to create new bucket, abort. t.Fatalf("failed to create new bucket (%s): %s", instanceType, err.Error()) @@ -426,7 +426,7 @@ func testListBucketsWebHandler(obj ObjectLayer, instanceType string, t TestErrHa bucketName := getRandomBucketName() // Create bucket. - err = obj.MakeBucketWithLocation(context.Background(), bucketName, "") + err = obj.MakeBucketWithLocation(context.Background(), bucketName, "", false) if err != nil { // failed to create newbucket, abort. t.Fatalf("%s : %s", instanceType, err) @@ -477,7 +477,7 @@ func testListObjectsWebHandler(obj ObjectLayer, instanceType string, t TestErrHa objectSize := 1 * humanize.KiByte // Create bucket. - err = obj.MakeBucketWithLocation(context.Background(), bucketName, "") + err = obj.MakeBucketWithLocation(context.Background(), bucketName, "", false) if err != nil { // failed to create newbucket, abort. t.Fatalf("%s : %s", instanceType, err) @@ -581,7 +581,7 @@ func testRemoveObjectWebHandler(obj ObjectLayer, instanceType string, t TestErrH objectSize := 1 * humanize.KiByte // Create bucket. - err = obj.MakeBucketWithLocation(context.Background(), bucketName, "") + err = obj.MakeBucketWithLocation(context.Background(), bucketName, "", false) if err != nil { // failed to create newbucket, abort. t.Fatalf("%s : %s", instanceType, err) @@ -858,7 +858,7 @@ func testUploadWebHandler(obj ObjectLayer, instanceType string, t TestErrHandler return rec.Code } // Create bucket. - err = obj.MakeBucketWithLocation(context.Background(), bucketName, "") + err = obj.MakeBucketWithLocation(context.Background(), bucketName, "", false) if err != nil { // failed to create newbucket, abort. t.Fatalf("%s : %s", instanceType, err) @@ -955,7 +955,7 @@ func testDownloadWebHandler(obj ObjectLayer, instanceType string, t TestErrHandl } // Create bucket. - err = obj.MakeBucketWithLocation(context.Background(), bucketName, "") + err = obj.MakeBucketWithLocation(context.Background(), bucketName, "", false) if err != nil { // failed to create newbucket, abort. t.Fatalf("%s : %s", instanceType, err) @@ -1060,7 +1060,7 @@ func testWebHandlerDownloadZip(obj ObjectLayer, instanceType string, t TestErrHa fileThree := "cccccccccccccc" // Create bucket. - err = obj.MakeBucketWithLocation(context.Background(), bucket, "") + err = obj.MakeBucketWithLocation(context.Background(), bucket, "", false) if err != nil { // failed to create newbucket, abort. t.Fatalf("%s : %s", instanceType, err) @@ -1147,7 +1147,7 @@ func testWebPresignedGetHandler(obj ObjectLayer, instanceType string, t TestErrH objectSize := 1 * humanize.KiByte // Create bucket. - err = obj.MakeBucketWithLocation(context.Background(), bucketName, "") + err = obj.MakeBucketWithLocation(context.Background(), bucketName, "", false) if err != nil { // failed to create newbucket, abort. t.Fatalf("%s : %s", instanceType, err) @@ -1248,7 +1248,7 @@ func testWebGetBucketPolicyHandler(obj ObjectLayer, instanceType string, t TestE rec := httptest.NewRecorder() bucketName := getRandomBucketName() - if err = obj.MakeBucketWithLocation(context.Background(), bucketName, ""); err != nil { + if err = obj.MakeBucketWithLocation(context.Background(), bucketName, "", false); err != nil { t.Fatal("Unexpected error: ", err) } @@ -1323,7 +1323,7 @@ func testWebListAllBucketPoliciesHandler(obj ObjectLayer, instanceType string, t rec := httptest.NewRecorder() bucketName := getRandomBucketName() - if err = obj.MakeBucketWithLocation(context.Background(), bucketName, ""); err != nil { + if err = obj.MakeBucketWithLocation(context.Background(), bucketName, "", false); err != nil { t.Fatal("Unexpected error: ", err) } @@ -1428,7 +1428,7 @@ func testWebSetBucketPolicyHandler(obj ObjectLayer, instanceType string, t TestE // Create a bucket bucketName := getRandomBucketName() - if err = obj.MakeBucketWithLocation(context.Background(), bucketName, ""); err != nil { + if err = obj.MakeBucketWithLocation(context.Background(), bucketName, "", false); err != nil { t.Fatal("Unexpected error: ", err) } @@ -1587,7 +1587,7 @@ func TestWebObjectLayerFaultyDisks(t *testing.T) { } bucketName := "mybucket" - err = obj.MakeBucketWithLocation(context.Background(), bucketName, "") + err = obj.MakeBucketWithLocation(context.Background(), bucketName, "", false) if err != nil { t.Fatal("Cannot make bucket:", err) } diff --git a/cmd/xl-sets.go b/cmd/xl-sets.go index 6de856115..6961f5c82 100644 --- a/cmd/xl-sets.go +++ b/cmd/xl-sets.go @@ -34,6 +34,7 @@ import ( "github.com/minio/minio/pkg/bpool" bucketsse "github.com/minio/minio/pkg/bucket/encryption" "github.com/minio/minio/pkg/bucket/lifecycle" + objectlock "github.com/minio/minio/pkg/bucket/object/lock" "github.com/minio/minio/pkg/bucket/policy" "github.com/minio/minio/pkg/dsync" "github.com/minio/minio/pkg/madmin" @@ -509,14 +510,14 @@ func (s *xlSets) Shutdown(ctx context.Context) error { // MakeBucketLocation - creates a new bucket across all sets simultaneously // even if one of the sets fail to create buckets, we proceed to undo a // successful operation. -func (s *xlSets) MakeBucketWithLocation(ctx context.Context, bucket, location string) error { +func (s *xlSets) MakeBucketWithLocation(ctx context.Context, bucket, location string, lockEnabled bool) error { g := errgroup.WithNErrs(len(s.sets)) // Create buckets in parallel across all sets. for index := range s.sets { index := index g.Go(func() error { - return s.sets[index].MakeBucketWithLocation(ctx, bucket, location) + return s.sets[index].MakeBucketWithLocation(ctx, bucket, location, lockEnabled) }, index) } @@ -657,6 +658,31 @@ func (s *xlSets) DeleteBucketSSEConfig(ctx context.Context, bucket string) error return removeBucketSSEConfig(ctx, s, bucket) } +// SetBucketObjectLockConfig enables/clears default object lock configuration +func (s *xlSets) SetBucketObjectLockConfig(ctx context.Context, bucket string, config *objectlock.Config) error { + return saveBucketObjectLockConfig(ctx, s, bucket, config) +} + +// GetBucketObjectLockConfig - returns current defaults for object lock configuration +func (s *xlSets) GetBucketObjectLockConfig(ctx context.Context, bucket string) (*objectlock.Config, error) { + return readBucketObjectLockConfig(ctx, s, bucket) +} + +// SetBucketTagging sets bucket tags on given bucket +func (s *xlSets) SetBucketTagging(ctx context.Context, bucket string, t *tags.Tags) error { + return saveBucketTagging(ctx, s, bucket, t) +} + +// GetBucketTagging get bucket tags set on given bucket +func (s *xlSets) GetBucketTagging(ctx context.Context, bucket string) (*tags.Tags, error) { + return readBucketTagging(ctx, s, bucket) +} + +// DeleteBucketTagging delete bucket tags set if any. +func (s *xlSets) DeleteBucketTagging(ctx context.Context, bucket string) error { + return deleteBucketTagging(ctx, s, bucket) +} + // IsNotificationSupported returns whether bucket notification is applicable for this layer. func (s *xlSets) IsNotificationSupported() bool { return s.getHashedSet("").IsNotificationSupported() @@ -717,7 +743,7 @@ func undoDeleteBucketSets(bucket string, sets []*xlObjects, errs []error) { index := index g.Go(func() error { if errs[index] == nil { - return sets[index].MakeBucketWithLocation(GlobalContext, bucket, "") + return sets[index].MakeBucketWithLocation(GlobalContext, bucket, "", false) } return nil }, index) diff --git a/cmd/xl-v1-bucket.go b/cmd/xl-v1-bucket.go index ae0bcdaba..8eefb1f17 100644 --- a/cmd/xl-v1-bucket.go +++ b/cmd/xl-v1-bucket.go @@ -21,9 +21,11 @@ import ( "sort" "github.com/minio/minio-go/v6/pkg/s3utils" + "github.com/minio/minio-go/v6/pkg/tags" "github.com/minio/minio/cmd/logger" bucketsse "github.com/minio/minio/pkg/bucket/encryption" "github.com/minio/minio/pkg/bucket/lifecycle" + objectlock "github.com/minio/minio/pkg/bucket/object/lock" "github.com/minio/minio/pkg/bucket/policy" "github.com/minio/minio/pkg/sync/errgroup" @@ -38,7 +40,7 @@ var bucketMetadataOpIgnoredErrs = append(bucketOpIgnoredErrs, errVolumeNotFound) /// Bucket operations // MakeBucket - make a bucket. -func (xl xlObjects) MakeBucketWithLocation(ctx context.Context, bucket, location string) error { +func (xl xlObjects) MakeBucketWithLocation(ctx context.Context, bucket, location string, lockEnabled bool) error { // Verify if bucket is valid. if err := s3utils.CheckValidBucketNameStrict(bucket); err != nil { return BucketNameInvalid{Bucket: bucket} @@ -321,6 +323,31 @@ func (xl xlObjects) DeleteBucketSSEConfig(ctx context.Context, bucket string) er return removeBucketSSEConfig(ctx, xl, bucket) } +// SetBucketObjectLockConfig enables/clears default object lock configuration +func (xl xlObjects) SetBucketObjectLockConfig(ctx context.Context, bucket string, config *objectlock.Config) error { + return saveBucketObjectLockConfig(ctx, xl, bucket, config) +} + +// GetBucketObjectLockConfig - returns current defaults for object lock configuration +func (xl xlObjects) GetBucketObjectLockConfig(ctx context.Context, bucket string) (*objectlock.Config, error) { + return readBucketObjectLockConfig(ctx, xl, bucket) +} + +// SetBucketTagging sets bucket tags on given bucket +func (xl xlObjects) SetBucketTagging(ctx context.Context, bucket string, t *tags.Tags) error { + return saveBucketTagging(ctx, xl, bucket, t) +} + +// GetBucketTagging get bucket tags set on given bucket +func (xl xlObjects) GetBucketTagging(ctx context.Context, bucket string) (*tags.Tags, error) { + return readBucketTagging(ctx, xl, bucket) +} + +// DeleteBucketTagging delete bucket tags set if any. +func (xl xlObjects) DeleteBucketTagging(ctx context.Context, bucket string) error { + return deleteBucketTagging(ctx, xl, bucket) +} + // IsNotificationSupported returns whether bucket notification is applicable for this layer. func (xl xlObjects) IsNotificationSupported() bool { return true diff --git a/cmd/xl-v1-common_test.go b/cmd/xl-v1-common_test.go index ba509fb0f..876ecb72e 100644 --- a/cmd/xl-v1-common_test.go +++ b/cmd/xl-v1-common_test.go @@ -41,7 +41,7 @@ func TestXLParentDirIsObject(t *testing.T) { bucketName := "testbucket" objectName := "object" - if err = obj.MakeBucketWithLocation(GlobalContext, bucketName, ""); err != nil { + if err = obj.MakeBucketWithLocation(GlobalContext, bucketName, "", false); err != nil { t.Fatal(err) } objectContent := "12345" diff --git a/cmd/xl-v1-healing-common_test.go b/cmd/xl-v1-healing-common_test.go index 32b4be1df..7d2df85e1 100644 --- a/cmd/xl-v1-healing-common_test.go +++ b/cmd/xl-v1-healing-common_test.go @@ -181,7 +181,7 @@ func TestListOnlineDisks(t *testing.T) { obj.DeleteObject(GlobalContext, bucket, object) obj.DeleteBucket(GlobalContext, bucket, false) - err = obj.MakeBucketWithLocation(GlobalContext, "bucket", "") + err = obj.MakeBucketWithLocation(GlobalContext, "bucket", "", false) if err != nil { t.Fatalf("Failed to make a bucket %v", err) } @@ -276,7 +276,7 @@ func TestDisksWithAllParts(t *testing.T) { z := obj.(*xlZones) xl := z.zones[0].sets[0] xlDisks := xl.getDisks() - err = obj.MakeBucketWithLocation(ctx, "bucket", "") + err = obj.MakeBucketWithLocation(ctx, "bucket", "", false) if err != nil { t.Fatalf("Failed to make a bucket %v", err) } diff --git a/cmd/xl-v1-healing_test.go b/cmd/xl-v1-healing_test.go index 5e56a2f77..859995cad 100644 --- a/cmd/xl-v1-healing_test.go +++ b/cmd/xl-v1-healing_test.go @@ -44,7 +44,7 @@ func TestUndoMakeBucket(t *testing.T) { } bucketName := getRandomBucketName() - if err = obj.MakeBucketWithLocation(ctx, bucketName, ""); err != nil { + if err = obj.MakeBucketWithLocation(ctx, bucketName, "", false); err != nil { t.Fatal(err) } z := obj.(*xlZones) @@ -88,7 +88,7 @@ func TestHealObjectCorrupted(t *testing.T) { data := bytes.Repeat([]byte("a"), 5*1024*1024) var opts ObjectOptions - err = objLayer.MakeBucketWithLocation(ctx, bucket, "") + err = objLayer.MakeBucketWithLocation(ctx, bucket, "", false) if err != nil { t.Fatalf("Failed to make a bucket - %v", err) } @@ -233,7 +233,7 @@ func TestHealObjectXL(t *testing.T) { data := bytes.Repeat([]byte("a"), 5*1024*1024) var opts ObjectOptions - err = obj.MakeBucketWithLocation(ctx, bucket, "") + err = obj.MakeBucketWithLocation(ctx, bucket, "", false) if err != nil { t.Fatalf("Failed to make a bucket - %v", err) } @@ -322,7 +322,7 @@ func TestHealEmptyDirectoryXL(t *testing.T) { object := "empty-dir/" var opts ObjectOptions - err = obj.MakeBucketWithLocation(ctx, bucket, "") + err = obj.MakeBucketWithLocation(ctx, bucket, "", false) if err != nil { t.Fatalf("Failed to make a bucket - %v", err) } diff --git a/cmd/xl-v1-multipart_test.go b/cmd/xl-v1-multipart_test.go index 61b1a5268..f2f14ed96 100644 --- a/cmd/xl-v1-multipart_test.go +++ b/cmd/xl-v1-multipart_test.go @@ -43,7 +43,7 @@ func TestXLCleanupStaleMultipartUploads(t *testing.T) { objectName := "object" var opts ObjectOptions - obj.MakeBucketWithLocation(ctx, bucketName, "") + obj.MakeBucketWithLocation(ctx, bucketName, "", false) uploadID, err := obj.NewMultipartUpload(GlobalContext, bucketName, objectName, opts) if err != nil { t.Fatal("Unexpected err: ", err) diff --git a/cmd/xl-v1-object_test.go b/cmd/xl-v1-object_test.go index cc632107d..1e7dfac3d 100644 --- a/cmd/xl-v1-object_test.go +++ b/cmd/xl-v1-object_test.go @@ -49,7 +49,7 @@ func TestRepeatPutObjectPart(t *testing.T) { // cleaning up of temporary test directories defer removeRoots(disks) - err = objLayer.MakeBucketWithLocation(ctx, "bucket1", "") + err = objLayer.MakeBucketWithLocation(ctx, "bucket1", "", false) if err != nil { t.Fatal(err) } @@ -96,7 +96,7 @@ func TestXLDeleteObjectBasic(t *testing.T) { t.Fatal(err) } - err = xl.MakeBucketWithLocation(ctx, "bucket", "") + err = xl.MakeBucketWithLocation(ctx, "bucket", "", false) if err != nil { t.Fatal(err) } @@ -152,7 +152,7 @@ func TestXLDeleteObjectsXLSet(t *testing.T) { {bucketName, "obj_4"}, } - err := xlSets.MakeBucketWithLocation(GlobalContext, bucketName, "") + err := xlSets.MakeBucketWithLocation(GlobalContext, bucketName, "", false) if err != nil { t.Fatal(err) } @@ -211,7 +211,7 @@ func TestXLDeleteObjectDiskNotFound(t *testing.T) { xl := z.zones[0].sets[0] // Create "bucket" - err = obj.MakeBucketWithLocation(ctx, "bucket", "") + err = obj.MakeBucketWithLocation(ctx, "bucket", "", false) if err != nil { t.Fatal(err) } @@ -278,7 +278,7 @@ func TestGetObjectNoQuorum(t *testing.T) { xl := z.zones[0].sets[0] // Create "bucket" - err = obj.MakeBucketWithLocation(ctx, "bucket", "") + err = obj.MakeBucketWithLocation(ctx, "bucket", "", false) if err != nil { t.Fatal(err) } @@ -340,7 +340,7 @@ func TestPutObjectNoQuorum(t *testing.T) { xl := z.zones[0].sets[0] // Create "bucket" - err = obj.MakeBucketWithLocation(ctx, "bucket", "") + err = obj.MakeBucketWithLocation(ctx, "bucket", "", false) if err != nil { t.Fatal(err) } @@ -400,7 +400,7 @@ func TestHealing(t *testing.T) { xl := z.zones[0].sets[0] // Create "bucket" - err = obj.MakeBucketWithLocation(ctx, "bucket", "") + err = obj.MakeBucketWithLocation(ctx, "bucket", "", false) if err != nil { t.Fatal(err) } @@ -511,7 +511,7 @@ func testObjectQuorumFromMeta(obj ObjectLayer, instanceType string, dirs []strin xl := z.zones[0].sets[0] xlDisks := xl.getDisks() - err := obj.MakeBucketWithLocation(GlobalContext, bucket, globalMinioDefaultRegion) + err := obj.MakeBucketWithLocation(GlobalContext, bucket, globalMinioDefaultRegion, false) if err != nil { t.Fatalf("Failed to make a bucket %v", err) } diff --git a/cmd/xl-zones.go b/cmd/xl-zones.go index 379334e22..b1e1aa42e 100644 --- a/cmd/xl-zones.go +++ b/cmd/xl-zones.go @@ -31,6 +31,7 @@ import ( "github.com/minio/minio/cmd/logger" bucketsse "github.com/minio/minio/pkg/bucket/encryption" "github.com/minio/minio/pkg/bucket/lifecycle" + objectlock "github.com/minio/minio/pkg/bucket/object/lock" "github.com/minio/minio/pkg/bucket/policy" "github.com/minio/minio/pkg/madmin" "github.com/minio/minio/pkg/sync/errgroup" @@ -336,9 +337,19 @@ func undoMakeBucketZones(bucket string, zones []*xlSets, errs []error) { // MakeBucketWithLocation - creates a new bucket across all zones simultaneously // even if one of the sets fail to create buckets, we proceed all the successful // operations. -func (z *xlZones) MakeBucketWithLocation(ctx context.Context, bucket, location string) error { +func (z *xlZones) MakeBucketWithLocation(ctx context.Context, bucket, location string, lockEnabled bool) error { if z.SingleZone() { - return z.zones[0].MakeBucketWithLocation(ctx, bucket, location) + if err := z.zones[0].MakeBucketWithLocation(ctx, bucket, location, lockEnabled); err != nil { + return err + } + if lockEnabled { + meta := newBucketMetadata(bucket) + meta.LockEnabled = lockEnabled + if err := meta.save(ctx, z); err != nil { + return toObjectErr(err, bucket) + } + } + return nil } g := errgroup.WithNErrs(len(z.zones)) @@ -347,7 +358,7 @@ func (z *xlZones) MakeBucketWithLocation(ctx context.Context, bucket, location s for index := range z.zones { index := index g.Go(func() error { - return z.zones[index].MakeBucketWithLocation(ctx, bucket, location) + return z.zones[index].MakeBucketWithLocation(ctx, bucket, location, lockEnabled) }, index) } @@ -362,6 +373,14 @@ func (z *xlZones) MakeBucketWithLocation(ctx context.Context, bucket, location s } } + if lockEnabled { + meta := newBucketMetadata(bucket) + meta.LockEnabled = lockEnabled + if err := meta.save(ctx, z); err != nil { + return toObjectErr(err, bucket) + } + } + // Success. return nil @@ -405,11 +424,11 @@ func (z *xlZones) GetObjectNInfo(ctx context.Context, bucket, object string, rs func (z *xlZones) GetObject(ctx context.Context, bucket, object string, startOffset int64, length int64, writer io.Writer, etag string, opts ObjectOptions) error { // Lock the object before reading. - objectLock := z.NewNSLock(ctx, bucket, object) - if err := objectLock.GetRLock(globalObjectTimeout); err != nil { + lk := z.NewNSLock(ctx, bucket, object) + if err := lk.GetRLock(globalObjectTimeout); err != nil { return err } - defer objectLock.RUnlock() + defer lk.RUnlock() if z.SingleZone() { return z.zones[0].GetObject(ctx, bucket, object, startOffset, length, writer, etag, opts) @@ -428,11 +447,11 @@ func (z *xlZones) GetObject(ctx context.Context, bucket, object string, startOff func (z *xlZones) GetObjectInfo(ctx context.Context, bucket, object string, opts ObjectOptions) (ObjectInfo, error) { // Lock the object before reading. - objectLock := z.NewNSLock(ctx, bucket, object) - if err := objectLock.GetRLock(globalObjectTimeout); err != nil { + lk := z.NewNSLock(ctx, bucket, object) + if err := lk.GetRLock(globalObjectTimeout); err != nil { return ObjectInfo{}, err } - defer objectLock.RUnlock() + defer lk.RUnlock() if z.SingleZone() { return z.zones[0].GetObjectInfo(ctx, bucket, object, opts) @@ -453,11 +472,11 @@ func (z *xlZones) GetObjectInfo(ctx context.Context, bucket, object string, opts // PutObject - writes an object to least used erasure zone. func (z *xlZones) PutObject(ctx context.Context, bucket string, object string, data *PutObjReader, opts ObjectOptions) (ObjectInfo, error) { // Lock the object. - objectLock := z.NewNSLock(ctx, bucket, object) - if err := objectLock.GetLock(globalObjectTimeout); err != nil { + lk := z.NewNSLock(ctx, bucket, object) + if err := lk.GetLock(globalObjectTimeout); err != nil { return ObjectInfo{}, err } - defer objectLock.Unlock() + defer lk.Unlock() if z.SingleZone() { return z.zones[0].PutObject(ctx, bucket, object, data, opts) @@ -480,11 +499,11 @@ func (z *xlZones) PutObject(ctx context.Context, bucket string, object string, d func (z *xlZones) DeleteObject(ctx context.Context, bucket string, object string) error { // Acquire a write lock before deleting the object. - objectLock := z.NewNSLock(ctx, bucket, object) - if err := objectLock.GetLock(globalOperationTimeout); err != nil { + lk := z.NewNSLock(ctx, bucket, object) + if err := lk.GetLock(globalOperationTimeout); err != nil { return err } - defer objectLock.Unlock() + defer lk.Unlock() if z.SingleZone() { return z.zones[0].DeleteObject(ctx, bucket, object) @@ -531,11 +550,11 @@ func (z *xlZones) CopyObject(ctx context.Context, srcBucket, srcObject, destBuck // Check if this request is only metadata update. cpSrcDstSame := isStringEqual(pathJoin(srcBucket, srcObject), pathJoin(destBucket, destObject)) if !cpSrcDstSame { - objectLock := z.NewNSLock(ctx, destBucket, destObject) - if err := objectLock.GetLock(globalObjectTimeout); err != nil { + lk := z.NewNSLock(ctx, destBucket, destObject) + if err := lk.GetLock(globalObjectTimeout); err != nil { return objInfo, err } - defer objectLock.Unlock() + defer lk.Unlock() } if z.SingleZone() { @@ -1110,11 +1129,11 @@ func (z *xlZones) CompleteMultipartUpload(ctx context.Context, bucket, object, u // Hold namespace to complete the transaction, only hold // if uploadID can be held exclusively. - objectLock := z.NewNSLock(ctx, bucket, object) - if err = objectLock.GetLock(globalOperationTimeout); err != nil { + lk := z.NewNSLock(ctx, bucket, object) + if err = lk.GetLock(globalOperationTimeout); err != nil { return objInfo, err } - defer objectLock.Unlock() + defer lk.Unlock() if z.SingleZone() { return z.zones[0].CompleteMultipartUpload(ctx, bucket, object, uploadID, uploadedParts, opts) @@ -1201,6 +1220,31 @@ func (z *xlZones) SetBucketSSEConfig(ctx context.Context, bucket string, config return saveBucketSSEConfig(ctx, z, bucket, config) } +// SetBucketObjectLockConfig enables/clears default object lock configuration +func (z *xlZones) SetBucketObjectLockConfig(ctx context.Context, bucket string, config *objectlock.Config) error { + return saveBucketObjectLockConfig(ctx, z, bucket, config) +} + +// GetBucketObjectLockConfig - returns current defaults for object lock configuration +func (z *xlZones) GetBucketObjectLockConfig(ctx context.Context, bucket string) (*objectlock.Config, error) { + return readBucketObjectLockConfig(ctx, z, bucket) +} + +// SetBucketTagging sets bucket tags on given bucket +func (z *xlZones) SetBucketTagging(ctx context.Context, bucket string, t *tags.Tags) error { + return saveBucketTagging(ctx, z, bucket, t) +} + +// GetBucketTagging get bucket tags set on given bucket +func (z *xlZones) GetBucketTagging(ctx context.Context, bucket string) (*tags.Tags, error) { + return readBucketTagging(ctx, z, bucket) +} + +// DeleteBucketTagging delete bucket tags set if any. +func (z *xlZones) DeleteBucketTagging(ctx context.Context, bucket string) error { + return deleteBucketTagging(ctx, z, bucket) +} + // DeleteBucketSSEConfig deletes bucket encryption config on given bucket func (z *xlZones) DeleteBucketSSEConfig(ctx context.Context, bucket string) error { return removeBucketSSEConfig(ctx, z, bucket) @@ -1245,27 +1289,14 @@ func (z *xlZones) DeleteBucket(ctx context.Context, bucket string, forceDelete b errs := g.Wait() - if forceDelete { - for _, err := range errs { - if err != nil { - if _, ok := err.(InsufficientWriteQuorum); ok { - undoDeleteBucketZones(bucket, z.zones, errs) - } - - return err - } - } - - return nil - } - - // For any write quorum failure, we undo all the delete buckets operation - // by creating all the buckets again. + // For any write quorum failure, we undo all the delete + // buckets operation by creating all the buckets again. for _, err := range errs { if err != nil { if _, ok := err.(InsufficientWriteQuorum); ok { undoDeleteBucketZones(bucket, z.zones, errs) } + return err } } @@ -1283,7 +1314,7 @@ func undoDeleteBucketZones(bucket string, zones []*xlSets, errs []error) { index := index g.Go(func() error { if errs[index] == nil { - return zones[index].MakeBucketWithLocation(GlobalContext, bucket, "") + return zones[index].MakeBucketWithLocation(GlobalContext, bucket, "", false) } return nil }, index) @@ -1297,17 +1328,30 @@ func undoDeleteBucketZones(bucket string, zones []*xlSets, errs []error) { // that all buckets are present on all zones. func (z *xlZones) ListBuckets(ctx context.Context) (buckets []BucketInfo, err error) { if z.SingleZone() { - return z.zones[0].ListBuckets(ctx) - } - for _, zone := range z.zones { - buckets, err := zone.ListBuckets(ctx) - if err != nil { - logger.LogIf(ctx, err) - continue + buckets, err = z.zones[0].ListBuckets(ctx) + } else { + for _, zone := range z.zones { + buckets, err = zone.ListBuckets(ctx) + if err != nil { + logger.LogIf(ctx, err) + continue + } + break } - return buckets, nil } - return buckets, InsufficientReadQuorum{} + if err != nil { + return nil, err + } + for i := range buckets { + meta, err := loadBucketMetadata(ctx, z, buckets[i].Name) + if err == nil { + buckets[i].Created = meta.Created + } + if err != errMetaDataConverted { + logger.LogIf(ctx, err) + } + } + return buckets, nil } func (z *xlZones) ReloadFormat(ctx context.Context, dryRun bool) error { @@ -1488,11 +1532,11 @@ func (z *xlZones) HealObjects(ctx context.Context, bucket, prefix string, opts m func (z *xlZones) HealObject(ctx context.Context, bucket, object string, opts madmin.HealOpts) (madmin.HealResultItem, error) { // Lock the object before healing. Use read lock since healing // will only regenerate parts & xl.json of outdated disks. - objectLock := z.NewNSLock(ctx, bucket, object) - if err := objectLock.GetRLock(globalHealingTimeout); err != nil { + lk := z.NewNSLock(ctx, bucket, object) + if err := lk.GetRLock(globalHealingTimeout); err != nil { return madmin.HealResultItem{}, err } - defer objectLock.RUnlock() + defer lk.RUnlock() if z.SingleZone() { return z.zones[0].HealObject(ctx, bucket, object, opts) diff --git a/mint/run/core/awscli/test.sh b/mint/run/core/awscli/test.sh index d620dcf73..23405a32d 100755 --- a/mint/run/core/awscli/test.sh +++ b/mint/run/core/awscli/test.sh @@ -1,6 +1,6 @@ #!/bin/bash # -# Mint (C) 2017, 2018 Minio, Inc. +# Mint (C) 2017-2020 Minio, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -69,24 +69,6 @@ function make_bucket() { return $rv } -function make_bucket_with_lock() { - # Make bucket - bucket_name="awscli-mint-test-bucket-$RANDOM" - function="${AWS} s3api create-bucket --bucket ${bucket_name} --object-lock-enabled-for-bucket" - - # execute the test - out=$($function 2>&1) - rv=$? - - # if command is successful print bucket_name or print error - if [ $rv -eq 0 ]; then - echo "${bucket_name}" - else - echo "${out}" - fi - - return $rv -} function delete_bucket() { # Delete bucket function="${AWS} s3 rb s3://${1} --force" @@ -688,7 +670,7 @@ function test_copy_object_storage_class() { out=$($function 2>&1) rv=$? # if this functionality is not implemented return right away. - if [ $rv -eq 255 ]; then + if [ $rv -ne 0 ]; then if echo "$out" | greq -q "NotImplemented"; then ${AWS} s3 rb s3://"${bucket_name}" --force > /dev/null 2>&1 return 0 @@ -758,7 +740,7 @@ function test_copy_object_storage_class_same() { out=$($function 2>&1) rv=$? # if this functionality is not implemented return right away. - if [ $rv -eq 255 ]; then + if [ $rv -ne 0 ]; then if echo "$out" | greq -q "NotImplemented"; then ${AWS} s3 rb s3://"${bucket_name}" --force > /dev/null 2>&1 return 0 @@ -1591,9 +1573,22 @@ function test_legal_hold() { # log start time start_time=$(get_time) - function="make_bucket_with_lock" - bucket_name=$(make_bucket_with_lock) + # Make bucket + bucket_name="awscli-mint-test-bucket-$RANDOM" + function="${AWS} s3api create-bucket --bucket ${bucket_name} --object-lock-enabled-for-bucket" + + # execute the test + out=$($function 2>&1) rv=$? + + if [ $rv -ne 0 ]; then + # if this functionality is not implemented return right away. + if echo "$out" | greq -q "NotImplemented"; then + ${AWS} s3 rb s3://"${bucket_name}" --force > /dev/null 2>&1 + return 0 + fi + fi + # if make bucket succeeds upload a file if [ $rv -eq 0 ]; then function="${AWS} s3api put-object --body ${MINT_DATA_DIR}/datafile-1-kB --bucket ${bucket_name} --key datafile-1-kB --object-lock-legal-hold-status ON" diff --git a/pkg/bucket/object/lock/lock.go b/pkg/bucket/object/lock/lock.go index fafd4acf3..cfdf7ee19 100644 --- a/pkg/bucket/object/lock/lock.go +++ b/pkg/bucket/object/lock/lock.go @@ -24,7 +24,6 @@ import ( "io" "net/http" "strings" - "sync" "time" "github.com/beevik/ntp" @@ -158,41 +157,6 @@ func (r Retention) Retain(created time.Time) bool { return created.Add(r.Validity).After(t) } -// BucketObjectLockConfig - map of bucket and retention configuration. -type BucketObjectLockConfig struct { - sync.RWMutex - retentionMap map[string]*Retention -} - -// Set - set retention configuration. -func (config *BucketObjectLockConfig) Set(bucketName string, retention *Retention) { - config.Lock() - config.retentionMap[bucketName] = retention - config.Unlock() -} - -// Get - Get retention configuration. -func (config *BucketObjectLockConfig) Get(bucketName string) (r *Retention, ok bool) { - config.RLock() - defer config.RUnlock() - r, ok = config.retentionMap[bucketName] - return r, ok -} - -// Remove - removes retention configuration. -func (config *BucketObjectLockConfig) Remove(bucketName string) { - config.Lock() - delete(config.retentionMap, bucketName) - config.Unlock() -} - -// NewBucketObjectLockConfig returns initialized BucketObjectLockConfig -func NewBucketObjectLockConfig() *BucketObjectLockConfig { - return &BucketObjectLockConfig{ - retentionMap: make(map[string]*Retention), - } -} - // DefaultRetention - default retention configuration. type DefaultRetention struct { XMLName xml.Name `xml:"DefaultRetention"`