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 }