diff --git a/internal/bucket/lifecycle/lifecycle.go b/internal/bucket/lifecycle/lifecycle.go index 04204f345..ef6d7b364 100644 --- a/internal/bucket/lifecycle/lifecycle.go +++ b/internal/bucket/lifecycle/lifecycle.go @@ -358,7 +358,7 @@ func (lc Lifecycle) Eval(obj ObjectOpts, now time.Time) Event { // Specifying the Days tag will automatically perform ExpiredObjectDeleteMarker cleanup // once delete markers are old enough to satisfy the age criteria. // https://docs.aws.amazon.com/AmazonS3/latest/userguide/lifecycle-configuration-examples.html - if expectedExpiry := ExpectedExpiryTime(obj.ModTime, int(rule.Expiration.Days)); now.After(expectedExpiry) { + if expectedExpiry := ExpectedExpiryTime(obj.ModTime, int(rule.Expiration.Days)); now.IsZero() || now.After(expectedExpiry) { events = append(events, Event{ Action: DeleteVersionAction, RuleID: rule.ID, @@ -380,7 +380,7 @@ func (lc Lifecycle) Eval(obj ObjectOpts, now time.Time) Event { if !obj.IsLatest && !rule.NoncurrentVersionExpiration.IsDaysNull() { // Non current versions should be deleted if their age exceeds non current days configuration // https://docs.aws.amazon.com/AmazonS3/latest/dev/intro-lifecycle-rules.html#intro-lifecycle-rules-actions - if expectedExpiry := ExpectedExpiryTime(obj.SuccessorModTime, int(rule.NoncurrentVersionExpiration.NoncurrentDays)); now.After(expectedExpiry) { + if expectedExpiry := ExpectedExpiryTime(obj.SuccessorModTime, int(rule.NoncurrentVersionExpiration.NoncurrentDays)); now.IsZero() || now.After(expectedExpiry) { events = append(events, Event{ Action: DeleteVersionAction, RuleID: rule.ID, @@ -393,7 +393,7 @@ func (lc Lifecycle) Eval(obj ObjectOpts, now time.Time) Event { if !obj.DeleteMarker && obj.TransitionStatus != TransitionComplete { // Non current versions should be transitioned if their age exceeds non current days configuration // https://docs.aws.amazon.com/AmazonS3/latest/dev/intro-lifecycle-rules.html#intro-lifecycle-rules-actions - if due, ok := rule.NoncurrentVersionTransition.NextDue(obj); ok && now.After(due) { + if due, ok := rule.NoncurrentVersionTransition.NextDue(obj); ok && (now.IsZero() || now.After(due)) { events = append(events, Event{ Action: TransitionVersionAction, RuleID: rule.ID, @@ -408,7 +408,7 @@ func (lc Lifecycle) Eval(obj ObjectOpts, now time.Time) Event { if obj.IsLatest && !obj.DeleteMarker { switch { case !rule.Expiration.IsDateNull(): - if time.Now().UTC().After(rule.Expiration.Date.Time) { + if now.IsZero() || now.After(rule.Expiration.Date.Time) { events = append(events, Event{ Action: DeleteAction, RuleID: rule.ID, @@ -416,7 +416,7 @@ func (lc Lifecycle) Eval(obj ObjectOpts, now time.Time) Event { }) } case !rule.Expiration.IsDaysNull(): - if expectedExpiry := ExpectedExpiryTime(obj.ModTime, int(rule.Expiration.Days)); now.After(expectedExpiry) { + if expectedExpiry := ExpectedExpiryTime(obj.ModTime, int(rule.Expiration.Days)); now.IsZero() || now.After(expectedExpiry) { events = append(events, Event{ Action: DeleteAction, RuleID: rule.ID, @@ -426,7 +426,7 @@ func (lc Lifecycle) Eval(obj ObjectOpts, now time.Time) Event { } if obj.TransitionStatus != TransitionComplete { - if due, ok := rule.Transition.NextDue(obj); ok && now.After(due) { + if due, ok := rule.Transition.NextDue(obj); ok && (now.IsZero() || now.After(due)) { events = append(events, Event{ Action: TransitionAction, RuleID: rule.ID, diff --git a/internal/bucket/lifecycle/lifecycle_test.go b/internal/bucket/lifecycle/lifecycle_test.go index 7bf7c35db..67ef558a5 100644 --- a/internal/bucket/lifecycle/lifecycle_test.go +++ b/internal/bucket/lifecycle/lifecycle_test.go @@ -932,3 +932,69 @@ func TestParseLifecycleConfigWithID(t *testing.T) { } } } + +func TestFilterAndSetPredictionHeaders(t *testing.T) { + lc := Lifecycle{ + Rules: []Rule{ + { + ID: "rule-1", + Status: "Enabled", + Filter: Filter{ + set: true, + Prefix: Prefix{ + string: "folder1/folder1/exp_dt=2022-", + set: true, + }, + }, + Expiration: Expiration{ + Days: 1, + set: true, + }, + }, + }, + } + tests := []struct { + opts ObjectOpts + lc Lifecycle + want int + }{ + { + opts: ObjectOpts{ + Name: "folder1/folder1/exp_dt=2022-08-01/obj-1", + ModTime: time.Now().UTC().Add(-10 * 24 * time.Hour), + VersionID: "", + IsLatest: true, + NumVersions: 1, + }, + want: 1, + lc: lc, + }, + { + opts: ObjectOpts{ + Name: "folder1/folder1/exp_dt=9999-01-01/obj-1", + ModTime: time.Now().UTC().Add(-10 * 24 * time.Hour), + VersionID: "", + IsLatest: true, + NumVersions: 1, + }, + want: 0, + lc: lc, + }, + } + for i, tc := range tests { + t.Run(fmt.Sprintf("test-%d", i+1), func(t *testing.T) { + if got := tc.lc.FilterRules(tc.opts); len(got) != tc.want { + t.Fatalf("Expected %d rules to match but got %d", tc.want, len(got)) + } + w := httptest.NewRecorder() + tc.lc.SetPredictionHeaders(w, tc.opts) + expHdr, ok := w.Header()[xhttp.AmzExpiration] + switch { + case ok && tc.want == 0: + t.Fatalf("Expected no rule to match but found x-amz-expiration header set: %v", expHdr) + case !ok && tc.want > 0: + t.Fatal("Expected x-amz-expiration header to be set but not found") + } + }) + } +}