lifecycle.go (16024B)
1 /* 2 * MinIO Go Library for Amazon S3 Compatible Cloud Storage 3 * Copyright 2020 MinIO, Inc. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 // Package lifecycle contains all the lifecycle related data types and marshallers. 19 package lifecycle 20 21 import ( 22 "encoding/json" 23 "encoding/xml" 24 "errors" 25 "time" 26 ) 27 28 var errMissingStorageClass = errors.New("storage-class cannot be empty") 29 30 // AbortIncompleteMultipartUpload structure, not supported yet on MinIO 31 type AbortIncompleteMultipartUpload struct { 32 XMLName xml.Name `xml:"AbortIncompleteMultipartUpload,omitempty" json:"-"` 33 DaysAfterInitiation ExpirationDays `xml:"DaysAfterInitiation,omitempty" json:"DaysAfterInitiation,omitempty"` 34 } 35 36 // IsDaysNull returns true if days field is null 37 func (n AbortIncompleteMultipartUpload) IsDaysNull() bool { 38 return n.DaysAfterInitiation == ExpirationDays(0) 39 } 40 41 // MarshalXML if days after initiation is set to non-zero value 42 func (n AbortIncompleteMultipartUpload) MarshalXML(e *xml.Encoder, start xml.StartElement) error { 43 if n.IsDaysNull() { 44 return nil 45 } 46 type abortIncompleteMultipartUploadWrapper AbortIncompleteMultipartUpload 47 return e.EncodeElement(abortIncompleteMultipartUploadWrapper(n), start) 48 } 49 50 // NoncurrentVersionExpiration - Specifies when noncurrent object versions expire. 51 // Upon expiration, server permanently deletes the noncurrent object versions. 52 // Set this lifecycle configuration action on a bucket that has versioning enabled 53 // (or suspended) to request server delete noncurrent object versions at a 54 // specific period in the object's lifetime. 55 type NoncurrentVersionExpiration struct { 56 XMLName xml.Name `xml:"NoncurrentVersionExpiration" json:"-"` 57 NoncurrentDays ExpirationDays `xml:"NoncurrentDays,omitempty" json:"NoncurrentDays,omitempty"` 58 NewerNoncurrentVersions int `xml:"NewerNoncurrentVersions,omitempty" json:"NewerNoncurrentVersions,omitempty"` 59 } 60 61 // MarshalXML if n is non-empty, i.e has a non-zero NoncurrentDays or NewerNoncurrentVersions. 62 func (n NoncurrentVersionExpiration) MarshalXML(e *xml.Encoder, start xml.StartElement) error { 63 if n.isNull() { 64 return nil 65 } 66 type noncurrentVersionExpirationWrapper NoncurrentVersionExpiration 67 return e.EncodeElement(noncurrentVersionExpirationWrapper(n), start) 68 } 69 70 // IsDaysNull returns true if days field is null 71 func (n NoncurrentVersionExpiration) IsDaysNull() bool { 72 return n.NoncurrentDays == ExpirationDays(0) 73 } 74 75 func (n NoncurrentVersionExpiration) isNull() bool { 76 return n.IsDaysNull() && n.NewerNoncurrentVersions == 0 77 } 78 79 // NoncurrentVersionTransition structure, set this action to request server to 80 // transition noncurrent object versions to different set storage classes 81 // at a specific period in the object's lifetime. 82 type NoncurrentVersionTransition struct { 83 XMLName xml.Name `xml:"NoncurrentVersionTransition,omitempty" json:"-"` 84 StorageClass string `xml:"StorageClass,omitempty" json:"StorageClass,omitempty"` 85 NoncurrentDays ExpirationDays `xml:"NoncurrentDays" json:"NoncurrentDays"` 86 NewerNoncurrentVersions int `xml:"NewerNoncurrentVersions,omitempty" json:"NewerNoncurrentVersions,omitempty"` 87 } 88 89 // IsDaysNull returns true if days field is null 90 func (n NoncurrentVersionTransition) IsDaysNull() bool { 91 return n.NoncurrentDays == ExpirationDays(0) 92 } 93 94 // IsStorageClassEmpty returns true if storage class field is empty 95 func (n NoncurrentVersionTransition) IsStorageClassEmpty() bool { 96 return n.StorageClass == "" 97 } 98 99 func (n NoncurrentVersionTransition) isNull() bool { 100 return n.StorageClass == "" 101 } 102 103 // UnmarshalJSON implements NoncurrentVersionTransition JSONify 104 func (n *NoncurrentVersionTransition) UnmarshalJSON(b []byte) error { 105 type noncurrentVersionTransition NoncurrentVersionTransition 106 var nt noncurrentVersionTransition 107 err := json.Unmarshal(b, &nt) 108 if err != nil { 109 return err 110 } 111 112 if nt.StorageClass == "" { 113 return errMissingStorageClass 114 } 115 *n = NoncurrentVersionTransition(nt) 116 return nil 117 } 118 119 // MarshalXML is extended to leave out 120 // <NoncurrentVersionTransition></NoncurrentVersionTransition> tags 121 func (n NoncurrentVersionTransition) MarshalXML(e *xml.Encoder, start xml.StartElement) error { 122 if n.isNull() { 123 return nil 124 } 125 type noncurrentVersionTransitionWrapper NoncurrentVersionTransition 126 return e.EncodeElement(noncurrentVersionTransitionWrapper(n), start) 127 } 128 129 // Tag structure key/value pair representing an object tag to apply lifecycle configuration 130 type Tag struct { 131 XMLName xml.Name `xml:"Tag,omitempty" json:"-"` 132 Key string `xml:"Key,omitempty" json:"Key,omitempty"` 133 Value string `xml:"Value,omitempty" json:"Value,omitempty"` 134 } 135 136 // IsEmpty returns whether this tag is empty or not. 137 func (tag Tag) IsEmpty() bool { 138 return tag.Key == "" 139 } 140 141 // Transition structure - transition details of lifecycle configuration 142 type Transition struct { 143 XMLName xml.Name `xml:"Transition" json:"-"` 144 Date ExpirationDate `xml:"Date,omitempty" json:"Date,omitempty"` 145 StorageClass string `xml:"StorageClass,omitempty" json:"StorageClass,omitempty"` 146 Days ExpirationDays `xml:"Days" json:"Days"` 147 } 148 149 // UnmarshalJSON returns an error if storage-class is empty. 150 func (t *Transition) UnmarshalJSON(b []byte) error { 151 type transition Transition 152 var tr transition 153 err := json.Unmarshal(b, &tr) 154 if err != nil { 155 return err 156 } 157 158 if tr.StorageClass == "" { 159 return errMissingStorageClass 160 } 161 *t = Transition(tr) 162 return nil 163 } 164 165 // MarshalJSON customizes json encoding by omitting empty values 166 func (t Transition) MarshalJSON() ([]byte, error) { 167 if t.IsNull() { 168 return nil, nil 169 } 170 type transition struct { 171 Date *ExpirationDate `json:"Date,omitempty"` 172 StorageClass string `json:"StorageClass,omitempty"` 173 Days *ExpirationDays `json:"Days"` 174 } 175 176 newt := transition{ 177 StorageClass: t.StorageClass, 178 } 179 180 if !t.IsDateNull() { 181 newt.Date = &t.Date 182 } else { 183 newt.Days = &t.Days 184 } 185 return json.Marshal(newt) 186 } 187 188 // IsDaysNull returns true if days field is null 189 func (t Transition) IsDaysNull() bool { 190 return t.Days == ExpirationDays(0) 191 } 192 193 // IsDateNull returns true if date field is null 194 func (t Transition) IsDateNull() bool { 195 return t.Date.Time.IsZero() 196 } 197 198 // IsNull returns true if no storage-class is set. 199 func (t Transition) IsNull() bool { 200 return t.StorageClass == "" 201 } 202 203 // MarshalXML is transition is non null 204 func (t Transition) MarshalXML(en *xml.Encoder, startElement xml.StartElement) error { 205 if t.IsNull() { 206 return nil 207 } 208 type transitionWrapper Transition 209 return en.EncodeElement(transitionWrapper(t), startElement) 210 } 211 212 // And And Rule for LifecycleTag, to be used in LifecycleRuleFilter 213 type And struct { 214 XMLName xml.Name `xml:"And" json:"-"` 215 Prefix string `xml:"Prefix" json:"Prefix,omitempty"` 216 Tags []Tag `xml:"Tag" json:"Tags,omitempty"` 217 } 218 219 // IsEmpty returns true if Tags field is null 220 func (a And) IsEmpty() bool { 221 return len(a.Tags) == 0 && a.Prefix == "" 222 } 223 224 // Filter will be used in selecting rule(s) for lifecycle configuration 225 type Filter struct { 226 XMLName xml.Name `xml:"Filter" json:"-"` 227 And And `xml:"And,omitempty" json:"And,omitempty"` 228 Prefix string `xml:"Prefix,omitempty" json:"Prefix,omitempty"` 229 Tag Tag `xml:"Tag,omitempty" json:"Tag,omitempty"` 230 } 231 232 // IsNull returns true if all Filter fields are empty. 233 func (f Filter) IsNull() bool { 234 return f.Tag.IsEmpty() && f.And.IsEmpty() && f.Prefix == "" 235 } 236 237 // MarshalJSON customizes json encoding by removing empty values. 238 func (f Filter) MarshalJSON() ([]byte, error) { 239 type filter struct { 240 And *And `json:"And,omitempty"` 241 Prefix string `json:"Prefix,omitempty"` 242 Tag *Tag `json:"Tag,omitempty"` 243 } 244 245 newf := filter{ 246 Prefix: f.Prefix, 247 } 248 if !f.Tag.IsEmpty() { 249 newf.Tag = &f.Tag 250 } 251 if !f.And.IsEmpty() { 252 newf.And = &f.And 253 } 254 return json.Marshal(newf) 255 } 256 257 // MarshalXML - produces the xml representation of the Filter struct 258 // only one of Prefix, And and Tag should be present in the output. 259 func (f Filter) MarshalXML(e *xml.Encoder, start xml.StartElement) error { 260 if err := e.EncodeToken(start); err != nil { 261 return err 262 } 263 264 switch { 265 case !f.And.IsEmpty(): 266 if err := e.EncodeElement(f.And, xml.StartElement{Name: xml.Name{Local: "And"}}); err != nil { 267 return err 268 } 269 case !f.Tag.IsEmpty(): 270 if err := e.EncodeElement(f.Tag, xml.StartElement{Name: xml.Name{Local: "Tag"}}); err != nil { 271 return err 272 } 273 default: 274 // Always print Prefix field when both And & Tag are empty 275 if err := e.EncodeElement(f.Prefix, xml.StartElement{Name: xml.Name{Local: "Prefix"}}); err != nil { 276 return err 277 } 278 } 279 280 return e.EncodeToken(xml.EndElement{Name: start.Name}) 281 } 282 283 // ExpirationDays is a type alias to unmarshal Days in Expiration 284 type ExpirationDays int 285 286 // MarshalXML encodes number of days to expire if it is non-zero and 287 // encodes empty string otherwise 288 func (eDays ExpirationDays) MarshalXML(e *xml.Encoder, startElement xml.StartElement) error { 289 if eDays == 0 { 290 return nil 291 } 292 return e.EncodeElement(int(eDays), startElement) 293 } 294 295 // ExpirationDate is a embedded type containing time.Time to unmarshal 296 // Date in Expiration 297 type ExpirationDate struct { 298 time.Time 299 } 300 301 // MarshalXML encodes expiration date if it is non-zero and encodes 302 // empty string otherwise 303 func (eDate ExpirationDate) MarshalXML(e *xml.Encoder, startElement xml.StartElement) error { 304 if eDate.Time.IsZero() { 305 return nil 306 } 307 return e.EncodeElement(eDate.Format(time.RFC3339), startElement) 308 } 309 310 // ExpireDeleteMarker represents value of ExpiredObjectDeleteMarker field in Expiration XML element. 311 type ExpireDeleteMarker bool 312 313 // MarshalXML encodes delete marker boolean into an XML form. 314 func (b ExpireDeleteMarker) MarshalXML(e *xml.Encoder, startElement xml.StartElement) error { 315 if !b { 316 return nil 317 } 318 type expireDeleteMarkerWrapper ExpireDeleteMarker 319 return e.EncodeElement(expireDeleteMarkerWrapper(b), startElement) 320 } 321 322 // IsEnabled returns true if the auto delete-marker expiration is enabled 323 func (b ExpireDeleteMarker) IsEnabled() bool { 324 return bool(b) 325 } 326 327 // Expiration structure - expiration details of lifecycle configuration 328 type Expiration struct { 329 XMLName xml.Name `xml:"Expiration,omitempty" json:"-"` 330 Date ExpirationDate `xml:"Date,omitempty" json:"Date,omitempty"` 331 Days ExpirationDays `xml:"Days,omitempty" json:"Days,omitempty"` 332 DeleteMarker ExpireDeleteMarker `xml:"ExpiredObjectDeleteMarker,omitempty" json:"ExpiredObjectDeleteMarker,omitempty"` 333 } 334 335 // MarshalJSON customizes json encoding by removing empty day/date specification. 336 func (e Expiration) MarshalJSON() ([]byte, error) { 337 type expiration struct { 338 Date *ExpirationDate `json:"Date,omitempty"` 339 Days *ExpirationDays `json:"Days,omitempty"` 340 DeleteMarker ExpireDeleteMarker `json:"ExpiredObjectDeleteMarker,omitempty"` 341 } 342 343 newexp := expiration{ 344 DeleteMarker: e.DeleteMarker, 345 } 346 if !e.IsDaysNull() { 347 newexp.Days = &e.Days 348 } 349 if !e.IsDateNull() { 350 newexp.Date = &e.Date 351 } 352 return json.Marshal(newexp) 353 } 354 355 // IsDaysNull returns true if days field is null 356 func (e Expiration) IsDaysNull() bool { 357 return e.Days == ExpirationDays(0) 358 } 359 360 // IsDateNull returns true if date field is null 361 func (e Expiration) IsDateNull() bool { 362 return e.Date.Time.IsZero() 363 } 364 365 // IsDeleteMarkerExpirationEnabled returns true if the auto-expiration of delete marker is enabled 366 func (e Expiration) IsDeleteMarkerExpirationEnabled() bool { 367 return e.DeleteMarker.IsEnabled() 368 } 369 370 // IsNull returns true if both date and days fields are null 371 func (e Expiration) IsNull() bool { 372 return e.IsDaysNull() && e.IsDateNull() && !e.IsDeleteMarkerExpirationEnabled() 373 } 374 375 // MarshalXML is expiration is non null 376 func (e Expiration) MarshalXML(en *xml.Encoder, startElement xml.StartElement) error { 377 if e.IsNull() { 378 return nil 379 } 380 type expirationWrapper Expiration 381 return en.EncodeElement(expirationWrapper(e), startElement) 382 } 383 384 // MarshalJSON customizes json encoding by omitting empty values 385 func (r Rule) MarshalJSON() ([]byte, error) { 386 type rule struct { 387 AbortIncompleteMultipartUpload *AbortIncompleteMultipartUpload `json:"AbortIncompleteMultipartUpload,omitempty"` 388 Expiration *Expiration `json:"Expiration,omitempty"` 389 ID string `json:"ID"` 390 RuleFilter *Filter `json:"Filter,omitempty"` 391 NoncurrentVersionExpiration *NoncurrentVersionExpiration `json:"NoncurrentVersionExpiration,omitempty"` 392 NoncurrentVersionTransition *NoncurrentVersionTransition `json:"NoncurrentVersionTransition,omitempty"` 393 Prefix string `json:"Prefix,omitempty"` 394 Status string `json:"Status"` 395 Transition *Transition `json:"Transition,omitempty"` 396 } 397 newr := rule{ 398 Prefix: r.Prefix, 399 Status: r.Status, 400 ID: r.ID, 401 } 402 403 if !r.RuleFilter.IsNull() { 404 newr.RuleFilter = &r.RuleFilter 405 } 406 if !r.AbortIncompleteMultipartUpload.IsDaysNull() { 407 newr.AbortIncompleteMultipartUpload = &r.AbortIncompleteMultipartUpload 408 } 409 if !r.Expiration.IsNull() { 410 newr.Expiration = &r.Expiration 411 } 412 if !r.Transition.IsNull() { 413 newr.Transition = &r.Transition 414 } 415 if !r.NoncurrentVersionExpiration.isNull() { 416 newr.NoncurrentVersionExpiration = &r.NoncurrentVersionExpiration 417 } 418 if !r.NoncurrentVersionTransition.isNull() { 419 newr.NoncurrentVersionTransition = &r.NoncurrentVersionTransition 420 } 421 422 return json.Marshal(newr) 423 } 424 425 // Rule represents a single rule in lifecycle configuration 426 type Rule struct { 427 XMLName xml.Name `xml:"Rule,omitempty" json:"-"` 428 AbortIncompleteMultipartUpload AbortIncompleteMultipartUpload `xml:"AbortIncompleteMultipartUpload,omitempty" json:"AbortIncompleteMultipartUpload,omitempty"` 429 Expiration Expiration `xml:"Expiration,omitempty" json:"Expiration,omitempty"` 430 ID string `xml:"ID" json:"ID"` 431 RuleFilter Filter `xml:"Filter,omitempty" json:"Filter,omitempty"` 432 NoncurrentVersionExpiration NoncurrentVersionExpiration `xml:"NoncurrentVersionExpiration,omitempty" json:"NoncurrentVersionExpiration,omitempty"` 433 NoncurrentVersionTransition NoncurrentVersionTransition `xml:"NoncurrentVersionTransition,omitempty" json:"NoncurrentVersionTransition,omitempty"` 434 Prefix string `xml:"Prefix,omitempty" json:"Prefix,omitempty"` 435 Status string `xml:"Status" json:"Status"` 436 Transition Transition `xml:"Transition,omitempty" json:"Transition,omitempty"` 437 } 438 439 // Configuration is a collection of Rule objects. 440 type Configuration struct { 441 XMLName xml.Name `xml:"LifecycleConfiguration,omitempty" json:"-"` 442 Rules []Rule `xml:"Rule"` 443 } 444 445 // Empty check if lifecycle configuration is empty 446 func (c *Configuration) Empty() bool { 447 if c == nil { 448 return true 449 } 450 return len(c.Rules) == 0 451 } 452 453 // NewConfiguration initializes a fresh lifecycle configuration 454 // for manipulation, such as setting and removing lifecycle rules 455 // and filters. 456 func NewConfiguration() *Configuration { 457 return &Configuration{} 458 }