github.com/minio/mc@v0.0.0-20240503112107-b471de8d1882/cmd/ilm/parse.go (about)

     1  // Copyright (c) 2015-2022 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 ilm
    19  
    20  import (
    21  	"errors"
    22  	"strconv"
    23  	"strings"
    24  	"time"
    25  
    26  	"github.com/minio/mc/pkg/probe"
    27  	"github.com/minio/minio-go/v7/pkg/lifecycle"
    28  )
    29  
    30  // Used in tags. Ex: --tags "key1=value1&key2=value2&key3=value3"
    31  const (
    32  	tagSeperator    string = "&"
    33  	keyValSeperator string = "="
    34  )
    35  
    36  // Extracts the tags provided by user. The tagfilter array will be put in lifecycleRule structure.
    37  func extractILMTags(tagLabelVal string) []lifecycle.Tag {
    38  	var ilmTagKVList []lifecycle.Tag
    39  	for _, tag := range strings.Split(tagLabelVal, tagSeperator) {
    40  		if tag == "" {
    41  			// split returns empty for empty tagLabelVal, skip it.
    42  			continue
    43  		}
    44  		lfcTag := lifecycle.Tag{}
    45  		kvs := strings.SplitN(tag, keyValSeperator, 2)
    46  		if len(kvs) == 2 {
    47  			lfcTag.Key = kvs[0]
    48  			lfcTag.Value = kvs[1]
    49  		} else {
    50  			lfcTag.Key = kvs[0]
    51  		}
    52  		ilmTagKVList = append(ilmTagKVList, lfcTag)
    53  	}
    54  	return ilmTagKVList
    55  }
    56  
    57  // Some of these rules are enforced by Amazon S3 standards.
    58  // For example: Transition has to happen before Expiry.
    59  // Storage class must be specified if transition date/days is provided.
    60  func validateTranExpDate(rule lifecycle.Rule) error {
    61  	expiryDateSet := !rule.Expiration.IsDateNull()
    62  	transitionSet := !rule.Transition.IsNull()
    63  	transitionDateSet := transitionSet && !rule.Transition.IsDateNull()
    64  	if transitionDateSet && expiryDateSet {
    65  		if rule.Expiration.Date.Before(rule.Transition.Date.Time) {
    66  			return errors.New("transition should apply before expiration")
    67  		}
    68  	}
    69  	if transitionDateSet && rule.Transition.StorageClass == "" {
    70  		return errors.New("missing transition storage-class")
    71  	}
    72  	return nil
    73  }
    74  
    75  func validateTranDays(rule lifecycle.Rule) error {
    76  	if rule.Transition.Days < 0 {
    77  		return errors.New("number of days to transition can't be negative")
    78  	}
    79  	if rule.Transition.Days < 30 && strings.ToLower(rule.Transition.StorageClass) == "standard_ia" {
    80  		return errors.New("number of days to transition should be >= 30 with STANDARD_IA storage-class")
    81  	}
    82  	return nil
    83  }
    84  
    85  // Amazon S3 requires a minimum of one action for a rule to be added.
    86  func validateRuleAction(rule lifecycle.Rule) error {
    87  	expirySet := !rule.Expiration.IsNull()
    88  	transitionSet := !rule.Transition.IsNull()
    89  	noncurrentExpirySet := !rule.NoncurrentVersionExpiration.IsDaysNull()
    90  	newerNoncurrentVersionsExpiry := rule.NoncurrentVersionExpiration.NewerNoncurrentVersions > 0
    91  	noncurrentTransitionSet := rule.NoncurrentVersionTransition.StorageClass != ""
    92  	newerNoncurrentVersionsTransition := rule.NoncurrentVersionTransition.NewerNoncurrentVersions > 0
    93  	if !expirySet && !transitionSet && !noncurrentExpirySet && !noncurrentTransitionSet && !newerNoncurrentVersionsExpiry && !newerNoncurrentVersionsTransition {
    94  		return errors.New("at least one of Expiry, Transition, NoncurrentExpiry, NoncurrentVersionTransition actions should be specified in a rule")
    95  	}
    96  	return nil
    97  }
    98  
    99  func validateExpiration(rule lifecycle.Rule) error {
   100  	var i int
   101  	if !rule.Expiration.IsDaysNull() {
   102  		i++
   103  	}
   104  	if !rule.Expiration.IsDateNull() {
   105  		i++
   106  	}
   107  	if rule.Expiration.IsDeleteMarkerExpirationEnabled() {
   108  		i++
   109  	}
   110  	if i > 1 {
   111  		return errors.New("only one parameter under Expiration can be specified")
   112  	}
   113  	return nil
   114  }
   115  
   116  func validateNoncurrentExpiration(rule lifecycle.Rule) error {
   117  	days := rule.NoncurrentVersionExpiration.NoncurrentDays
   118  	if days < 0 {
   119  		return errors.New("NoncurrentVersionExpiration.NoncurrentDays is not a positive integer")
   120  	}
   121  	return nil
   122  }
   123  
   124  func validateNoncurrentTransition(rule lifecycle.Rule) error {
   125  	days := rule.NoncurrentVersionTransition.NoncurrentDays
   126  	storageClass := rule.NoncurrentVersionTransition.StorageClass
   127  	if days < 0 {
   128  		return errors.New("NoncurrentVersionTransition.NoncurrentDays is not a positive integer")
   129  	}
   130  	if days > 0 && storageClass == "" {
   131  		return errors.New("both NoncurrentVersionTransition NoncurrentDays and StorageClass need to be specified")
   132  	}
   133  	return nil
   134  }
   135  
   136  // Check if any date is before than cur date
   137  func validateTranExpCurdate(rule lifecycle.Rule) error {
   138  	var e error
   139  	expirySet := !rule.Expiration.IsNull()
   140  	transitionSet := !rule.Transition.IsNull()
   141  	transitionDateSet := transitionSet && !rule.Transition.IsDateNull()
   142  	expiryDateSet := expirySet && !rule.Expiration.IsDateNull()
   143  	currentTime := time.Now()
   144  	curTimeStr := currentTime.Format(defaultILMDateFormat)
   145  	currentTime, e = time.Parse(defaultILMDateFormat, curTimeStr)
   146  	if e != nil {
   147  		return e
   148  	}
   149  	if expirySet && expiryDateSet && rule.Expiration.Date.Before(currentTime) {
   150  		e = errors.New("expiry date falls before or on today's date")
   151  	} else if transitionSet && transitionDateSet && rule.Transition.Date.Before(currentTime) {
   152  		e = errors.New("transition date falls before or on today's date")
   153  	}
   154  	return e
   155  }
   156  
   157  // Check S3 compatibility for the new rule and some other basic checks.
   158  func validateILMRule(rule lifecycle.Rule) *probe.Error {
   159  	if e := validateRuleAction(rule); e != nil {
   160  		return probe.NewError(e)
   161  	}
   162  	if e := validateExpiration(rule); e != nil {
   163  		return probe.NewError(e)
   164  	}
   165  	if e := validateTranExpCurdate(rule); e != nil {
   166  		return probe.NewError(e)
   167  	}
   168  	if e := validateTranExpDate(rule); e != nil {
   169  		return probe.NewError(e)
   170  	}
   171  	if e := validateTranDays(rule); e != nil {
   172  		return probe.NewError(e)
   173  	}
   174  	if e := validateNoncurrentExpiration(rule); e != nil {
   175  		return probe.NewError(e)
   176  	}
   177  	if e := validateNoncurrentTransition(rule); e != nil {
   178  		return probe.NewError(e)
   179  	}
   180  
   181  	return nil
   182  }
   183  
   184  func parseTransitionDate(transitionDateStr string) (lifecycle.ExpirationDate, *probe.Error) {
   185  	transitionDate, e := time.Parse(defaultILMDateFormat, transitionDateStr)
   186  	if e != nil {
   187  		return lifecycle.ExpirationDate{}, probe.NewError(e)
   188  	}
   189  	return lifecycle.ExpirationDate{Time: transitionDate}, nil
   190  }
   191  
   192  func parseTransitionDays(transitionDaysStr string) (lifecycle.ExpirationDays, *probe.Error) {
   193  	transitionDays, e := strconv.Atoi(transitionDaysStr)
   194  	if e != nil {
   195  		return lifecycle.ExpirationDays(0), probe.NewError(e)
   196  	}
   197  	return lifecycle.ExpirationDays(transitionDays), nil
   198  }
   199  
   200  // Returns valid lifecycleTransition to be included in lifecycleRule
   201  func parseTransition(storageClass, transitionDateStr, transitionDaysStr *string) (lifecycle.Transition, *probe.Error) {
   202  	var transition lifecycle.Transition
   203  	if transitionDateStr != nil {
   204  		transitionDate, err := parseTransitionDate(*transitionDateStr)
   205  		if err != nil {
   206  			return lifecycle.Transition{}, err
   207  		}
   208  		transition.Date = transitionDate
   209  	} else if transitionDaysStr != nil {
   210  		transitionDays, err := parseTransitionDays(*transitionDaysStr)
   211  		if err != nil {
   212  			return lifecycle.Transition{}, err
   213  		}
   214  		transition.Days = transitionDays
   215  	}
   216  	if storageClass != nil {
   217  		transition.StorageClass = *storageClass
   218  	}
   219  	return transition, nil
   220  }
   221  
   222  func parseExpiryDate(expiryDateStr string) (lifecycle.ExpirationDate, *probe.Error) {
   223  	date, e := time.Parse(defaultILMDateFormat, expiryDateStr)
   224  	if e != nil {
   225  		return lifecycle.ExpirationDate{}, probe.NewError(e)
   226  	}
   227  	if date.IsZero() {
   228  		return lifecycle.ExpirationDate{}, probe.NewError(errors.New("expiration date cannot be set to zero"))
   229  	}
   230  	return lifecycle.ExpirationDate{Time: date}, nil
   231  }
   232  
   233  func parseExpiryDays(expiryDayStr string) (lifecycle.ExpirationDays, *probe.Error) {
   234  	days, e := strconv.Atoi(expiryDayStr)
   235  	if e != nil {
   236  		return lifecycle.ExpirationDays(0), probe.NewError(e)
   237  	}
   238  	if days == 0 {
   239  		return lifecycle.ExpirationDays(0), probe.NewError(errors.New("expiration days cannot be set to zero"))
   240  	}
   241  	return lifecycle.ExpirationDays(days), nil
   242  }
   243  
   244  // Returns lifecycleExpiration to be included in lifecycleRule
   245  func parseExpiry(expiryDate, expiryDays *string, expiredDeleteMarker, expiredObjectAllVersions *bool) (lfcExp lifecycle.Expiration, err *probe.Error) {
   246  	if expiryDate != nil {
   247  		date, err := parseExpiryDate(*expiryDate)
   248  		if err != nil {
   249  			return lifecycle.Expiration{}, err
   250  		}
   251  		lfcExp.Date = date
   252  	}
   253  
   254  	if expiryDays != nil {
   255  		days, err := parseExpiryDays(*expiryDays)
   256  		if err != nil {
   257  			return lifecycle.Expiration{}, err
   258  		}
   259  		lfcExp.Days = days
   260  	}
   261  
   262  	if expiredDeleteMarker != nil {
   263  		lfcExp.DeleteMarker = lifecycle.ExpireDeleteMarker(*expiredDeleteMarker)
   264  	}
   265  
   266  	if expiredObjectAllVersions != nil {
   267  		lfcExp.DeleteAll = lifecycle.ExpirationBoolean(*expiredObjectAllVersions)
   268  	}
   269  
   270  	return lfcExp, nil
   271  }