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`,