You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
127 lines
3.9 KiB
127 lines
3.9 KiB
package s3lifecycle
|
|
|
|
import "time"
|
|
|
|
// Evaluate checks the given lifecycle rules against an object and returns
|
|
// the highest-priority action that applies. The evaluation follows S3's
|
|
// action priority:
|
|
// 1. ExpiredObjectDeleteMarker (delete marker is sole version)
|
|
// 2. NoncurrentVersionExpiration (non-current version age/count)
|
|
// 3. Current version Expiration (Days or Date)
|
|
//
|
|
// AbortIncompleteMultipartUpload is evaluated separately since it applies
|
|
// to uploads, not objects. Use EvaluateMPUAbort for that.
|
|
func Evaluate(rules []Rule, obj ObjectInfo, now time.Time) EvalResult {
|
|
// Phase 1: ExpiredObjectDeleteMarker
|
|
if obj.IsDeleteMarker && obj.IsLatest && obj.NumVersions == 1 {
|
|
for _, rule := range rules {
|
|
if rule.Status != "Enabled" {
|
|
continue
|
|
}
|
|
if !MatchesFilter(rule, obj) {
|
|
continue
|
|
}
|
|
if rule.ExpiredObjectDeleteMarker {
|
|
return EvalResult{Action: ActionExpireDeleteMarker, RuleID: rule.ID}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Phase 2: NoncurrentVersionExpiration
|
|
if !obj.IsLatest && !obj.SuccessorModTime.IsZero() {
|
|
for _, rule := range rules {
|
|
if ShouldExpireNoncurrentVersion(rule, obj, obj.NoncurrentIndex, now) {
|
|
return EvalResult{Action: ActionDeleteVersion, RuleID: rule.ID}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Phase 3: Current version Expiration
|
|
if obj.IsLatest && !obj.IsDeleteMarker {
|
|
for _, rule := range rules {
|
|
if rule.Status != "Enabled" {
|
|
continue
|
|
}
|
|
if !MatchesFilter(rule, obj) {
|
|
continue
|
|
}
|
|
// Date-based expiration
|
|
if !rule.ExpirationDate.IsZero() && !now.Before(rule.ExpirationDate) {
|
|
return EvalResult{Action: ActionDeleteObject, RuleID: rule.ID}
|
|
}
|
|
// Days-based expiration
|
|
if rule.ExpirationDays > 0 {
|
|
expiryTime := expectedExpiryTime(obj.ModTime, rule.ExpirationDays)
|
|
if !now.Before(expiryTime) {
|
|
return EvalResult{Action: ActionDeleteObject, RuleID: rule.ID}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return EvalResult{Action: ActionNone}
|
|
}
|
|
|
|
// ShouldExpireNoncurrentVersion checks whether a non-current version should
|
|
// be expired considering both NoncurrentDays and NewerNoncurrentVersions.
|
|
// noncurrentIndex is the 0-based position among non-current versions sorted
|
|
// newest-first (0 = newest non-current version).
|
|
func ShouldExpireNoncurrentVersion(rule Rule, obj ObjectInfo, noncurrentIndex int, now time.Time) bool {
|
|
if rule.Status != "Enabled" {
|
|
return false
|
|
}
|
|
if rule.NoncurrentVersionExpirationDays <= 0 {
|
|
return false
|
|
}
|
|
if obj.IsLatest || obj.SuccessorModTime.IsZero() {
|
|
return false
|
|
}
|
|
if !MatchesFilter(rule, obj) {
|
|
return false
|
|
}
|
|
|
|
// Check age threshold.
|
|
expiryTime := expectedExpiryTime(obj.SuccessorModTime, rule.NoncurrentVersionExpirationDays)
|
|
if now.Before(expiryTime) {
|
|
return false
|
|
}
|
|
|
|
// Check NewerNoncurrentVersions count threshold.
|
|
if rule.NewerNoncurrentVersions > 0 && noncurrentIndex < rule.NewerNoncurrentVersions {
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// EvaluateMPUAbort finds the applicable AbortIncompleteMultipartUpload rule
|
|
// for a multipart upload with the given key prefix and creation time.
|
|
func EvaluateMPUAbort(rules []Rule, uploadKey string, createdAt time.Time, now time.Time) EvalResult {
|
|
for _, rule := range rules {
|
|
if rule.Status != "Enabled" {
|
|
continue
|
|
}
|
|
if rule.AbortMPUDaysAfterInitiation <= 0 {
|
|
continue
|
|
}
|
|
if !matchesPrefix(rule.Prefix, uploadKey) {
|
|
continue
|
|
}
|
|
cutoff := expectedExpiryTime(createdAt, rule.AbortMPUDaysAfterInitiation)
|
|
if !now.Before(cutoff) {
|
|
return EvalResult{Action: ActionAbortMultipartUpload, RuleID: rule.ID}
|
|
}
|
|
}
|
|
return EvalResult{Action: ActionNone}
|
|
}
|
|
|
|
// expectedExpiryTime computes the expiration time given a reference time and
|
|
// a number of days. Following S3 semantics, expiration happens at midnight UTC
|
|
// of the day after the specified number of days.
|
|
func expectedExpiryTime(refTime time.Time, days int) time.Time {
|
|
if days == 0 {
|
|
return refTime
|
|
}
|
|
t := refTime.UTC().Add(time.Duration(days+1) * 24 * time.Hour)
|
|
return t.Truncate(24 * time.Hour)
|
|
}
|