2024-04-30 21:11:10 -04:00
// Copyright (c) 2015-2024 MinIO, Inc.
2021-04-18 15:41:13 -04:00
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
2019-07-19 16:20:33 -04:00
package lifecycle
import (
"encoding/xml"
2021-03-30 02:52:30 -04:00
"fmt"
2019-07-19 16:20:33 -04:00
"io"
2021-07-20 20:36:55 -04:00
"net/http"
2024-04-30 21:11:10 -04:00
"slices"
2019-07-19 16:20:33 -04:00
"strings"
2019-08-09 13:02:41 -04:00
"time"
2021-07-20 20:36:55 -04:00
2022-09-22 13:51:54 -04:00
"github.com/google/uuid"
2024-05-22 02:50:03 -04:00
"github.com/minio/minio/internal/bucket/object/lock"
2021-07-20 20:36:55 -04:00
xhttp "github.com/minio/minio/internal/http"
2019-07-19 16:20:33 -04:00
)
var (
2021-12-14 12:41:44 -05:00
errLifecycleTooManyRules = Errorf ( "Lifecycle configuration allows a maximum of 1000 rules" )
errLifecycleNoRule = Errorf ( "Lifecycle configuration should have at least one rule" )
2023-06-13 16:52:33 -04:00
errLifecycleDuplicateID = Errorf ( "Rule ID must be unique. Found same ID for more than one rule" )
2021-12-14 12:41:44 -05:00
errXMLNotWellFormed = Errorf ( "The XML you provided was not well-formed or did not validate against our published schema" )
2024-05-22 07:26:59 -04:00
errLifecycleBucketLocked = Errorf ( "--expire-day, --expire-delete-marker, --expire-all-object-versions and --noncurrent-expire-days can't be used for locked bucket" )
2019-07-19 16:20:33 -04:00
)
2020-11-12 15:12:09 -05:00
const (
// TransitionComplete marks completed transition
TransitionComplete = "complete"
// TransitionPending - transition is yet to be attempted
TransitionPending = "pending"
)
2019-08-09 13:02:41 -04:00
// Action represents a delete action or other transition
// actions that will be implemented later.
type Action int
2020-06-12 13:28:21 -04:00
//go:generate stringer -type Action $GOFILE
2019-08-09 13:02:41 -04:00
const (
2021-03-05 17:15:53 -05:00
// NoneAction means no action required after evaluating lifecycle rules
2019-08-09 13:02:41 -04:00
NoneAction Action = iota
2020-11-12 15:12:09 -05:00
// DeleteAction means the object needs to be removed after evaluating lifecycle rules
2019-08-09 13:02:41 -04:00
DeleteAction
2020-07-04 23:56:02 -04:00
// DeleteVersionAction deletes a particular version
DeleteVersionAction
2020-11-12 15:12:09 -05:00
// TransitionAction transitions a particular object after evaluating lifecycle transition rules
TransitionAction
2021-11-16 12:28:29 -05:00
// TransitionVersionAction transitions a particular object version after evaluating lifecycle transition rules
2020-11-12 15:12:09 -05:00
TransitionVersionAction
// DeleteRestoredAction means the temporarily restored object needs to be removed after evaluating lifecycle rules
DeleteRestoredAction
// DeleteRestoredVersionAction deletes a particular version that was temporarily restored
DeleteRestoredVersionAction
2023-06-29 01:12:28 -04:00
// DeleteAllVersionsAction deletes all versions when an object expires
DeleteAllVersionsAction
2024-04-30 21:11:10 -04:00
// DelMarkerDeleteAllVersionsAction deletes all versions when an object with delete marker as latest version expires
DelMarkerDeleteAllVersionsAction
2021-10-02 12:31:05 -04:00
// ActionCount must be the last action and shouldn't be used as a regular action.
ActionCount
2019-08-09 13:02:41 -04:00
)
2023-03-09 18:15:30 -05:00
// DeleteRestored - Returns true if action demands delete on restored objects
func ( a Action ) DeleteRestored ( ) bool {
return a == DeleteRestoredAction || a == DeleteRestoredVersionAction
}
// DeleteVersioned - Returns true if action demands delete on a versioned object
func ( a Action ) DeleteVersioned ( ) bool {
return a == DeleteVersionAction || a == DeleteRestoredVersionAction
}
2023-06-29 01:12:28 -04:00
// DeleteAll - Returns true if the action demands deleting all versions of an object
func ( a Action ) DeleteAll ( ) bool {
2024-04-30 21:11:10 -04:00
return a == DeleteAllVersionsAction || a == DelMarkerDeleteAllVersionsAction
2023-06-29 01:12:28 -04:00
}
2023-03-09 18:15:30 -05:00
// Delete - Returns true if action demands delete on all objects (including restored)
func ( a Action ) Delete ( ) bool {
if a . DeleteRestored ( ) {
return true
}
2024-04-30 21:11:10 -04:00
return a == DeleteVersionAction || a == DeleteAction || a == DeleteAllVersionsAction || a == DelMarkerDeleteAllVersionsAction
2023-03-09 18:15:30 -05:00
}
2019-07-19 16:20:33 -04:00
// Lifecycle - Configuration for bucket lifecycle.
type Lifecycle struct {
2023-11-21 12:48:06 -05:00
XMLName xml . Name ` xml:"LifecycleConfiguration" `
Rules [ ] Rule ` xml:"Rule" `
ExpiryUpdatedAt * time . Time ` xml:"ExpiryUpdatedAt,omitempty" `
2019-07-19 16:20:33 -04:00
}
2022-01-10 12:07:49 -05:00
// HasTransition returns 'true' if lifecycle document has Transition enabled.
func ( lc Lifecycle ) HasTransition ( ) bool {
for _ , rule := range lc . Rules {
if rule . Transition . IsEnabled ( ) {
return true
}
}
return false
}
2023-11-21 12:48:06 -05:00
// HasExpiry returns 'true' if lifecycle document has Expiry enabled.
func ( lc Lifecycle ) HasExpiry ( ) bool {
for _ , rule := range lc . Rules {
if ! rule . Expiration . IsNull ( ) || ! rule . NoncurrentVersionExpiration . IsNull ( ) {
return true
}
}
return false
}
2021-03-19 01:18:35 -04:00
// UnmarshalXML - decodes XML data.
func ( lc * Lifecycle ) UnmarshalXML ( d * xml . Decoder , start xml . StartElement ) ( err error ) {
switch start . Name . Local {
case "LifecycleConfiguration" , "BucketLifecycleConfiguration" :
default :
2021-03-30 02:52:30 -04:00
return xml . UnmarshalError ( fmt . Sprintf ( "expected element type <LifecycleConfiguration>/<BucketLifecycleConfiguration> but have <%s>" ,
start . Name . Local ) )
2021-03-19 01:18:35 -04:00
}
for {
// Read tokens from the XML document in a stream.
t , err := d . Token ( )
if err != nil {
if err == io . EOF {
break
}
return err
}
2023-03-06 11:56:10 -05:00
if se , ok := t . ( xml . StartElement ) ; ok {
2021-03-19 01:18:35 -04:00
switch se . Name . Local {
case "Rule" :
var r Rule
if err = d . DecodeElement ( & r , & se ) ; err != nil {
return err
}
lc . Rules = append ( lc . Rules , r )
2023-11-21 12:48:06 -05:00
case "ExpiryUpdatedAt" :
var t time . Time
if err = d . DecodeElement ( & t , & start ) ; err != nil {
return err
}
lc . ExpiryUpdatedAt = & t
2021-03-19 01:18:35 -04:00
default :
2021-03-30 02:52:30 -04:00
return xml . UnmarshalError ( fmt . Sprintf ( "expected element type <Rule> but have <%s>" , se . Name . Local ) )
2021-03-19 01:18:35 -04:00
}
}
}
return nil
}
2022-11-10 10:17:45 -05:00
// HasActiveRules - returns whether lc has active rules at any level below or at prefix.
func ( lc Lifecycle ) HasActiveRules ( prefix string ) bool {
2020-06-12 13:28:21 -04:00
if len ( lc . Rules ) == 0 {
return false
}
for _ , rule := range lc . Rules {
if rule . Status == Disabled {
continue
}
2020-07-17 12:48:00 -04:00
2021-02-04 14:26:02 -05:00
if len ( prefix ) > 0 && len ( rule . GetPrefix ( ) ) > 0 {
2023-10-17 01:46:46 -04:00
// we can skip this rule if it doesn't match the tested
// prefix.
2022-11-10 10:17:45 -05:00
if ! strings . HasPrefix ( prefix , rule . GetPrefix ( ) ) && ! strings . HasPrefix ( rule . GetPrefix ( ) , prefix ) {
continue
2020-06-12 13:28:21 -04:00
}
}
if rule . NoncurrentVersionExpiration . NoncurrentDays > 0 {
return true
}
2021-12-14 12:41:44 -05:00
if rule . NoncurrentVersionExpiration . NewerNoncurrentVersions > 0 {
2021-11-19 20:54:10 -05:00
return true
}
2021-10-01 14:58:17 -04:00
if ! rule . NoncurrentVersionTransition . IsNull ( ) {
2020-06-12 13:28:21 -04:00
return true
}
2021-12-14 12:41:44 -05:00
if ! rule . Expiration . IsDateNull ( ) && rule . Expiration . Date . Before ( time . Now ( ) . UTC ( ) ) {
2020-11-12 15:12:09 -05:00
return true
}
2021-10-01 14:58:17 -04:00
if ! rule . Expiration . IsDaysNull ( ) {
return true
}
2023-10-17 01:46:46 -04:00
if rule . Expiration . DeleteMarker . val {
return true
}
2021-12-14 12:41:44 -05:00
if ! rule . Transition . IsDateNull ( ) && rule . Transition . Date . Before ( time . Now ( ) . UTC ( ) ) {
2020-11-12 15:12:09 -05:00
return true
}
2021-10-01 14:58:17 -04:00
if ! rule . Transition . IsNull ( ) { // this allows for Transition.Days to be zero.
2020-11-12 15:12:09 -05:00
return true
2020-06-12 13:28:21 -04:00
}
2021-10-01 14:58:17 -04:00
2020-06-12 13:28:21 -04:00
}
return false
2019-07-19 16:20:33 -04:00
}
2022-09-22 13:51:54 -04:00
// ParseLifecycleConfigWithID - parses for a Lifecycle config and assigns
// unique id to rules with empty ID.
func ParseLifecycleConfigWithID ( r io . Reader ) ( * Lifecycle , error ) {
var lc Lifecycle
if err := xml . NewDecoder ( r ) . Decode ( & lc ) ; err != nil {
return nil , err
}
// assign a unique id for rules with empty ID
for i := range lc . Rules {
if lc . Rules [ i ] . ID == "" {
lc . Rules [ i ] . ID = uuid . New ( ) . String ( )
}
}
return & lc , nil
}
2019-07-19 16:20:33 -04:00
// ParseLifecycleConfig - parses data in given reader to Lifecycle.
func ParseLifecycleConfig ( reader io . Reader ) ( * Lifecycle , error ) {
var lc Lifecycle
if err := xml . NewDecoder ( reader ) . Decode ( & lc ) ; err != nil {
return nil , err
}
return & lc , nil
}
// Validate - validates the lifecycle configuration
2024-05-22 02:50:03 -04:00
func ( lc Lifecycle ) Validate ( lr lock . Retention ) error {
2019-07-19 16:20:33 -04:00
// Lifecycle config can't have more than 1000 rules
if len ( lc . Rules ) > 1000 {
return errLifecycleTooManyRules
}
// Lifecycle config should have at least one rule
if len ( lc . Rules ) == 0 {
return errLifecycleNoRule
}
2021-10-01 14:58:17 -04:00
2019-07-19 16:20:33 -04:00
// Validate all the rules in the lifecycle config
for _ , r := range lc . Rules {
if err := r . Validate ( ) ; err != nil {
return err
}
2024-05-22 02:50:03 -04:00
if ( r . Expiration . DeleteMarker . val || // DeleteVersionAction
! r . DelMarkerExpiration . Empty ( ) || // DelMarkerDeleteAllVersionsAction
! r . NoncurrentVersionExpiration . IsDaysNull ( ) || // DeleteVersionAction
! r . Expiration . IsDaysNull ( ) ) && lr . LockEnabled {
2024-05-22 07:26:59 -04:00
return errLifecycleBucketLocked
2024-05-22 02:50:03 -04:00
}
2019-07-19 16:20:33 -04:00
}
2020-07-16 10:39:41 -04:00
// Make sure Rule ID is unique
2019-07-19 16:20:33 -04:00
for i := range lc . Rules {
if i == len ( lc . Rules ) - 1 {
break
}
otherRules := lc . Rules [ i + 1 : ]
for _ , otherRule := range otherRules {
2020-07-16 10:39:41 -04:00
if lc . Rules [ i ] . ID == otherRule . ID {
return errLifecycleDuplicateID
2019-07-19 16:20:33 -04:00
}
}
}
return nil
}
2019-08-09 13:02:41 -04:00
2022-10-21 13:46:53 -04:00
// FilterRules returns the rules filtered by the status, prefix and tags
func ( lc Lifecycle ) FilterRules ( obj ObjectOpts ) [ ] Rule {
2020-06-12 23:04:01 -04:00
if obj . Name == "" {
2020-05-28 13:55:48 -04:00
return nil
2020-02-25 10:52:28 -05:00
}
2020-05-28 13:55:48 -04:00
var rules [ ] Rule
2019-08-09 13:02:41 -04:00
for _ , rule := range lc . Rules {
2020-02-25 10:52:28 -05:00
if rule . Status == Disabled {
2019-08-09 13:02:41 -04:00
continue
}
2021-02-04 14:26:02 -05:00
if ! strings . HasPrefix ( obj . Name , rule . GetPrefix ( ) ) {
2020-06-05 13:30:10 -04:00
continue
}
2024-04-30 21:11:10 -04:00
if ! rule . Filter . TestTags ( obj . UserTags ) {
2020-06-12 23:04:01 -04:00
continue
}
2023-11-22 16:42:39 -05:00
if ! obj . DeleteMarker && ! rule . Filter . BySize ( obj . Size ) {
continue
}
2022-10-21 13:46:53 -04:00
rules = append ( rules , rule )
2019-08-09 13:02:41 -04:00
}
2020-05-28 13:55:48 -04:00
return rules
2019-08-09 13:02:41 -04:00
}
2020-06-12 23:04:01 -04:00
// ObjectOpts provides information to deduce the lifecycle actions
// which can be triggered on the resultant object.
type ObjectOpts struct {
2021-10-01 14:58:17 -04:00
Name string
UserTags string
ModTime time . Time
2023-11-22 16:42:39 -05:00
Size int64
2021-10-01 14:58:17 -04:00
VersionID string
IsLatest bool
DeleteMarker bool
NumVersions int
SuccessorModTime time . Time
TransitionStatus string
RestoreOngoing bool
RestoreExpires time . Time
2020-06-12 23:04:01 -04:00
}
2021-03-05 18:29:27 -05:00
// ExpiredObjectDeleteMarker returns true if an object version referred to by o
// is the only version remaining and is a delete marker. It returns false
// otherwise.
func ( o ObjectOpts ) ExpiredObjectDeleteMarker ( ) bool {
return o . DeleteMarker && o . NumVersions == 1
}
2022-10-21 13:46:53 -04:00
// Event contains a lifecycle action with associated info
type Event struct {
2023-05-02 15:56:33 -04:00
Action Action
RuleID string
Due time . Time
NoncurrentDays int
NewerNoncurrentVersions int
StorageClass string
2022-10-07 17:36:23 -04:00
}
2022-11-10 10:17:45 -05:00
// Eval returns the lifecycle event applicable now.
func ( lc Lifecycle ) Eval ( obj ObjectOpts ) Event {
return lc . eval ( obj , time . Now ( ) . UTC ( ) )
}
// eval returns the lifecycle event applicable at the given now. If now is the
// zero value of time.Time, it returns the upcoming lifecycle event.
func ( lc Lifecycle ) eval ( obj ObjectOpts , now time . Time ) Event {
2022-10-21 13:46:53 -04:00
var events [ ] Event
2020-06-12 23:04:01 -04:00
if obj . ModTime . IsZero ( ) {
2022-10-21 13:46:53 -04:00
return Event { }
2020-02-25 10:52:28 -05:00
}
2022-10-07 17:36:23 -04:00
// Handle expiry of restored object; NB Restored Objects have expiry set on
// them as part of RestoreObject API. They aren't governed by lifecycle
// rules.
if ! obj . RestoreExpires . IsZero ( ) && now . After ( obj . RestoreExpires ) {
action := DeleteRestoredAction
if ! obj . IsLatest {
action = DeleteRestoredVersionAction
}
2022-10-21 13:46:53 -04:00
events = append ( events , Event {
Action : action ,
Due : now ,
2022-10-07 17:36:23 -04:00
} )
}
2022-10-21 13:46:53 -04:00
for _ , rule := range lc . FilterRules ( obj ) {
2021-08-05 14:21:21 -04:00
if obj . ExpiredObjectDeleteMarker ( ) {
if rule . Expiration . DeleteMarker . val {
// Indicates whether MinIO will remove a delete marker with no noncurrent versions.
// Only latest marker is removed. If set to true, the delete marker will be expired;
// if set to false the policy takes no action. This cannot be specified with Days or
// Date in a Lifecycle Expiration Policy.
2022-10-21 13:46:53 -04:00
events = append ( events , Event {
Action : DeleteVersionAction ,
RuleID : rule . ID ,
Due : now ,
2022-10-07 17:36:23 -04:00
} )
// No other conflicting actions apply to an expired object delete marker
break
2021-08-05 14:21:21 -04:00
}
if ! rule . Expiration . IsDaysNull ( ) {
// 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
2022-11-09 07:20:34 -05:00
if expectedExpiry := ExpectedExpiryTime ( obj . ModTime , int ( rule . Expiration . Days ) ) ; now . IsZero ( ) || now . After ( expectedExpiry ) {
2022-10-21 13:46:53 -04:00
events = append ( events , Event {
Action : DeleteVersionAction ,
RuleID : rule . ID ,
Due : expectedExpiry ,
2022-10-07 17:36:23 -04:00
} )
// No other conflicting actions apply to an expired object delete marker
break
2021-08-05 14:21:21 -04:00
}
}
2020-06-12 23:04:01 -04:00
}
2024-04-30 21:11:10 -04:00
// DelMarkerExpiration
if obj . IsLatest && obj . DeleteMarker && ! rule . DelMarkerExpiration . Empty ( ) {
if due , ok := rule . DelMarkerExpiration . NextDue ( obj ) ; ok && ( now . IsZero ( ) || now . After ( due ) ) {
events = append ( events , Event {
Action : DelMarkerDeleteAllVersionsAction ,
RuleID : rule . ID ,
Due : due ,
} )
}
// No other conflicting actions in this rule can apply to an object with current version as DEL marker
// Note: There could be other rules with earlier expiration which need to be considered.
// See TestDelMarkerExpiration
continue
}
2022-10-07 17:36:23 -04:00
// Skip rules with newer noncurrent versions specified. These rules are
2022-11-10 10:17:45 -05:00
// not handled at an individual version level. eval applies only to a
// specific version.
2022-10-07 17:36:23 -04:00
if ! obj . IsLatest && rule . NoncurrentVersionExpiration . NewerNoncurrentVersions > 0 {
continue
}
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
2022-11-09 07:20:34 -05:00
if expectedExpiry := ExpectedExpiryTime ( obj . SuccessorModTime , int ( rule . NoncurrentVersionExpiration . NoncurrentDays ) ) ; now . IsZero ( ) || now . After ( expectedExpiry ) {
2022-10-21 13:46:53 -04:00
events = append ( events , Event {
Action : DeleteVersionAction ,
RuleID : rule . ID ,
Due : expectedExpiry ,
2022-10-07 17:36:23 -04:00
} )
2020-06-12 23:04:01 -04:00
}
}
2021-02-10 11:51:34 -05:00
2022-10-07 17:36:23 -04:00
if ! obj . IsLatest && ! rule . NoncurrentVersionTransition . IsNull ( ) {
if ! obj . DeleteMarker && obj . TransitionStatus != TransitionComplete {
2021-04-19 13:30:42 -04:00
// Non current versions should be transitioned if their age exceeds non current days configuration
2020-11-12 15:12:09 -05:00
// https://docs.aws.amazon.com/AmazonS3/latest/dev/intro-lifecycle-rules.html#intro-lifecycle-rules-actions
2022-11-09 07:20:34 -05:00
if due , ok := rule . NoncurrentVersionTransition . NextDue ( obj ) ; ok && ( now . IsZero ( ) || now . After ( due ) ) {
2022-10-21 13:46:53 -04:00
events = append ( events , Event {
Action : TransitionVersionAction ,
RuleID : rule . ID ,
Due : due ,
StorageClass : rule . NoncurrentVersionTransition . StorageClass ,
2022-10-07 17:36:23 -04:00
} )
2020-11-12 15:12:09 -05:00
}
}
}
2020-06-12 23:04:01 -04:00
2020-07-18 18:43:13 -04:00
// Remove the object or simply add a delete marker (once) in a versioned bucket
2022-10-07 17:36:23 -04:00
if obj . IsLatest && ! obj . DeleteMarker {
2020-06-12 23:04:01 -04:00
switch {
case ! rule . Expiration . IsDateNull ( ) :
2022-11-09 07:20:34 -05:00
if now . IsZero ( ) || now . After ( rule . Expiration . Date . Time ) {
2022-10-21 13:46:53 -04:00
events = append ( events , Event {
Action : DeleteAction ,
RuleID : rule . ID ,
Due : rule . Expiration . Date . Time ,
2022-10-07 17:36:23 -04:00
} )
2020-06-12 23:04:01 -04:00
}
case ! rule . Expiration . IsDaysNull ( ) :
2022-11-09 07:20:34 -05:00
if expectedExpiry := ExpectedExpiryTime ( obj . ModTime , int ( rule . Expiration . Days ) ) ; now . IsZero ( ) || now . After ( expectedExpiry ) {
2024-04-30 21:11:10 -04:00
event := Event {
2022-10-21 13:46:53 -04:00
Action : DeleteAction ,
RuleID : rule . ID ,
Due : expectedExpiry ,
2024-04-30 21:11:10 -04:00
}
if rule . Expiration . DeleteAll . val {
// Expires all versions of this object once the latest object is old enough.
// This is a MinIO only extension.
event . Action = DeleteAllVersionsAction
}
events = append ( events , event )
2020-06-12 23:04:01 -04:00
}
}
2021-03-05 18:29:27 -05:00
if obj . TransitionStatus != TransitionComplete {
2022-11-09 07:20:34 -05:00
if due , ok := rule . Transition . NextDue ( obj ) ; ok && ( now . IsZero ( ) || now . After ( due ) ) {
2022-10-21 13:46:53 -04:00
events = append ( events , Event {
Action : TransitionAction ,
RuleID : rule . ID ,
Due : due ,
StorageClass : rule . Transition . StorageClass ,
2022-10-07 17:36:23 -04:00
} )
2020-11-12 15:12:09 -05:00
}
}
2020-06-12 23:04:01 -04:00
}
2019-08-09 13:02:41 -04:00
}
2022-10-07 17:36:23 -04:00
if len ( events ) > 0 {
2024-04-30 21:11:10 -04:00
slices . SortFunc ( events , func ( a , b Event ) int {
2024-03-09 01:41:22 -05:00
// Prefer Expiration over Transition for both current
// and noncurrent versions when,
// - now is past the expected time to action
// - expected time to action is the same for both actions
2024-04-30 21:11:10 -04:00
if now . After ( a . Due ) && now . After ( b . Due ) || a . Due . Equal ( b . Due ) {
switch a . Action {
case DeleteAllVersionsAction , DelMarkerDeleteAllVersionsAction ,
DeleteAction , DeleteVersionAction :
return - 1
2023-04-24 16:28:18 -04:00
}
2024-04-30 21:11:10 -04:00
switch b . Action {
case DeleteAllVersionsAction , DelMarkerDeleteAllVersionsAction ,
DeleteAction , DeleteVersionAction :
return 1
2023-04-24 16:28:18 -04:00
}
2024-04-30 21:11:10 -04:00
return - 1
2023-04-24 16:28:18 -04:00
}
// Prefer earlier occurring event
2024-04-30 21:11:10 -04:00
if a . Due . Before ( b . Due ) {
return - 1
}
return 1
2023-04-24 16:28:18 -04:00
} )
2022-10-07 17:36:23 -04:00
return events [ 0 ]
}
2022-10-21 13:46:53 -04:00
return Event {
Action : NoneAction ,
2022-10-07 17:36:23 -04:00
}
}
2020-11-12 15:12:09 -05:00
// ExpectedExpiryTime calculates the expiry, transition or restore date/time based on a object modtime.
2022-08-30 11:26:43 -04:00
// The expected transition or restore time is always a midnight time following the object
2020-11-12 15:12:09 -05:00
// modification time plus the number of transition/restore days.
2022-08-26 15:52:29 -04:00
//
// e.g. If the object modtime is `Thu May 21 13:42:50 GMT 2020` and the object should
// transition in 1 day, then the expected transition time is `Fri, 23 May 2020 00:00:00 GMT`
2020-11-12 15:12:09 -05:00
func ExpectedExpiryTime ( modTime time . Time , days int ) time . Time {
2021-12-14 12:41:44 -05:00
if days == 0 {
return modTime
}
2020-05-21 17:12:52 -04:00
t := modTime . UTC ( ) . Add ( time . Duration ( days + 1 ) * 24 * time . Hour )
return t . Truncate ( 24 * time . Hour )
}
2021-07-20 20:36:55 -04:00
// SetPredictionHeaders sets time to expiry and transition headers on w for a
// given obj.
func ( lc Lifecycle ) SetPredictionHeaders ( w http . ResponseWriter , obj ObjectOpts ) {
2022-11-10 10:17:45 -05:00
event := lc . eval ( obj , time . Time { } )
2022-10-21 13:46:53 -04:00
switch event . Action {
2024-04-30 21:11:10 -04:00
case DeleteAction , DeleteVersionAction , DeleteAllVersionsAction , DelMarkerDeleteAllVersionsAction :
2021-07-20 20:36:55 -04:00
w . Header ( ) [ xhttp . AmzExpiration ] = [ ] string {
2022-10-07 17:36:23 -04:00
fmt . Sprintf ( ` expiry-date="%s", rule-id="%s" ` , event . Due . Format ( http . TimeFormat ) , event . RuleID ) ,
2021-07-20 20:36:55 -04:00
}
2022-10-07 17:36:23 -04:00
case TransitionAction , TransitionVersionAction :
2021-07-20 20:36:55 -04:00
w . Header ( ) [ xhttp . MinIOTransition ] = [ ] string {
2022-10-07 17:36:23 -04:00
fmt . Sprintf ( ` transition-date="%s", rule-id="%s" ` , event . Due . Format ( http . TimeFormat ) , event . RuleID ) ,
2021-07-20 20:36:55 -04:00
}
}
}
2021-07-21 22:12:44 -04:00
2022-10-07 17:36:23 -04:00
// NoncurrentVersionsExpirationLimit returns the number of noncurrent versions
// to be retained from the first applicable rule per S3 behavior.
2023-05-02 15:56:33 -04:00
func ( lc Lifecycle ) NoncurrentVersionsExpirationLimit ( obj ObjectOpts ) Event {
2022-10-21 13:46:53 -04:00
for _ , rule := range lc . FilterRules ( obj ) {
2021-12-14 12:41:44 -05:00
if rule . NoncurrentVersionExpiration . NewerNoncurrentVersions == 0 {
2021-11-19 20:54:10 -05:00
continue
}
2023-05-02 15:56:33 -04:00
return Event {
Action : DeleteVersionAction ,
RuleID : rule . ID ,
NoncurrentDays : int ( rule . NoncurrentVersionExpiration . NoncurrentDays ) ,
NewerNoncurrentVersions : rule . NoncurrentVersionExpiration . NewerNoncurrentVersions ,
}
2021-11-19 20:54:10 -05:00
}
2023-05-02 15:56:33 -04:00
return Event { }
2021-11-19 20:54:10 -05:00
}