github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/internal/bucket/lifecycle/transition.go (about) 1 // Copyright (c) 2015-2021 MinIO, Inc. 2 // 3 // This file is part of MinIO Object Storage stack 4 // 5 // This program is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU Affero General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // This program is distributed in the hope that it will be useful 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Affero General Public License for more details. 14 // 15 // You should have received a copy of the GNU Affero General Public License 16 // along with this program. If not, see <http://www.gnu.org/licenses/>. 17 18 package lifecycle 19 20 import ( 21 "encoding/xml" 22 "time" 23 ) 24 25 var ( 26 errTransitionInvalidDays = Errorf("Days must be 0 or greater when used with Transition") 27 errTransitionInvalidDate = Errorf("Date must be provided in ISO 8601 format") 28 errTransitionInvalid = Errorf("Exactly one of Days (0 or greater) or Date (positive ISO 8601 format) should be present in Transition.") 29 errTransitionDateNotMidnight = Errorf("'Date' must be at midnight GMT") 30 ) 31 32 // TransitionDate is a embedded type containing time.Time to unmarshal 33 // Date in Transition 34 type TransitionDate struct { 35 time.Time 36 } 37 38 // UnmarshalXML parses date from Transition and validates date format 39 func (tDate *TransitionDate) UnmarshalXML(d *xml.Decoder, startElement xml.StartElement) error { 40 var dateStr string 41 err := d.DecodeElement(&dateStr, &startElement) 42 if err != nil { 43 return err 44 } 45 // While AWS documentation mentions that the date specified 46 // must be present in ISO 8601 format, in reality they allow 47 // users to provide RFC 3339 compliant dates. 48 trnDate, err := time.Parse(time.RFC3339, dateStr) 49 if err != nil { 50 return errTransitionInvalidDate 51 } 52 // Allow only date timestamp specifying midnight GMT 53 hr, min, sec := trnDate.Clock() 54 nsec := trnDate.Nanosecond() 55 loc := trnDate.Location() 56 if !(hr == 0 && min == 0 && sec == 0 && nsec == 0 && loc.String() == time.UTC.String()) { 57 return errTransitionDateNotMidnight 58 } 59 60 *tDate = TransitionDate{trnDate} 61 return nil 62 } 63 64 // MarshalXML encodes expiration date if it is non-zero and encodes 65 // empty string otherwise 66 func (tDate TransitionDate) MarshalXML(e *xml.Encoder, startElement xml.StartElement) error { 67 if tDate.Time.IsZero() { 68 return nil 69 } 70 return e.EncodeElement(tDate.Format(time.RFC3339), startElement) 71 } 72 73 // TransitionDays is a type alias to unmarshal Days in Transition 74 type TransitionDays int 75 76 // UnmarshalXML parses number of days from Transition and validates if 77 // >= 0 78 func (tDays *TransitionDays) UnmarshalXML(d *xml.Decoder, startElement xml.StartElement) error { 79 var days int 80 err := d.DecodeElement(&days, &startElement) 81 if err != nil { 82 return err 83 } 84 85 if days < 0 { 86 return errTransitionInvalidDays 87 } 88 *tDays = TransitionDays(days) 89 90 return nil 91 } 92 93 // MarshalXML encodes number of days to expire if it is non-zero and 94 // encodes empty string otherwise 95 func (tDays TransitionDays) MarshalXML(e *xml.Encoder, startElement xml.StartElement) error { 96 return e.EncodeElement(int(tDays), startElement) 97 } 98 99 // Transition - transition actions for a rule in lifecycle configuration. 100 type Transition struct { 101 XMLName xml.Name `xml:"Transition"` 102 Days TransitionDays `xml:"Days,omitempty"` 103 Date TransitionDate `xml:"Date,omitempty"` 104 StorageClass string `xml:"StorageClass,omitempty"` 105 106 set bool 107 } 108 109 // IsEnabled returns if transition is enabled. 110 func (t Transition) IsEnabled() bool { 111 return t.set 112 } 113 114 // MarshalXML encodes transition field into an XML form. 115 func (t Transition) MarshalXML(enc *xml.Encoder, start xml.StartElement) error { 116 if !t.set { 117 return nil 118 } 119 type transitionWrapper Transition 120 return enc.EncodeElement(transitionWrapper(t), start) 121 } 122 123 // UnmarshalXML decodes transition field from the XML form. 124 func (t *Transition) UnmarshalXML(d *xml.Decoder, startElement xml.StartElement) error { 125 type transitionWrapper Transition 126 var trw transitionWrapper 127 err := d.DecodeElement(&trw, &startElement) 128 if err != nil { 129 return err 130 } 131 *t = Transition(trw) 132 t.set = true 133 return nil 134 } 135 136 // Validate - validates the "Transition" element 137 func (t Transition) Validate() error { 138 if !t.set { 139 return nil 140 } 141 142 if !t.IsDateNull() && t.Days > 0 { 143 return errTransitionInvalid 144 } 145 146 if t.StorageClass == "" { 147 return errXMLNotWellFormed 148 } 149 return nil 150 } 151 152 // IsDateNull returns true if date field is null 153 func (t Transition) IsDateNull() bool { 154 return t.Date.Time.IsZero() 155 } 156 157 // IsNull returns true if both date and days fields are null 158 func (t Transition) IsNull() bool { 159 return t.StorageClass == "" 160 } 161 162 // NextDue returns upcoming transition date for obj and true if applicable, 163 // returns false otherwise. 164 func (t Transition) NextDue(obj ObjectOpts) (time.Time, bool) { 165 if !obj.IsLatest || t.IsNull() { 166 return time.Time{}, false 167 } 168 169 if !t.IsDateNull() { 170 return t.Date.Time, true 171 } 172 173 // Days == 0 indicates immediate tiering, i.e object is eligible for tiering since its creation. 174 if t.Days == 0 { 175 return obj.ModTime, true 176 } 177 return ExpectedExpiryTime(obj.ModTime, int(t.Days)), true 178 }