storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/pkg/bucket/lifecycle/transition.go (about) 1 /* 2 * MinIO Cloud Storage, (C) 2019 MinIO, Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package lifecycle 18 19 import ( 20 "encoding/xml" 21 "time" 22 ) 23 24 var ( 25 errTransitionInvalidDays = Errorf("Days must be 0 or greater when used with Transition") 26 errTransitionInvalidDate = Errorf("Date must be provided in ISO 8601 format") 27 errTransitionInvalid = Errorf("Exactly one of Days (0 or greater) or Date (positive ISO 8601 format) should be present inside Expiration.") 28 errTransitionDateNotMidnight = Errorf("'Date' must be at midnight GMT") 29 ) 30 31 // TransitionDate is a embedded type containing time.Time to unmarshal 32 // Date in Transition 33 type TransitionDate struct { 34 time.Time 35 } 36 37 // UnmarshalXML parses date from Transition and validates date format 38 func (tDate *TransitionDate) UnmarshalXML(d *xml.Decoder, startElement xml.StartElement) error { 39 var dateStr string 40 err := d.DecodeElement(&dateStr, &startElement) 41 if err != nil { 42 return err 43 } 44 // While AWS documentation mentions that the date specified 45 // must be present in ISO 8601 format, in reality they allow 46 // users to provide RFC 3339 compliant dates. 47 trnDate, err := time.Parse(time.RFC3339, dateStr) 48 if err != nil { 49 return errTransitionInvalidDate 50 } 51 // Allow only date timestamp specifying midnight GMT 52 hr, min, sec := trnDate.Clock() 53 nsec := trnDate.Nanosecond() 54 loc := trnDate.Location() 55 if !(hr == 0 && min == 0 && sec == 0 && nsec == 0 && loc.String() == time.UTC.String()) { 56 return errTransitionDateNotMidnight 57 } 58 59 *tDate = TransitionDate{trnDate} 60 return nil 61 } 62 63 // MarshalXML encodes expiration date if it is non-zero and encodes 64 // empty string otherwise 65 func (tDate TransitionDate) MarshalXML(e *xml.Encoder, startElement xml.StartElement) error { 66 if tDate.Time.IsZero() { 67 return nil 68 } 69 return e.EncodeElement(tDate.Format(time.RFC3339), startElement) 70 } 71 72 // TransitionDays is a type alias to unmarshal Days in Transition 73 type TransitionDays int 74 75 // UnmarshalXML parses number of days from Transition and validates if 76 // >= 0 77 func (tDays *TransitionDays) UnmarshalXML(d *xml.Decoder, startElement xml.StartElement) error { 78 var numDays int 79 err := d.DecodeElement(&numDays, &startElement) 80 if err != nil { 81 return err 82 } 83 if numDays < 0 { 84 return errTransitionInvalidDays 85 } 86 *tDays = TransitionDays(numDays) 87 return nil 88 } 89 90 // MarshalXML encodes number of days to expire if it is non-zero and 91 // encodes empty string otherwise 92 func (tDays TransitionDays) MarshalXML(e *xml.Encoder, startElement xml.StartElement) error { 93 if tDays == 0 { 94 return nil 95 } 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 // MarshalXML encodes transition field into an XML form. 110 func (t Transition) MarshalXML(enc *xml.Encoder, start xml.StartElement) error { 111 if !t.set { 112 return nil 113 } 114 type transitionWrapper Transition 115 return enc.EncodeElement(transitionWrapper(t), start) 116 } 117 118 // UnmarshalXML decodes transition field from the XML form. 119 func (t *Transition) UnmarshalXML(d *xml.Decoder, startElement xml.StartElement) error { 120 type transitionWrapper Transition 121 var trw transitionWrapper 122 err := d.DecodeElement(&trw, &startElement) 123 if err != nil { 124 return err 125 } 126 *t = Transition(trw) 127 t.set = true 128 return nil 129 } 130 131 // Validate - validates the "Expiration" element 132 func (t Transition) Validate() error { 133 if !t.set { 134 return nil 135 } 136 137 if t.IsDaysNull() && t.IsDateNull() { 138 return errXMLNotWellFormed 139 } 140 141 // Both transition days and date are specified 142 if !t.IsDaysNull() && !t.IsDateNull() { 143 return errTransitionInvalid 144 } 145 if t.StorageClass == "" { 146 return errXMLNotWellFormed 147 } 148 return nil 149 } 150 151 // IsDaysNull returns true if days field is null 152 func (t Transition) IsDaysNull() bool { 153 return t.Days == TransitionDays(0) 154 } 155 156 // IsDateNull returns true if date field is null 157 func (t Transition) IsDateNull() bool { 158 return t.Date.Time.IsZero() 159 } 160 161 // IsNull returns true if both date and days fields are null 162 func (t Transition) IsNull() bool { 163 return t.IsDaysNull() && t.IsDateNull() 164 }