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  }