mirror of
https://github.com/minio/minio.git
synced 2025-02-03 01:46:00 -05:00
lifcycle: Add more validation to the config (#11382)
This commit is contained in:
parent
df0c678167
commit
075c429021
@ -20,22 +20,33 @@ import (
|
|||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var errDuplicateTagKey = Errorf("Duplicate Tag Keys are not allowed")
|
||||||
|
|
||||||
// And - a tag to combine a prefix and multiple tags for lifecycle configuration rule.
|
// And - a tag to combine a prefix and multiple tags for lifecycle configuration rule.
|
||||||
type And struct {
|
type And struct {
|
||||||
XMLName xml.Name `xml:"And"`
|
XMLName xml.Name `xml:"And"`
|
||||||
Prefix string `xml:"Prefix,omitempty"`
|
Prefix Prefix `xml:"Prefix,omitempty"`
|
||||||
Tags []Tag `xml:"Tag,omitempty"`
|
Tags []Tag `xml:"Tag,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var errDuplicateTagKey = Errorf("Duplicate Tag Keys are not allowed")
|
|
||||||
|
|
||||||
// isEmpty returns true if Tags field is null
|
// isEmpty returns true if Tags field is null
|
||||||
func (a And) isEmpty() bool {
|
func (a And) isEmpty() bool {
|
||||||
return len(a.Tags) == 0 && a.Prefix == ""
|
return len(a.Tags) == 0 && !a.Prefix.set
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate - validates the And field
|
// Validate - validates the And field
|
||||||
func (a And) Validate() error {
|
func (a And) Validate() error {
|
||||||
|
emptyPrefix := !a.Prefix.set
|
||||||
|
emptyTags := len(a.Tags) == 0
|
||||||
|
|
||||||
|
if emptyPrefix && emptyTags {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if emptyPrefix && !emptyTags || !emptyPrefix && emptyTags {
|
||||||
|
return errXMLNotWellFormed
|
||||||
|
}
|
||||||
|
|
||||||
if a.ContainsDuplicateTag() {
|
if a.ContainsDuplicateTag() {
|
||||||
return errDuplicateTagKey
|
return errDuplicateTagKey
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@ package lifecycle
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
|
"io"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -27,10 +28,14 @@ var (
|
|||||||
// Filter - a filter for a lifecycle configuration Rule.
|
// Filter - a filter for a lifecycle configuration Rule.
|
||||||
type Filter struct {
|
type Filter struct {
|
||||||
XMLName xml.Name `xml:"Filter"`
|
XMLName xml.Name `xml:"Filter"`
|
||||||
Prefix string
|
|
||||||
And And
|
|
||||||
Tag Tag
|
|
||||||
|
|
||||||
|
Prefix Prefix
|
||||||
|
|
||||||
|
And And
|
||||||
|
andSet bool
|
||||||
|
|
||||||
|
Tag Tag
|
||||||
|
tagSet bool
|
||||||
// Caching tags, only once
|
// Caching tags, only once
|
||||||
cachedTags []string
|
cachedTags []string
|
||||||
}
|
}
|
||||||
@ -61,11 +66,62 @@ func (f Filter) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
|
|||||||
return e.EncodeToken(xml.EndElement{Name: start.Name})
|
return e.EncodeToken(xml.EndElement{Name: start.Name})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UnmarshalXML - decodes XML data.
|
||||||
|
func (f *Filter) UnmarshalXML(d *xml.Decoder, start xml.StartElement) (err error) {
|
||||||
|
for {
|
||||||
|
// Read tokens from the XML document in a stream.
|
||||||
|
t, err := d.Token()
|
||||||
|
if err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch se := t.(type) {
|
||||||
|
case xml.StartElement:
|
||||||
|
switch se.Name.Local {
|
||||||
|
case "Prefix":
|
||||||
|
var p Prefix
|
||||||
|
if err = d.DecodeElement(&p, &se); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
f.Prefix = p
|
||||||
|
case "And":
|
||||||
|
var and And
|
||||||
|
if err = d.DecodeElement(&and, &se); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
f.And = and
|
||||||
|
f.andSet = true
|
||||||
|
case "Tag":
|
||||||
|
var tag Tag
|
||||||
|
if err = d.DecodeElement(&tag, &se); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
f.Tag = tag
|
||||||
|
f.tagSet = true
|
||||||
|
default:
|
||||||
|
return errUnknownXMLTag
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsEmpty returns true if Filter is not specified in the XML
|
||||||
|
func (f Filter) IsEmpty() bool {
|
||||||
|
return !f.Prefix.set && !f.andSet && !f.tagSet
|
||||||
|
}
|
||||||
|
|
||||||
// Validate - validates the filter element
|
// Validate - validates the filter element
|
||||||
func (f Filter) Validate() error {
|
func (f Filter) Validate() error {
|
||||||
|
if !f.Prefix.set && !f.andSet && !f.tagSet {
|
||||||
|
return errXMLNotWellFormed
|
||||||
|
}
|
||||||
// A Filter must have exactly one of Prefix, Tag, or And specified.
|
// A Filter must have exactly one of Prefix, Tag, or And specified.
|
||||||
if !f.And.isEmpty() {
|
if !f.And.isEmpty() {
|
||||||
if f.Prefix != "" {
|
if f.Prefix.set {
|
||||||
return errInvalidFilter
|
return errInvalidFilter
|
||||||
}
|
}
|
||||||
if !f.Tag.IsEmpty() {
|
if !f.Tag.IsEmpty() {
|
||||||
@ -75,12 +131,15 @@ func (f Filter) Validate() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if f.Prefix != "" {
|
if f.Prefix.set {
|
||||||
if !f.Tag.IsEmpty() {
|
if !f.Tag.IsEmpty() {
|
||||||
return errInvalidFilter
|
return errInvalidFilter
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !f.Tag.IsEmpty() {
|
if !f.Tag.IsEmpty() {
|
||||||
|
if f.Prefix.set {
|
||||||
|
return errInvalidFilter
|
||||||
|
}
|
||||||
if err := f.Tag.Validate(); err != nil {
|
if err := f.Tag.Validate(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -35,7 +35,7 @@ func TestUnsupportedFilters(t *testing.T) {
|
|||||||
<Prefix>key-prefix</Prefix>
|
<Prefix>key-prefix</Prefix>
|
||||||
</And>
|
</And>
|
||||||
</Filter>`,
|
</Filter>`,
|
||||||
expectedErr: nil,
|
expectedErr: errXMLNotWellFormed,
|
||||||
},
|
},
|
||||||
{ // Filter with Tag tags
|
{ // Filter with Tag tags
|
||||||
inputXML: ` <Filter>
|
inputXML: ` <Filter>
|
||||||
@ -85,6 +85,7 @@ func TestUnsupportedFilters(t *testing.T) {
|
|||||||
{ // Filter with And and multiple Tag tags
|
{ // Filter with And and multiple Tag tags
|
||||||
inputXML: ` <Filter>
|
inputXML: ` <Filter>
|
||||||
<And>
|
<And>
|
||||||
|
<Prefix></Prefix>
|
||||||
<Tag>
|
<Tag>
|
||||||
<Key>key1</Key>
|
<Key>key1</Key>
|
||||||
<Value>value1</Value>
|
<Value>value1</Value>
|
||||||
|
@ -79,16 +79,16 @@ func (lc Lifecycle) HasActiveRules(prefix string, recursive bool) bool {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(prefix) > 0 && len(rule.Filter.Prefix) > 0 {
|
if len(prefix) > 0 && len(rule.GetPrefix()) > 0 {
|
||||||
if !recursive {
|
if !recursive {
|
||||||
// If not recursive, incoming prefix must be in rule prefix
|
// If not recursive, incoming prefix must be in rule prefix
|
||||||
if !strings.HasPrefix(prefix, rule.Filter.Prefix) {
|
if !strings.HasPrefix(prefix, rule.GetPrefix()) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if recursive {
|
if recursive {
|
||||||
// If recursive, we can skip this rule if it doesn't match the tested prefix.
|
// If recursive, we can skip this rule if it doesn't match the tested prefix.
|
||||||
if !strings.HasPrefix(prefix, rule.Filter.Prefix) && !strings.HasPrefix(rule.Filter.Prefix, prefix) {
|
if !strings.HasPrefix(prefix, rule.GetPrefix()) && !strings.HasPrefix(rule.GetPrefix(), prefix) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -167,7 +167,7 @@ func (lc Lifecycle) FilterActionableRules(obj ObjectOpts) []Rule {
|
|||||||
if rule.Status == Disabled {
|
if rule.Status == Disabled {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if !strings.HasPrefix(obj.Name, rule.Prefix()) {
|
if !strings.HasPrefix(obj.Name, rule.GetPrefix()) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// Indicates whether MinIO will remove a delete marker with no
|
// Indicates whether MinIO will remove a delete marker with no
|
||||||
|
@ -25,72 +25,6 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestParseAndValidateLifecycleConfig(t *testing.T) {
|
func TestParseAndValidateLifecycleConfig(t *testing.T) {
|
||||||
// Test for lifecycle config with more than 1000 rules
|
|
||||||
var manyRules []Rule
|
|
||||||
for i := 0; i < 1001; i++ {
|
|
||||||
rule := Rule{
|
|
||||||
ID: fmt.Sprintf("toManyRule%d", i),
|
|
||||||
Status: "Enabled",
|
|
||||||
Expiration: Expiration{Days: ExpirationDays(i)},
|
|
||||||
}
|
|
||||||
manyRules = append(manyRules, rule)
|
|
||||||
}
|
|
||||||
|
|
||||||
manyRuleLcConfig, err := xml.Marshal(Lifecycle{Rules: manyRules})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("Failed to marshal lifecycle config with more than 1000 rules")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test for lifecycle config with rules containing overlapping prefixes
|
|
||||||
rule1 := Rule{
|
|
||||||
ID: "rule1",
|
|
||||||
Status: "Enabled",
|
|
||||||
Expiration: Expiration{Days: ExpirationDays(3)},
|
|
||||||
Filter: Filter{
|
|
||||||
Prefix: "/a/b",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
rule2 := Rule{
|
|
||||||
ID: "rule2",
|
|
||||||
Status: "Enabled",
|
|
||||||
Expiration: Expiration{Days: ExpirationDays(3)},
|
|
||||||
Filter: Filter{
|
|
||||||
And: And{
|
|
||||||
Prefix: "/a/b/c",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
overlappingRules := []Rule{rule1, rule2}
|
|
||||||
overlappingLcConfig, err := xml.Marshal(Lifecycle{Rules: overlappingRules})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("Failed to marshal lifecycle config with rules having overlapping prefix")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test for lifecycle rules with duplicate IDs
|
|
||||||
rule3 := Rule{
|
|
||||||
ID: "duplicateID",
|
|
||||||
Status: "Enabled",
|
|
||||||
Expiration: Expiration{Days: ExpirationDays(3)},
|
|
||||||
Filter: Filter{
|
|
||||||
Prefix: "/a/b",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
rule4 := Rule{
|
|
||||||
ID: "duplicateID",
|
|
||||||
Status: "Enabled",
|
|
||||||
Expiration: Expiration{Days: ExpirationDays(4)},
|
|
||||||
Filter: Filter{
|
|
||||||
And: And{
|
|
||||||
Prefix: "/x/z",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
duplicateIDRules := []Rule{rule3, rule4}
|
|
||||||
duplicateIDLcConfig, err := xml.Marshal(Lifecycle{Rules: duplicateIDRules})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("Failed to marshal lifecycle config of rules with duplicate ID.")
|
|
||||||
}
|
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
inputConfig string
|
inputConfig string
|
||||||
expectedParsingErr error
|
expectedParsingErr error
|
||||||
@ -136,21 +70,28 @@ func TestParseAndValidateLifecycleConfig(t *testing.T) {
|
|||||||
expectedParsingErr: nil,
|
expectedParsingErr: nil,
|
||||||
expectedValidationErr: errLifecycleNoRule,
|
expectedValidationErr: errLifecycleNoRule,
|
||||||
},
|
},
|
||||||
{ // lifecycle config with more than 1000 rules
|
|
||||||
inputConfig: string(manyRuleLcConfig),
|
|
||||||
expectedParsingErr: nil,
|
|
||||||
expectedValidationErr: errLifecycleTooManyRules,
|
|
||||||
},
|
|
||||||
{ // lifecycle config with rules having overlapping prefix
|
{ // lifecycle config with rules having overlapping prefix
|
||||||
inputConfig: string(overlappingLcConfig),
|
inputConfig: `<LifecycleConfiguration><Rule><ID>rule1</ID><Status>Enabled</Status><Filter><Prefix>/a/b</Prefix></Filter><Expiration><Days>3</Days></Expiration></Rule><Rule><ID>rule2</ID><Status>Enabled</Status><Filter><And><Prefix>/a/b/c</Prefix><Tag><Key>key1</Key><Value>val1</Value></Tag></And></Filter><Expiration><Days>3</Days></Expiration></Rule></LifecycleConfiguration> `,
|
||||||
expectedParsingErr: nil,
|
expectedParsingErr: nil,
|
||||||
expectedValidationErr: nil,
|
expectedValidationErr: nil,
|
||||||
},
|
},
|
||||||
{ // lifecycle config with rules having duplicate ID
|
{ // lifecycle config with rules having duplicate ID
|
||||||
inputConfig: string(duplicateIDLcConfig),
|
inputConfig: `<LifecycleConfiguration><Rule><ID>duplicateID</ID><Status>Enabled</Status><Filter><Prefix>/a/b</Prefix></Filter><Expiration><Days>3</Days></Expiration></Rule><Rule><ID>duplicateID</ID><Status>Enabled</Status><Filter><And><Prefix>/x/z</Prefix><Tag><Key>key1</Key><Value>val1</Value></Tag></And></Filter><Expiration><Days>4</Days></Expiration></Rule></LifecycleConfiguration>`,
|
||||||
expectedParsingErr: nil,
|
expectedParsingErr: nil,
|
||||||
expectedValidationErr: errLifecycleDuplicateID,
|
expectedValidationErr: errLifecycleDuplicateID,
|
||||||
},
|
},
|
||||||
|
// Missing <Tag> in <And>
|
||||||
|
{
|
||||||
|
inputConfig: `<LifecycleConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><Rule><ID>sample-rule-2</ID><Filter><And><Prefix>/a/b/c</Prefix></And></Filter><Status>Enabled</Status><Expiration><Days>1</Days></Expiration></Rule></LifecycleConfiguration>`,
|
||||||
|
expectedParsingErr: nil,
|
||||||
|
expectedValidationErr: errXMLNotWellFormed,
|
||||||
|
},
|
||||||
|
// Legitimate lifecycle
|
||||||
|
{
|
||||||
|
inputConfig: `<LifecycleConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><Rule><ID>rule</ID><Prefix /><Status>Enabled</Status><Expiration><Days>1</Days></Expiration></Rule></LifecycleConfiguration>`,
|
||||||
|
expectedParsingErr: nil,
|
||||||
|
expectedValidationErr: nil,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, tc := range testCases {
|
for i, tc := range testCases {
|
||||||
@ -181,17 +122,17 @@ func TestMarshalLifecycleConfig(t *testing.T) {
|
|||||||
Rules: []Rule{
|
Rules: []Rule{
|
||||||
{
|
{
|
||||||
Status: "Enabled",
|
Status: "Enabled",
|
||||||
Filter: Filter{Prefix: "prefix-1"},
|
Filter: Filter{Prefix: Prefix{string: "prefix-1", set: true}},
|
||||||
Expiration: Expiration{Days: ExpirationDays(3)},
|
Expiration: Expiration{Days: ExpirationDays(3)},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Status: "Enabled",
|
Status: "Enabled",
|
||||||
Filter: Filter{Prefix: "prefix-1"},
|
Filter: Filter{Prefix: Prefix{string: "prefix-1", set: true}},
|
||||||
Expiration: Expiration{Date: ExpirationDate(midnightTS)},
|
Expiration: Expiration{Date: ExpirationDate(midnightTS)},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Status: "Enabled",
|
Status: "Enabled",
|
||||||
Filter: Filter{Prefix: "prefix-1"},
|
Filter: Filter{Prefix: Prefix{string: "prefix-1", set: true}},
|
||||||
Expiration: Expiration{Date: ExpirationDate(midnightTS)},
|
Expiration: Expiration{Date: ExpirationDate(midnightTS)},
|
||||||
NoncurrentVersionTransition: NoncurrentVersionTransition{NoncurrentDays: 2, StorageClass: "TEST"},
|
NoncurrentVersionTransition: NoncurrentVersionTransition{NoncurrentDays: 2, StorageClass: "TEST"},
|
||||||
},
|
},
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* MinIO Cloud Storage, (C) 2019 MinIO, Inc.
|
* MinIO Cloud Storage, (C) 2019-2020 MinIO, Inc.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -24,12 +24,7 @@ import (
|
|||||||
type NoncurrentVersionExpiration struct {
|
type NoncurrentVersionExpiration struct {
|
||||||
XMLName xml.Name `xml:"NoncurrentVersionExpiration"`
|
XMLName xml.Name `xml:"NoncurrentVersionExpiration"`
|
||||||
NoncurrentDays ExpirationDays `xml:"NoncurrentDays,omitempty"`
|
NoncurrentDays ExpirationDays `xml:"NoncurrentDays,omitempty"`
|
||||||
}
|
set bool
|
||||||
|
|
||||||
// NoncurrentVersionTransition - an action for lifecycle configuration rule.
|
|
||||||
type NoncurrentVersionTransition struct {
|
|
||||||
NoncurrentDays ExpirationDays `xml:"NoncurrentDays"`
|
|
||||||
StorageClass string `xml:"StorageClass"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarshalXML if non-current days not set to non zero value
|
// MarshalXML if non-current days not set to non zero value
|
||||||
@ -41,11 +36,43 @@ func (n NoncurrentVersionExpiration) MarshalXML(e *xml.Encoder, start xml.StartE
|
|||||||
return e.EncodeElement(noncurrentVersionExpirationWrapper(n), start)
|
return e.EncodeElement(noncurrentVersionExpirationWrapper(n), start)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UnmarshalXML decodes NoncurrentVersionExpiration
|
||||||
|
func (n *NoncurrentVersionExpiration) UnmarshalXML(d *xml.Decoder, startElement xml.StartElement) error {
|
||||||
|
type noncurrentVersionExpirationWrapper NoncurrentVersionExpiration
|
||||||
|
var val noncurrentVersionExpirationWrapper
|
||||||
|
err := d.DecodeElement(&val, &startElement)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*n = NoncurrentVersionExpiration(val)
|
||||||
|
n.set = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// IsDaysNull returns true if days field is null
|
// IsDaysNull returns true if days field is null
|
||||||
func (n NoncurrentVersionExpiration) IsDaysNull() bool {
|
func (n NoncurrentVersionExpiration) IsDaysNull() bool {
|
||||||
return n.NoncurrentDays == ExpirationDays(0)
|
return n.NoncurrentDays == ExpirationDays(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate returns an error with wrong value
|
||||||
|
func (n NoncurrentVersionExpiration) Validate() error {
|
||||||
|
if !n.set {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
val := int(n.NoncurrentDays)
|
||||||
|
if val <= 0 {
|
||||||
|
return errXMLNotWellFormed
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NoncurrentVersionTransition - an action for lifecycle configuration rule.
|
||||||
|
type NoncurrentVersionTransition struct {
|
||||||
|
NoncurrentDays ExpirationDays `xml:"NoncurrentDays"`
|
||||||
|
StorageClass string `xml:"StorageClass"`
|
||||||
|
set bool
|
||||||
|
}
|
||||||
|
|
||||||
// MarshalXML is extended to leave out
|
// MarshalXML is extended to leave out
|
||||||
// <NoncurrentVersionTransition></NoncurrentVersionTransition> tags
|
// <NoncurrentVersionTransition></NoncurrentVersionTransition> tags
|
||||||
func (n NoncurrentVersionTransition) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
|
func (n NoncurrentVersionTransition) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
|
||||||
@ -60,3 +87,27 @@ func (n NoncurrentVersionTransition) MarshalXML(e *xml.Encoder, start xml.StartE
|
|||||||
func (n NoncurrentVersionTransition) IsDaysNull() bool {
|
func (n NoncurrentVersionTransition) IsDaysNull() bool {
|
||||||
return n.NoncurrentDays == ExpirationDays(0)
|
return n.NoncurrentDays == ExpirationDays(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UnmarshalXML decodes NoncurrentVersionExpiration
|
||||||
|
func (n *NoncurrentVersionTransition) UnmarshalXML(d *xml.Decoder, startElement xml.StartElement) error {
|
||||||
|
type noncurrentVersionTransitionWrapper NoncurrentVersionTransition
|
||||||
|
var val noncurrentVersionTransitionWrapper
|
||||||
|
err := d.DecodeElement(&val, &startElement)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*n = NoncurrentVersionTransition(val)
|
||||||
|
n.set = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate returns an error with wrong value
|
||||||
|
func (n NoncurrentVersionTransition) Validate() error {
|
||||||
|
if !n.set {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if int(n.NoncurrentDays) <= 0 || n.StorageClass == "" {
|
||||||
|
return errXMLNotWellFormed
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
50
pkg/bucket/lifecycle/prefix.go
Normal file
50
pkg/bucket/lifecycle/prefix.go
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
* MinIO Cloud Storage, (C) 2020 MinIO, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package lifecycle
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/xml"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Prefix holds the prefix xml tag in <Rule> and <Filter>
|
||||||
|
type Prefix struct {
|
||||||
|
string
|
||||||
|
set bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalXML - decodes XML data.
|
||||||
|
func (p *Prefix) UnmarshalXML(d *xml.Decoder, start xml.StartElement) (err error) {
|
||||||
|
var s string
|
||||||
|
if err = d.DecodeElement(&s, &start); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*p = Prefix{string: s, set: true}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalXML - decodes XML data.
|
||||||
|
func (p Prefix) MarshalXML(e *xml.Encoder, startElement xml.StartElement) error {
|
||||||
|
if !p.set {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return e.EncodeElement(p.string, startElement)
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the prefix string
|
||||||
|
func (p Prefix) String() string {
|
||||||
|
return p.string
|
||||||
|
}
|
@ -38,6 +38,7 @@ type Rule struct {
|
|||||||
ID string `xml:"ID,omitempty"`
|
ID string `xml:"ID,omitempty"`
|
||||||
Status Status `xml:"Status"`
|
Status Status `xml:"Status"`
|
||||||
Filter Filter `xml:"Filter,omitempty"`
|
Filter Filter `xml:"Filter,omitempty"`
|
||||||
|
Prefix Prefix `xml:"Prefix,omitempty"`
|
||||||
Expiration Expiration `xml:"Expiration,omitempty"`
|
Expiration Expiration `xml:"Expiration,omitempty"`
|
||||||
Transition Transition `xml:"Transition,omitempty"`
|
Transition Transition `xml:"Transition,omitempty"`
|
||||||
// FIXME: add a type to catch unsupported AbortIncompleteMultipartUpload AbortIncompleteMultipartUpload `xml:"AbortIncompleteMultipartUpload,omitempty"`
|
// FIXME: add a type to catch unsupported AbortIncompleteMultipartUpload AbortIncompleteMultipartUpload `xml:"AbortIncompleteMultipartUpload,omitempty"`
|
||||||
@ -92,27 +93,44 @@ func (r Rule) validateStatus() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r Rule) validateAction() error {
|
func (r Rule) validateExpiration() error {
|
||||||
return r.Expiration.Validate()
|
return r.Expiration.Validate()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r Rule) validateFilter() error {
|
func (r Rule) validateNoncurrentExpiration() error {
|
||||||
|
return r.NoncurrentVersionExpiration.Validate()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r Rule) validatePrefixAndFilter() error {
|
||||||
|
if !r.Prefix.set && r.Filter.IsEmpty() || r.Prefix.set && !r.Filter.IsEmpty() {
|
||||||
|
return errXMLNotWellFormed
|
||||||
|
}
|
||||||
|
if !r.Prefix.set {
|
||||||
return r.Filter.Validate()
|
return r.Filter.Validate()
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (r Rule) validateTransition() error {
|
func (r Rule) validateTransition() error {
|
||||||
return r.Transition.Validate()
|
return r.Transition.Validate()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prefix - a rule can either have prefix under <filter></filter> or under
|
func (r Rule) validateNoncurrentTransition() error {
|
||||||
// <filter><and></and></filter>. This method returns the prefix from the
|
return r.NoncurrentVersionTransition.Validate()
|
||||||
// location where it is available
|
|
||||||
func (r Rule) Prefix() string {
|
|
||||||
if r.Filter.Prefix != "" {
|
|
||||||
return r.Filter.Prefix
|
|
||||||
}
|
}
|
||||||
if r.Filter.And.Prefix != "" {
|
|
||||||
return r.Filter.And.Prefix
|
// GetPrefix - a rule can either have prefix under <rule></rule>, <filter></filter>
|
||||||
|
// or under <filter><and></and></filter>. 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 ""
|
return ""
|
||||||
}
|
}
|
||||||
@ -145,14 +163,23 @@ func (r Rule) Validate() error {
|
|||||||
if err := r.validateStatus(); err != nil {
|
if err := r.validateStatus(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := r.validateAction(); err != nil {
|
if err := r.validateExpiration(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := r.validateFilter(); err != nil {
|
if err := r.validateNoncurrentExpiration(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := r.validatePrefixAndFilter(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := r.validateTransition(); err != nil {
|
if err := r.validateTransition(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if err := r.validateNoncurrentTransition(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !r.Expiration.set && !r.Transition.set && !r.NoncurrentVersionExpiration.set && !r.NoncurrentVersionTransition.set {
|
||||||
|
return errXMLNotWellFormed
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -38,6 +38,7 @@ func TestInvalidRules(t *testing.T) {
|
|||||||
{ // Rule with empty ID
|
{ // Rule with empty ID
|
||||||
inputXML: `<Rule>
|
inputXML: `<Rule>
|
||||||
<ID></ID>
|
<ID></ID>
|
||||||
|
<Filter><Prefix></Prefix></Filter>
|
||||||
<Expiration>
|
<Expiration>
|
||||||
<Days>365</Days>
|
<Days>365</Days>
|
||||||
</Expiration>
|
</Expiration>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user