mirror of
https://github.com/minio/minio.git
synced 2024-12-24 22:25:54 -05:00
lifecycle: Consider multiple tags filtering (#9775)
* lifecycle: Consider multiple tags filtering * lifecycle: Disallow duplicated Key/Value xml in <Tag>
This commit is contained in:
parent
4dd07e5763
commit
e906b511e9
@ -30,6 +30,9 @@ type Filter struct {
|
||||
Prefix string
|
||||
And And
|
||||
Tag Tag
|
||||
|
||||
// Caching tags, only once
|
||||
cachedTags []string
|
||||
}
|
||||
|
||||
// MarshalXML - produces the xml representation of the Filter struct
|
||||
@ -84,3 +87,30 @@ func (f Filter) Validate() error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// TestTags tests if the object tags satisfy the Filter tags requirement,
|
||||
// it returns true if there is no tags in the underlying Filter.
|
||||
func (f Filter) TestTags(tags []string) bool {
|
||||
if f.cachedTags == nil {
|
||||
tags := make([]string, 0)
|
||||
for _, t := range append(f.And.Tags, f.Tag) {
|
||||
if !t.IsEmpty() {
|
||||
tags = append(tags, t.String())
|
||||
}
|
||||
}
|
||||
f.cachedTags = tags
|
||||
}
|
||||
for _, ct := range f.cachedTags {
|
||||
foundTag := false
|
||||
for _, t := range tags {
|
||||
if ct == t {
|
||||
foundTag = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !foundTag {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
@ -104,15 +104,12 @@ func (lc Lifecycle) FilterActionableRules(objName, objTags string) []Rule {
|
||||
if rule.Status == Disabled {
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(objName, rule.Prefix()) {
|
||||
tags := rule.Tags()
|
||||
if tags != "" {
|
||||
if strings.Contains(objTags, tags) {
|
||||
rules = append(rules, rule)
|
||||
}
|
||||
} else {
|
||||
rules = append(rules, rule)
|
||||
}
|
||||
if !strings.HasPrefix(objName, rule.Prefix()) {
|
||||
continue
|
||||
}
|
||||
tags := strings.Split(objTags, "&")
|
||||
if rule.Filter.TestTags(tags) {
|
||||
rules = append(rules, rule)
|
||||
}
|
||||
}
|
||||
return rules
|
||||
|
@ -88,6 +88,18 @@ func TestParseAndValidateLifecycleConfig(t *testing.T) {
|
||||
expectedParsingErr: nil,
|
||||
expectedValidationErr: nil,
|
||||
},
|
||||
{ // Valid lifecycle config
|
||||
inputConfig: `<LifecycleConfiguration>
|
||||
<Rule>
|
||||
<Filter>
|
||||
<And><Tag><Key>key1</Key><Value>val1</Value><Key>key2</Key><Value>val2</Value></Tag></And>
|
||||
</Filter>
|
||||
<Expiration><Days>3</Days></Expiration>
|
||||
</Rule>
|
||||
</LifecycleConfiguration>`,
|
||||
expectedParsingErr: errDuplicatedXMLTag,
|
||||
expectedValidationErr: nil,
|
||||
},
|
||||
{ // lifecycle config with no rules
|
||||
inputConfig: `<LifecycleConfiguration>
|
||||
</LifecycleConfiguration>`,
|
||||
@ -112,6 +124,11 @@ func TestParseAndValidateLifecycleConfig(t *testing.T) {
|
||||
if err != tc.expectedParsingErr {
|
||||
t.Fatalf("%d: Expected %v during parsing but got %v", i+1, tc.expectedParsingErr, err)
|
||||
}
|
||||
if tc.expectedParsingErr != nil {
|
||||
// We already expect a parsing error,
|
||||
// no need to continue this test.
|
||||
return
|
||||
}
|
||||
err = lc.Validate()
|
||||
if err != tc.expectedValidationErr {
|
||||
t.Fatalf("%d: Expected %v during parsing but got %v", i+1, tc.expectedValidationErr, err)
|
||||
@ -268,7 +285,7 @@ func TestComputeActions(t *testing.T) {
|
||||
},
|
||||
// Should remove (Multiple Rules, Tags match)
|
||||
{
|
||||
inputConfig: `<LifecycleConfiguration><Rule><Filter><And><Prefix>foodir/</Prefix><Tag><Key>tag1</Key><Value>value1</Value><Key>tag2</Key><Value>value2</Value></Tag></And></Filter><Status>Enabled</Status><Expiration><Date>` + time.Now().Truncate(24*time.Hour).UTC().Add(-24*time.Hour).Format(time.RFC3339) + `</Date></Expiration></Rule><Rule><Filter><And><Prefix>abc/</Prefix><Tag><Key>tag2</Key><Value>value</Value></Tag></And></Filter><Status>Enabled</Status><Expiration><Date>` + time.Now().Truncate(24*time.Hour).UTC().Add(-24*time.Hour).Format(time.RFC3339) + `</Date></Expiration></Rule></LifecycleConfiguration>`,
|
||||
inputConfig: `<LifecycleConfiguration><Rule><Filter><And><Prefix>foodir/</Prefix><Tag><Key>tag1</Key><Value>value1</Value></Tag><Tag><Key>tag2</Key><Value>value2</Value></Tag></And></Filter><Status>Enabled</Status><Expiration><Date>` + time.Now().Truncate(24*time.Hour).UTC().Add(-24*time.Hour).Format(time.RFC3339) + `</Date></Expiration></Rule><Rule><Filter><And><Prefix>abc/</Prefix><Tag><Key>tag2</Key><Value>value</Value></Tag></And></Filter><Status>Enabled</Status><Expiration><Date>` + time.Now().Truncate(24*time.Hour).UTC().Add(-24*time.Hour).Format(time.RFC3339) + `</Date></Expiration></Rule></LifecycleConfiguration>`,
|
||||
objectName: "foodir/fooobject",
|
||||
objectTags: "tag1=value1&tag2=value2",
|
||||
objectModTime: time.Now().UTC().Add(-24 * time.Hour), // Created 1 day ago
|
||||
@ -276,12 +293,21 @@ func TestComputeActions(t *testing.T) {
|
||||
},
|
||||
// Should remove (Tags match)
|
||||
{
|
||||
inputConfig: `<LifecycleConfiguration><Rule><Filter><And><Prefix>foodir/</Prefix><Tag><Key>tag1</Key><Value>value1</Value><Key>tag2</Key><Value>value2</Value></Tag></And></Filter><Status>Enabled</Status><Expiration><Date>` + time.Now().Truncate(24*time.Hour).UTC().Add(-24*time.Hour).Format(time.RFC3339) + `</Date></Expiration></Rule></LifecycleConfiguration>`,
|
||||
inputConfig: `<LifecycleConfiguration><Rule><Filter><And><Prefix>foodir/</Prefix><Tag><Key>tag1</Key><Value>value1</Value></Tag><Tag><Key>tag2</Key><Value>value2</Value></Tag></And></Filter><Status>Enabled</Status><Expiration><Date>` + time.Now().Truncate(24*time.Hour).UTC().Add(-24*time.Hour).Format(time.RFC3339) + `</Date></Expiration></Rule></LifecycleConfiguration>`,
|
||||
objectName: "foodir/fooobject",
|
||||
objectTags: "tag1=value1&tag2=value2",
|
||||
objectModTime: time.Now().UTC().Add(-24 * time.Hour), // Created 1 day ago
|
||||
expectedAction: DeleteAction,
|
||||
},
|
||||
// Should remove (Tags match with inverted order)
|
||||
{
|
||||
inputConfig: `<LifecycleConfiguration><Rule><Filter><And><Tag><Key>factory</Key><Value>true</Value></Tag><Tag><Key>storeforever</Key><Value>false</Value></Tag></And></Filter><Status>Enabled</Status><Expiration><Date>` + time.Now().Truncate(24*time.Hour).UTC().Add(-24*time.Hour).Format(time.RFC3339) + `</Date></Expiration></Rule></LifecycleConfiguration>`,
|
||||
objectName: "fooobject",
|
||||
objectTags: "storeforever=false&factory=true",
|
||||
objectModTime: time.Now().UTC().Add(-24 * time.Hour), // Created 1 day ago
|
||||
expectedAction: DeleteAction,
|
||||
},
|
||||
|
||||
// Should not remove (Tags don't match)
|
||||
{
|
||||
inputConfig: `<LifecycleConfiguration><Rule><Filter><And><Prefix>foodir/</Prefix><Tag><Key>tag</Key><Value>value1</Value></Tag></And></Filter><Status>Enabled</Status><Expiration><Date>` + time.Now().Truncate(24*time.Hour).UTC().Add(-24*time.Hour).Format(time.RFC3339) + `</Date></Expiration></Rule></LifecycleConfiguration>`,
|
||||
|
@ -18,6 +18,7 @@ package lifecycle
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"io"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
@ -31,8 +32,52 @@ type Tag struct {
|
||||
var (
|
||||
errInvalidTagKey = Errorf("The TagKey you have provided is invalid")
|
||||
errInvalidTagValue = Errorf("The TagValue you have provided is invalid")
|
||||
|
||||
errDuplicatedXMLTag = Errorf("duplicated XML Tag")
|
||||
errUnknownXMLTag = Errorf("unknown XML Tag")
|
||||
)
|
||||
|
||||
// UnmarshalXML - decodes XML data.
|
||||
func (tag *Tag) UnmarshalXML(d *xml.Decoder, start xml.StartElement) (err error) {
|
||||
var keyAlreadyParsed, valueAlreadyParsed bool
|
||||
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:
|
||||
var s string
|
||||
if err = d.DecodeElement(&s, &se); err != nil {
|
||||
return err
|
||||
}
|
||||
switch se.Name.Local {
|
||||
case "Key":
|
||||
if keyAlreadyParsed {
|
||||
return errDuplicatedXMLTag
|
||||
}
|
||||
tag.Key = s
|
||||
keyAlreadyParsed = true
|
||||
case "Value":
|
||||
if valueAlreadyParsed {
|
||||
return errDuplicatedXMLTag
|
||||
}
|
||||
tag.Value = s
|
||||
valueAlreadyParsed = true
|
||||
default:
|
||||
return errUnknownXMLTag
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tag Tag) String() string {
|
||||
return tag.Key + "=" + tag.Value
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user