storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/pkg/bucket/replication/rule.go (about)

     1  /*
     2   * MinIO Cloud Storage, (C) 2020 MinIO, Inc.
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *     http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  
    17  package replication
    18  
    19  import (
    20  	"bytes"
    21  	"encoding/xml"
    22  )
    23  
    24  // Status represents Enabled/Disabled status
    25  type Status string
    26  
    27  // Supported status types
    28  const (
    29  	Enabled  Status = "Enabled"
    30  	Disabled Status = "Disabled"
    31  )
    32  
    33  // DeleteMarkerReplication - whether delete markers are replicated - https://docs.aws.amazon.com/AmazonS3/latest/dev/replication-add-config.html
    34  type DeleteMarkerReplication struct {
    35  	Status Status `xml:"Status"` // should be set to "Disabled" by default
    36  }
    37  
    38  // IsEmpty returns true if DeleteMarkerReplication is not set
    39  func (d DeleteMarkerReplication) IsEmpty() bool {
    40  	return len(d.Status) == 0
    41  }
    42  
    43  // Validate validates whether the status is disabled.
    44  func (d DeleteMarkerReplication) Validate() error {
    45  	if d.IsEmpty() {
    46  		return errDeleteMarkerReplicationMissing
    47  	}
    48  	if d.Status != Disabled && d.Status != Enabled {
    49  		return errInvalidDeleteMarkerReplicationStatus
    50  	}
    51  	return nil
    52  }
    53  
    54  // DeleteReplication - whether versioned deletes are replicated - this is a MinIO only
    55  // extension.
    56  type DeleteReplication struct {
    57  	Status Status `xml:"Status"` // should be set to "Disabled" by default
    58  }
    59  
    60  // IsEmpty returns true if DeleteReplication is not set
    61  func (d DeleteReplication) IsEmpty() bool {
    62  	return len(d.Status) == 0
    63  }
    64  
    65  // Validate validates whether the status is disabled.
    66  func (d DeleteReplication) Validate() error {
    67  	if d.IsEmpty() {
    68  		return errDeleteReplicationMissing
    69  	}
    70  	if d.Status != Disabled && d.Status != Enabled {
    71  		return errInvalidDeleteReplicationStatus
    72  	}
    73  	return nil
    74  }
    75  
    76  // UnmarshalXML - decodes XML data.
    77  func (d *DeleteReplication) UnmarshalXML(dec *xml.Decoder, start xml.StartElement) (err error) {
    78  	// Make subtype to avoid recursive UnmarshalXML().
    79  	type deleteReplication DeleteReplication
    80  	drep := deleteReplication{}
    81  
    82  	if err := dec.DecodeElement(&drep, &start); err != nil {
    83  		return err
    84  	}
    85  	if len(drep.Status) == 0 {
    86  		drep.Status = Disabled
    87  	}
    88  	d.Status = drep.Status
    89  	return nil
    90  }
    91  
    92  // Rule - a rule for replication configuration.
    93  type Rule struct {
    94  	XMLName                 xml.Name                `xml:"Rule" json:"Rule"`
    95  	ID                      string                  `xml:"ID,omitempty" json:"ID,omitempty"`
    96  	Status                  Status                  `xml:"Status" json:"Status"`
    97  	Priority                int                     `xml:"Priority" json:"Priority"`
    98  	DeleteMarkerReplication DeleteMarkerReplication `xml:"DeleteMarkerReplication" json:"DeleteMarkerReplication"`
    99  	// MinIO extension to replicate versioned deletes
   100  	DeleteReplication DeleteReplication `xml:"DeleteReplication" json:"DeleteReplication"`
   101  	Destination       Destination       `xml:"Destination" json:"Destination"`
   102  	Filter            Filter            `xml:"Filter" json:"Filter"`
   103  }
   104  
   105  var (
   106  	errInvalidRuleID                        = Errorf("ID must be less than 255 characters")
   107  	errEmptyRuleStatus                      = Errorf("Status should not be empty")
   108  	errInvalidRuleStatus                    = Errorf("Status must be set to either Enabled or Disabled")
   109  	errDeleteMarkerReplicationMissing       = Errorf("DeleteMarkerReplication must be specified")
   110  	errPriorityMissing                      = Errorf("Priority must be specified")
   111  	errInvalidDeleteMarkerReplicationStatus = Errorf("Delete marker replication status is invalid")
   112  	errDestinationSourceIdentical           = Errorf("Destination bucket cannot be the same as the source bucket.")
   113  	errDeleteReplicationMissing             = Errorf("Delete replication must be specified")
   114  	errInvalidDeleteReplicationStatus       = Errorf("Delete replication is either enable|disable")
   115  )
   116  
   117  // validateID - checks if ID is valid or not.
   118  func (r Rule) validateID() error {
   119  	// cannot be longer than 255 characters
   120  	if len(r.ID) > 255 {
   121  		return errInvalidRuleID
   122  	}
   123  	return nil
   124  }
   125  
   126  // validateStatus - checks if status is valid or not.
   127  func (r Rule) validateStatus() error {
   128  	// Status can't be empty
   129  	if len(r.Status) == 0 {
   130  		return errEmptyRuleStatus
   131  	}
   132  
   133  	// Status must be one of Enabled or Disabled
   134  	if r.Status != Enabled && r.Status != Disabled {
   135  		return errInvalidRuleStatus
   136  	}
   137  	return nil
   138  }
   139  
   140  func (r Rule) validateFilter() error {
   141  	if err := r.Filter.Validate(); err != nil {
   142  		return err
   143  	}
   144  	return nil
   145  }
   146  
   147  // Prefix - a rule can either have prefix under <filter></filter> or under
   148  // <filter><and></and></filter>. This method returns the prefix from the
   149  // location where it is available
   150  func (r Rule) Prefix() string {
   151  	if r.Filter.Prefix != "" {
   152  		return r.Filter.Prefix
   153  	}
   154  	return r.Filter.And.Prefix
   155  }
   156  
   157  // Tags - a rule can either have tag under <filter></filter> or under
   158  // <filter><and></and></filter>. This method returns all the tags from the
   159  // rule in the format tag1=value1&tag2=value2
   160  func (r Rule) Tags() string {
   161  	if !r.Filter.Tag.IsEmpty() {
   162  		return r.Filter.Tag.String()
   163  	}
   164  	if len(r.Filter.And.Tags) != 0 {
   165  		var buf bytes.Buffer
   166  		for _, t := range r.Filter.And.Tags {
   167  			if buf.Len() > 0 {
   168  				buf.WriteString("&")
   169  			}
   170  			buf.WriteString(t.String())
   171  		}
   172  		return buf.String()
   173  	}
   174  	return ""
   175  }
   176  
   177  // Validate - validates the rule element
   178  func (r Rule) Validate(bucket string, sameTarget bool) error {
   179  	if err := r.validateID(); err != nil {
   180  		return err
   181  	}
   182  	if err := r.validateStatus(); err != nil {
   183  		return err
   184  	}
   185  	if err := r.validateFilter(); err != nil {
   186  		return err
   187  	}
   188  	if err := r.DeleteMarkerReplication.Validate(); err != nil {
   189  		return err
   190  	}
   191  	if err := r.DeleteReplication.Validate(); err != nil {
   192  		return err
   193  	}
   194  	if r.Priority < 0 {
   195  		return errPriorityMissing
   196  	}
   197  	if r.Destination.Bucket == bucket && sameTarget {
   198  		return errDestinationSourceIdentical
   199  	}
   200  	return nil
   201  }