// Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package lifecycle
import (
"bytes"
"encoding/xml"
)
// Status represents lifecycle configuration status
type Status string
// Supported status types
const (
Enabled Status = "Enabled"
Disabled Status = "Disabled"
)
// Rule - a rule for lifecycle configuration.
type Rule struct {
XMLName xml.Name `xml:"Rule"`
ID string `xml:"ID,omitempty"`
Status Status `xml:"Status"`
Filter Filter `xml:"Filter,omitempty"`
Prefix Prefix `xml:"Prefix,omitempty"`
Expiration Expiration `xml:"Expiration,omitempty"`
Transition Transition `xml:"Transition,omitempty"`
DelMarkerExpiration DelMarkerExpiration `xml:"DelMarkerExpiration,omitempty"`
// FIXME: add a type to catch unsupported AbortIncompleteMultipartUpload AbortIncompleteMultipartUpload `xml:"AbortIncompleteMultipartUpload,omitempty"`
NoncurrentVersionExpiration NoncurrentVersionExpiration `xml:"NoncurrentVersionExpiration,omitempty"`
NoncurrentVersionTransition NoncurrentVersionTransition `xml:"NoncurrentVersionTransition,omitempty"`
}
var (
errInvalidRuleID = Errorf("ID length is limited to 255 characters")
errEmptyRuleStatus = Errorf("Status should not be empty")
errInvalidRuleStatus = Errorf("Status must be set to either Enabled or Disabled")
errInvalidRuleDelMarkerExpiration = Errorf("Rule with DelMarkerExpiration cannot have tags based filtering")
)
// validateID - checks if ID is valid or not.
func (r Rule) validateID() error {
if len(r.ID) > 255 {
return errInvalidRuleID
}
return nil
}
// validateStatus - checks if status is valid or not.
func (r Rule) validateStatus() error {
// Status can't be empty
if len(r.Status) == 0 {
return errEmptyRuleStatus
}
// Status must be one of Enabled or Disabled
if r.Status != Enabled && r.Status != Disabled {
return errInvalidRuleStatus
}
return nil
}
func (r Rule) validateExpiration() error {
return r.Expiration.Validate()
}
func (r Rule) validateNoncurrentExpiration() error {
return r.NoncurrentVersionExpiration.Validate()
}
func (r Rule) validatePrefixAndFilter() error {
// In the now deprecated PutBucketLifecycle API, Rule had a mandatory Prefix element and there existed no Filter field.
// See https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutBucketLifecycle.html
// In the newer PutBucketLifecycleConfiguration API, Rule has a prefix field that is deprecated, and there exists an optional
// Filter field, and within it, an optional Prefix field.
// See https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutBucketLifecycleConfiguration.html
// A valid rule could be a pre-existing one created using the now deprecated PutBucketLifecycle.
// Or, a valid rule could also be either a pre-existing or a new rule that is created using PutBucketLifecycleConfiguration.
// Prefix validation below may check that either Rule.Prefix or Rule.Filter.Prefix exist but not both.
// Here, we assume the pre-existing rule created using PutBucketLifecycle API is already valid and won't fail the validation if Rule.Prefix is empty.
if r.Prefix.set && !r.Filter.IsEmpty() && r.Filter.Prefix.set {
return errXMLNotWellFormed
}
if r.Filter.set {
return r.Filter.Validate()
}
return nil
}
func (r Rule) validateTransition() error {
return r.Transition.Validate()
}
func (r Rule) validateNoncurrentTransition() error {
return r.NoncurrentVersionTransition.Validate()
}
// GetPrefix - a rule can either have prefix under ,
// or under . This method returns the prefix from the
// location where it is available.
func (r Rule) GetPrefix() string {
if p := r.Prefix.String(); p != "" {
return p
}
if p := r.Filter.Prefix.String(); p != "" {
return p
}
if p := r.Filter.And.Prefix.String(); p != "" {
return p
}
return ""
}
// Tags - a rule can either have tag under or under
// . This method returns all the tags from the
// rule in the format tag1=value1&tag2=value2
func (r Rule) Tags() string {
if !r.Filter.Tag.IsEmpty() {
return r.Filter.Tag.String()
}
if len(r.Filter.And.Tags) != 0 {
var buf bytes.Buffer
for _, t := range r.Filter.And.Tags {
if buf.Len() > 0 {
buf.WriteString("&")
}
buf.WriteString(t.String())
}
return buf.String()
}
return ""
}
// Validate - validates the rule element
func (r Rule) Validate() error {
if err := r.validateID(); err != nil {
return err
}
if err := r.validateStatus(); err != nil {
return err
}
if err := r.validateExpiration(); err != nil {
return err
}
if err := r.validateNoncurrentExpiration(); err != nil {
return err
}
if err := r.validatePrefixAndFilter(); err != nil {
return err
}
if err := r.validateTransition(); err != nil {
return err
}
if err := r.validateNoncurrentTransition(); err != nil {
return err
}
if (!r.Filter.Tag.IsEmpty() || len(r.Filter.And.Tags) != 0) && !r.DelMarkerExpiration.Empty() {
return errInvalidRuleDelMarkerExpiration
}
if !r.Expiration.set && !r.Transition.set && !r.NoncurrentVersionExpiration.set && !r.NoncurrentVersionTransition.set && r.DelMarkerExpiration.Empty() {
return errXMLNotWellFormed
}
return nil
}
// CloneNonTransition - returns a clone of the object containing non transition rules
func (r Rule) CloneNonTransition() Rule {
return Rule{
XMLName: r.XMLName,
ID: r.ID,
Status: r.Status,
Filter: r.Filter,
Prefix: r.Prefix,
Expiration: r.Expiration,
NoncurrentVersionExpiration: r.NoncurrentVersionExpiration,
}
}