diff --git a/cmd/data-scanner.go b/cmd/data-scanner.go index cfc07dfb3..35ef2f7b0 100644 --- a/cmd/data-scanner.go +++ b/cmd/data-scanner.go @@ -1187,6 +1187,9 @@ func applyExpiryOnNonTransitionedObjects(ctx context.Context, objLayer ObjectLay opts.Versioned = globalBucketVersioningSys.PrefixEnabled(obj.Bucket, obj.Name) opts.VersionSuspended = globalBucketVersioningSys.PrefixSuspended(obj.Bucket, obj.Name) } + if lcEvent.Action.DeleteAll() { + opts.DeletePrefix = true + } obj, err := objLayer.DeleteObject(ctx, obj.Bucket, obj.Name, opts) if err != nil { diff --git a/cmd/erasure-server-pool.go b/cmd/erasure-server-pool.go index cb6688e97..276811c4f 100644 --- a/cmd/erasure-server-pool.go +++ b/cmd/erasure-server-pool.go @@ -993,13 +993,10 @@ func (z *erasureServerPools) DeleteObject(ctx context.Context, bucket string, ob return objInfo, err } - if opts.DeletePrefix { - err := z.deletePrefix(ctx, bucket, object) - return ObjectInfo{}, err + if !opts.DeletePrefix { // DeletePrefix handles dir object encoding differently. + object = encodeDirObject(object) } - object = encodeDirObject(object) - // Acquire a write lock before deleting the object. lk := z.NewNSLock(bucket, object) lkctx, err := lk.GetLock(ctx, globalDeleteOperationTimeout) @@ -1009,6 +1006,10 @@ func (z *erasureServerPools) DeleteObject(ctx context.Context, bucket string, ob ctx = lkctx.Context() defer lk.Unlock(lkctx) + if opts.DeletePrefix { + return ObjectInfo{}, z.deletePrefix(ctx, bucket, object) + } + gopts := opts gopts.NoLock = true pinfo, err := z.getPoolInfoExistingWithOpts(ctx, bucket, object, gopts) diff --git a/docs/bucket/lifecycle/README.md b/docs/bucket/lifecycle/README.md index 457fd02eb..34c84d3a9 100644 --- a/docs/bucket/lifecycle/README.md +++ b/docs/bucket/lifecycle/README.md @@ -129,9 +129,31 @@ of objects under the prefix `user-uploads/` as soon as there are more than `N` n ] } ``` - Note: This rule has an implicit zero NoncurrentDays, which makes the expiry of those 'extra' noncurrent versions immediate. +#### 3.2.b Automatic removal of all versions (MinIO only extension) + +This is available only on MinIO as an extension to the Expiration feature. The following rule makes it possible to remove all versions of an object under +the prefix `user-uploads/` as soon as the latest object satisfies the expiration criteria. + +``` +{ + "Rules": [ + { + "ID": "Purge all versions of an expired object", + "Status": "Enabled", + "Filter": { + "Prefix": "users-uploads/" + }, + "Expiration": { + "Days": 7, + "ExpiredObjectAllVersions": true + } + } + ] +} +``` + ### 3.3 Automatic removal of delete markers with no other versions When an object has only one version as a delete marker, the latter can be automatically removed after a certain number of days using the following configuration: diff --git a/docs/debugging/reorder-disks/main.go b/docs/debugging/reorder-disks/main.go index 6c6f47ad9..e323c8373 100644 --- a/docs/debugging/reorder-disks/main.go +++ b/docs/debugging/reorder-disks/main.go @@ -162,7 +162,6 @@ func getDiskLocation(f format) (string, error) { } func main() { - var node, args string flag.StringVar(&node, "local-node-name", "", "the name of the local node") diff --git a/internal/bucket/lifecycle/action_string.go b/internal/bucket/lifecycle/action_string.go index 89c3f8f73..0a7a55eed 100644 --- a/internal/bucket/lifecycle/action_string.go +++ b/internal/bucket/lifecycle/action_string.go @@ -15,12 +15,13 @@ func _() { _ = x[TransitionVersionAction-4] _ = x[DeleteRestoredAction-5] _ = x[DeleteRestoredVersionAction-6] - _ = x[ActionCount-7] + _ = x[DeleteAllVersionsAction-7] + _ = x[ActionCount-8] } -const _Action_name = "NoneActionDeleteActionDeleteVersionActionTransitionActionTransitionVersionActionDeleteRestoredActionDeleteRestoredVersionActionActionCount" +const _Action_name = "NoneActionDeleteActionDeleteVersionActionTransitionActionTransitionVersionActionDeleteRestoredActionDeleteRestoredVersionActionDeleteAllVersionsActionActionCount" -var _Action_index = [...]uint8{0, 10, 22, 41, 57, 80, 100, 127, 138} +var _Action_index = [...]uint8{0, 10, 22, 41, 57, 80, 100, 127, 150, 161} func (i Action) String() string { if i < 0 || i >= Action(len(_Action_index)-1) { diff --git a/internal/bucket/lifecycle/expiration.go b/internal/bucket/lifecycle/expiration.go index 9670d250e..41dbbd613 100644 --- a/internal/bucket/lifecycle/expiration.go +++ b/internal/bucket/lifecycle/expiration.go @@ -100,6 +100,11 @@ func (eDate ExpirationDate) MarshalXML(e *xml.Encoder, startElement xml.StartEle // ExpireDeleteMarker represents value of ExpiredObjectDeleteMarker field in Expiration XML element. type ExpireDeleteMarker struct { + Boolean +} + +// Boolean signifies a boolean XML struct with custom marshaling +type Boolean struct { val bool set bool } @@ -110,12 +115,16 @@ type Expiration struct { Days ExpirationDays `xml:"Days,omitempty"` Date ExpirationDate `xml:"Date,omitempty"` DeleteMarker ExpireDeleteMarker `xml:"ExpiredObjectDeleteMarker"` + // Indicates whether MinIO will remove all versions. If set to true, all versions will be deleted; + // if set to false the policy takes no action. This action uses the Days/Date to expire objects. + // This check is verified for latest version of the object. + DeleteAll Boolean `xml:"ExpiredObjectAllVersions"` set bool } // MarshalXML encodes delete marker boolean into an XML form. -func (b ExpireDeleteMarker) MarshalXML(e *xml.Encoder, startElement xml.StartElement) error { +func (b Boolean) MarshalXML(e *xml.Encoder, startElement xml.StartElement) error { if !b.set { return nil } @@ -123,7 +132,7 @@ func (b ExpireDeleteMarker) MarshalXML(e *xml.Encoder, startElement xml.StartEle } // UnmarshalXML decodes delete marker boolean from the XML form. -func (b *ExpireDeleteMarker) UnmarshalXML(d *xml.Decoder, startElement xml.StartElement) error { +func (b *Boolean) UnmarshalXML(d *xml.Decoder, startElement xml.StartElement) error { var exp bool err := d.DecodeElement(&exp, &startElement) if err != nil { diff --git a/internal/bucket/lifecycle/lifecycle.go b/internal/bucket/lifecycle/lifecycle.go index d3891ae66..ee6b90010 100644 --- a/internal/bucket/lifecycle/lifecycle.go +++ b/internal/bucket/lifecycle/lifecycle.go @@ -65,6 +65,8 @@ const ( DeleteRestoredAction // DeleteRestoredVersionAction deletes a particular version that was temporarily restored DeleteRestoredVersionAction + // DeleteAllVersionsAction deletes all versions when an object expires + DeleteAllVersionsAction // ActionCount must be the last action and shouldn't be used as a regular action. ActionCount @@ -80,6 +82,11 @@ func (a Action) DeleteVersioned() bool { return a == DeleteVersionAction || a == DeleteRestoredVersionAction } +// DeleteAll - Returns true if the action demands deleting all versions of an object +func (a Action) DeleteAll() bool { + return a == DeleteAllVersionsAction +} + // Delete - Returns true if action demands delete on all objects (including restored) func (a Action) Delete() bool { if a.DeleteRestored() { @@ -324,6 +331,23 @@ func (lc Lifecycle) eval(obj ObjectOpts, now time.Time) Event { } for _, rule := range lc.FilterRules(obj) { + if obj.IsLatest && rule.Expiration.DeleteAll.val { + if !rule.Expiration.IsDaysNull() { + // Specifying the Days tag will automatically perform all versions cleanup + // once the latest object is old enough to satisfy the age criteria. + // This is a MinIO only extension. + if expectedExpiry := ExpectedExpiryTime(obj.ModTime, int(rule.Expiration.Days)); now.IsZero() || now.After(expectedExpiry) { + events = append(events, Event{ + Action: DeleteAllVersionsAction, + RuleID: rule.ID, + Due: expectedExpiry, + }) + // No other conflicting actions apply to an all version expired object. + break + } + } + } + if obj.ExpiredObjectDeleteMarker() { if rule.Expiration.DeleteMarker.val { // Indicates whether MinIO will remove a delete marker with no noncurrent versions. diff --git a/internal/bucket/lifecycle/lifecycle_test.go b/internal/bucket/lifecycle/lifecycle_test.go index 23d15beaa..e61905e44 100644 --- a/internal/bucket/lifecycle/lifecycle_test.go +++ b/internal/bucket/lifecycle/lifecycle_test.go @@ -388,6 +388,15 @@ func TestEval(t *testing.T) { isExpiredDelMarker: true, expectedAction: DeleteVersionAction, }, + // Should delete expired object right away with 1 day expiration + { + inputConfig: `1trueEnabled`, + objectName: "foodir/fooobject", + objectModTime: time.Now().UTC().Add(-10 * 24 * time.Hour), // Created 10 days ago + isExpiredDelMarker: true, + expectedAction: DeleteAllVersionsAction, + }, + // Should not delete expired marker if its time has not come yet { inputConfig: `Enabled1`,