Add updatedAt for GetBucketLifecycleConfig (#17271)

This commit is contained in:
Krishnan Parthasarathi 2023-05-24 22:52:39 -07:00 committed by GitHub
parent d0a0eb9738
commit 62df731006
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 219 additions and 166 deletions

View File

@ -460,7 +460,7 @@ func (a adminAPIHandlers) ExportBucketMetadataHandler(w http.ResponseWriter, r *
return return
} }
case bucketLifecycleConfig: case bucketLifecycleConfig:
config, err := globalBucketMetadataSys.GetLifecycleConfig(bucket) config, _, err := globalBucketMetadataSys.GetLifecycleConfig(bucket)
if err != nil { if err != nil {
if errors.Is(err, BucketLifecycleNotFound{Bucket: bucket}) { if errors.Is(err, BucketLifecycleNotFound{Bucket: bucket}) {
continue continue

View File

@ -253,6 +253,7 @@ const (
ErrInvalidObjectName ErrInvalidObjectName
ErrInvalidObjectNamePrefixSlash ErrInvalidObjectNamePrefixSlash
ErrInvalidResourceName ErrInvalidResourceName
ErrInvalidLifecycleQueryParameter
ErrServerNotInitialized ErrServerNotInitialized
ErrOperationTimedOut ErrOperationTimedOut
ErrClientDisconnected ErrClientDisconnected
@ -1255,6 +1256,11 @@ var errorCodes = errorCodeMap{
Description: "The JSON you provided was not well-formed or did not validate against our published format.", Description: "The JSON you provided was not well-formed or did not validate against our published format.",
HTTPStatusCode: http.StatusBadRequest, HTTPStatusCode: http.StatusBadRequest,
}, },
ErrInvalidLifecycleQueryParameter: {
Code: "XMinioInvalidLifecycleParameter",
Description: "The boolean value provided for withUpdatedAt query parameter was invalid.",
HTTPStatusCode: http.StatusBadRequest,
},
ErrAdminNoSuchUser: { ErrAdminNoSuchUser: {
Code: "XMinioAdminNoSuchUser", Code: "XMinioAdminNoSuchUser",
Description: "The specified user does not exist.", Description: "The specified user does not exist.",

File diff suppressed because one or more lines are too long

View File

@ -21,6 +21,7 @@ import (
"encoding/xml" "encoding/xml"
"io" "io"
"net/http" "net/http"
"strconv"
"github.com/minio/minio/internal/bucket/lifecycle" "github.com/minio/minio/internal/bucket/lifecycle"
xhttp "github.com/minio/minio/internal/http" xhttp "github.com/minio/minio/internal/http"
@ -115,6 +116,16 @@ func (api objectAPIHandlers) GetBucketLifecycleHandler(w http.ResponseWriter, r
vars := mux.Vars(r) vars := mux.Vars(r)
bucket := vars["bucket"] bucket := vars["bucket"]
var withUpdatedAt bool
if updatedAtStr := r.Form.Get("withUpdatedAt"); updatedAtStr != "" {
var err error
withUpdatedAt, err = strconv.ParseBool(updatedAtStr)
if err != nil {
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidLifecycleQueryParameter), r.URL)
return
}
}
if s3Error := checkRequestAuthType(ctx, r, policy.GetBucketLifecycleAction, bucket, ""); s3Error != ErrNone { if s3Error := checkRequestAuthType(ctx, r, policy.GetBucketLifecycleAction, bucket, ""); s3Error != ErrNone {
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL) writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL)
return return
@ -126,7 +137,7 @@ func (api objectAPIHandlers) GetBucketLifecycleHandler(w http.ResponseWriter, r
return return
} }
config, err := globalBucketMetadataSys.GetLifecycleConfig(bucket) config, updatedAt, err := globalBucketMetadataSys.GetLifecycleConfig(bucket)
if err != nil { if err != nil {
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL) writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
return return
@ -138,6 +149,9 @@ func (api objectAPIHandlers) GetBucketLifecycleHandler(w http.ResponseWriter, r
return return
} }
if withUpdatedAt {
w.Header().Set(xhttp.MinIOLifecycleCfgUpdatedAt, updatedAt.Format(iso8601Format))
}
// Write lifecycle configuration to client. // Write lifecycle configuration to client.
writeSuccessResponseXML(w, configData) writeSuccessResponseXML(w, configData)
} }

View File

@ -63,7 +63,8 @@ type LifecycleSys struct{}
// Get - gets lifecycle config associated to a given bucket name. // Get - gets lifecycle config associated to a given bucket name.
func (sys *LifecycleSys) Get(bucketName string) (lc *lifecycle.Lifecycle, err error) { func (sys *LifecycleSys) Get(bucketName string) (lc *lifecycle.Lifecycle, err error) {
return globalBucketMetadataSys.GetLifecycleConfig(bucketName) lc, _, err = globalBucketMetadataSys.GetLifecycleConfig(bucketName)
return lc, err
} }
// NewLifecycleSys - creates new lifecycle system. // NewLifecycleSys - creates new lifecycle system.

View File

@ -121,6 +121,7 @@ func (sys *BucketMetadataSys) updateAndParse(ctx context.Context, bucket string,
meta.NotificationConfigXML = configData meta.NotificationConfigXML = configData
case bucketLifecycleConfig: case bucketLifecycleConfig:
meta.LifecycleConfigXML = configData meta.LifecycleConfigXML = configData
meta.LifecycleConfigUpdatedAt = updatedAt
case bucketSSEConfig: case bucketSSEConfig:
meta.EncryptionConfigXML = configData meta.EncryptionConfigXML = configData
meta.EncryptionConfigUpdatedAt = updatedAt meta.EncryptionConfigUpdatedAt = updatedAt
@ -246,18 +247,18 @@ func (sys *BucketMetadataSys) GetObjectLockConfig(bucket string) (*objectlock.Co
// GetLifecycleConfig returns configured lifecycle config // GetLifecycleConfig returns configured lifecycle config
// The returned object may not be modified. // The returned object may not be modified.
func (sys *BucketMetadataSys) GetLifecycleConfig(bucket string) (*lifecycle.Lifecycle, error) { func (sys *BucketMetadataSys) GetLifecycleConfig(bucket string) (*lifecycle.Lifecycle, time.Time, error) {
meta, _, err := sys.GetConfig(GlobalContext, bucket) meta, _, err := sys.GetConfig(GlobalContext, bucket)
if err != nil { if err != nil {
if errors.Is(err, errConfigNotFound) { if errors.Is(err, errConfigNotFound) {
return nil, BucketLifecycleNotFound{Bucket: bucket} return nil, time.Time{}, BucketLifecycleNotFound{Bucket: bucket}
} }
return nil, err return nil, time.Time{}, err
} }
if meta.lifecycleConfig == nil { if meta.lifecycleConfig == nil {
return nil, BucketLifecycleNotFound{Bucket: bucket} return nil, time.Time{}, BucketLifecycleNotFound{Bucket: bucket}
} }
return meta.lifecycleConfig, nil return meta.lifecycleConfig, meta.LifecycleConfigUpdatedAt, nil
} }
// GetNotificationConfig returns configured notification config // GetNotificationConfig returns configured notification config

View File

@ -88,6 +88,7 @@ type BucketMetadata struct {
QuotaConfigUpdatedAt time.Time QuotaConfigUpdatedAt time.Time
ReplicationConfigUpdatedAt time.Time ReplicationConfigUpdatedAt time.Time
VersioningConfigUpdatedAt time.Time VersioningConfigUpdatedAt time.Time
LifecycleConfigUpdatedAt time.Time
// Unexported fields. Must be updated atomically. // Unexported fields. Must be updated atomically.
policyConfig *policy.Policy policyConfig *policy.Policy
@ -427,6 +428,10 @@ func (b *BucketMetadata) defaultTimestamps() {
if b.VersioningConfigUpdatedAt.IsZero() { if b.VersioningConfigUpdatedAt.IsZero() {
b.VersioningConfigUpdatedAt = b.Created b.VersioningConfigUpdatedAt = b.Created
} }
if b.LifecycleConfigUpdatedAt.IsZero() {
b.LifecycleConfigUpdatedAt = b.Created
}
} }
// Save config to supplied ObjectLayer api. // Save config to supplied ObjectLayer api.

View File

@ -150,6 +150,12 @@ func (z *BucketMetadata) DecodeMsg(dc *msgp.Reader) (err error) {
err = msgp.WrapError(err, "VersioningConfigUpdatedAt") err = msgp.WrapError(err, "VersioningConfigUpdatedAt")
return return
} }
case "LifecycleConfigUpdatedAt":
z.LifecycleConfigUpdatedAt, err = dc.ReadTime()
if err != nil {
err = msgp.WrapError(err, "LifecycleConfigUpdatedAt")
return
}
default: default:
err = dc.Skip() err = dc.Skip()
if err != nil { if err != nil {
@ -163,9 +169,9 @@ func (z *BucketMetadata) DecodeMsg(dc *msgp.Reader) (err error) {
// EncodeMsg implements msgp.Encodable // EncodeMsg implements msgp.Encodable
func (z *BucketMetadata) EncodeMsg(en *msgp.Writer) (err error) { func (z *BucketMetadata) EncodeMsg(en *msgp.Writer) (err error) {
// map header, size 21 // map header, size 22
// write "Name" // write "Name"
err = en.Append(0xde, 0x0, 0x15, 0xa4, 0x4e, 0x61, 0x6d, 0x65) err = en.Append(0xde, 0x0, 0x16, 0xa4, 0x4e, 0x61, 0x6d, 0x65)
if err != nil { if err != nil {
return return
} }
@ -374,15 +380,25 @@ func (z *BucketMetadata) EncodeMsg(en *msgp.Writer) (err error) {
err = msgp.WrapError(err, "VersioningConfigUpdatedAt") err = msgp.WrapError(err, "VersioningConfigUpdatedAt")
return return
} }
// write "LifecycleConfigUpdatedAt"
err = en.Append(0xb8, 0x4c, 0x69, 0x66, 0x65, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74)
if err != nil {
return
}
err = en.WriteTime(z.LifecycleConfigUpdatedAt)
if err != nil {
err = msgp.WrapError(err, "LifecycleConfigUpdatedAt")
return
}
return return
} }
// MarshalMsg implements msgp.Marshaler // MarshalMsg implements msgp.Marshaler
func (z *BucketMetadata) MarshalMsg(b []byte) (o []byte, err error) { func (z *BucketMetadata) MarshalMsg(b []byte) (o []byte, err error) {
o = msgp.Require(b, z.Msgsize()) o = msgp.Require(b, z.Msgsize())
// map header, size 21 // map header, size 22
// string "Name" // string "Name"
o = append(o, 0xde, 0x0, 0x15, 0xa4, 0x4e, 0x61, 0x6d, 0x65) o = append(o, 0xde, 0x0, 0x16, 0xa4, 0x4e, 0x61, 0x6d, 0x65)
o = msgp.AppendString(o, z.Name) o = msgp.AppendString(o, z.Name)
// string "Created" // string "Created"
o = append(o, 0xa7, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64) o = append(o, 0xa7, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64)
@ -444,6 +460,9 @@ func (z *BucketMetadata) MarshalMsg(b []byte) (o []byte, err error) {
// string "VersioningConfigUpdatedAt" // string "VersioningConfigUpdatedAt"
o = append(o, 0xb9, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74) o = append(o, 0xb9, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74)
o = msgp.AppendTime(o, z.VersioningConfigUpdatedAt) o = msgp.AppendTime(o, z.VersioningConfigUpdatedAt)
// string "LifecycleConfigUpdatedAt"
o = append(o, 0xb8, 0x4c, 0x69, 0x66, 0x65, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74)
o = msgp.AppendTime(o, z.LifecycleConfigUpdatedAt)
return return
} }
@ -591,6 +610,12 @@ func (z *BucketMetadata) UnmarshalMsg(bts []byte) (o []byte, err error) {
err = msgp.WrapError(err, "VersioningConfigUpdatedAt") err = msgp.WrapError(err, "VersioningConfigUpdatedAt")
return return
} }
case "LifecycleConfigUpdatedAt":
z.LifecycleConfigUpdatedAt, bts, err = msgp.ReadTimeBytes(bts)
if err != nil {
err = msgp.WrapError(err, "LifecycleConfigUpdatedAt")
return
}
default: default:
bts, err = msgp.Skip(bts) bts, err = msgp.Skip(bts)
if err != nil { if err != nil {
@ -605,6 +630,6 @@ func (z *BucketMetadata) UnmarshalMsg(bts []byte) (o []byte, err error) {
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message // Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
func (z *BucketMetadata) Msgsize() (s int) { func (z *BucketMetadata) Msgsize() (s int) {
s = 3 + 5 + msgp.StringPrefixSize + len(z.Name) + 8 + msgp.TimeSize + 12 + msgp.BoolSize + 17 + msgp.BytesPrefixSize + len(z.PolicyConfigJSON) + 22 + msgp.BytesPrefixSize + len(z.NotificationConfigXML) + 19 + msgp.BytesPrefixSize + len(z.LifecycleConfigXML) + 20 + msgp.BytesPrefixSize + len(z.ObjectLockConfigXML) + 20 + msgp.BytesPrefixSize + len(z.VersioningConfigXML) + 20 + msgp.BytesPrefixSize + len(z.EncryptionConfigXML) + 17 + msgp.BytesPrefixSize + len(z.TaggingConfigXML) + 16 + msgp.BytesPrefixSize + len(z.QuotaConfigJSON) + 21 + msgp.BytesPrefixSize + len(z.ReplicationConfigXML) + 24 + msgp.BytesPrefixSize + len(z.BucketTargetsConfigJSON) + 28 + msgp.BytesPrefixSize + len(z.BucketTargetsConfigMetaJSON) + 22 + msgp.TimeSize + 26 + msgp.TimeSize + 26 + msgp.TimeSize + 23 + msgp.TimeSize + 21 + msgp.TimeSize + 27 + msgp.TimeSize + 26 + msgp.TimeSize s = 3 + 5 + msgp.StringPrefixSize + len(z.Name) + 8 + msgp.TimeSize + 12 + msgp.BoolSize + 17 + msgp.BytesPrefixSize + len(z.PolicyConfigJSON) + 22 + msgp.BytesPrefixSize + len(z.NotificationConfigXML) + 19 + msgp.BytesPrefixSize + len(z.LifecycleConfigXML) + 20 + msgp.BytesPrefixSize + len(z.ObjectLockConfigXML) + 20 + msgp.BytesPrefixSize + len(z.VersioningConfigXML) + 20 + msgp.BytesPrefixSize + len(z.EncryptionConfigXML) + 17 + msgp.BytesPrefixSize + len(z.TaggingConfigXML) + 16 + msgp.BytesPrefixSize + len(z.QuotaConfigJSON) + 21 + msgp.BytesPrefixSize + len(z.ReplicationConfigXML) + 24 + msgp.BytesPrefixSize + len(z.BucketTargetsConfigJSON) + 28 + msgp.BytesPrefixSize + len(z.BucketTargetsConfigMetaJSON) + 22 + msgp.TimeSize + 26 + msgp.TimeSize + 26 + msgp.TimeSize + 23 + msgp.TimeSize + 21 + msgp.TimeSize + 27 + msgp.TimeSize + 26 + msgp.TimeSize + 25 + msgp.TimeSize
return return
} }

View File

@ -211,8 +211,8 @@ const (
// Header indiicates last rtention update time on source // Header indiicates last rtention update time on source
MinIOSourceObjectLegalHoldTimestamp = "X-Minio-Source-Replication-LegalHold-Timestamp" MinIOSourceObjectLegalHoldTimestamp = "X-Minio-Source-Replication-LegalHold-Timestamp"
// predicted date/time of transition // predicted date/time of transition
MinIOTransition = "X-Minio-Transition" MinIOTransition = "X-Minio-Transition"
MinIOLifecycleCfgUpdatedAt = "X-Minio-LifecycleConfig-UpdatedAt"
// MinIOCompressed is returned when object is compressed // MinIOCompressed is returned when object is compressed
MinIOCompressed = "X-Minio-Compressed" MinIOCompressed = "X-Minio-Compressed"