mirror of
https://github.com/minio/minio.git
synced 2025-01-12 23:43:22 -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
|
Prefix string
|
||||||
And And
|
And And
|
||||||
Tag Tag
|
Tag Tag
|
||||||
|
|
||||||
|
// Caching tags, only once
|
||||||
|
cachedTags []string
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarshalXML - produces the xml representation of the Filter struct
|
// MarshalXML - produces the xml representation of the Filter struct
|
||||||
@ -84,3 +87,30 @@ func (f Filter) Validate() error {
|
|||||||
}
|
}
|
||||||
return nil
|
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,17 +104,14 @@ func (lc Lifecycle) FilterActionableRules(objName, objTags string) []Rule {
|
|||||||
if rule.Status == Disabled {
|
if rule.Status == Disabled {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if strings.HasPrefix(objName, rule.Prefix()) {
|
if !strings.HasPrefix(objName, rule.Prefix()) {
|
||||||
tags := rule.Tags()
|
continue
|
||||||
if tags != "" {
|
|
||||||
if strings.Contains(objTags, tags) {
|
|
||||||
rules = append(rules, rule)
|
|
||||||
}
|
}
|
||||||
} else {
|
tags := strings.Split(objTags, "&")
|
||||||
|
if rule.Filter.TestTags(tags) {
|
||||||
rules = append(rules, rule)
|
rules = append(rules, rule)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return rules
|
return rules
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,6 +88,18 @@ func TestParseAndValidateLifecycleConfig(t *testing.T) {
|
|||||||
expectedParsingErr: nil,
|
expectedParsingErr: nil,
|
||||||
expectedValidationErr: 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
|
{ // lifecycle config with no rules
|
||||||
inputConfig: `<LifecycleConfiguration>
|
inputConfig: `<LifecycleConfiguration>
|
||||||
</LifecycleConfiguration>`,
|
</LifecycleConfiguration>`,
|
||||||
@ -112,6 +124,11 @@ func TestParseAndValidateLifecycleConfig(t *testing.T) {
|
|||||||
if err != tc.expectedParsingErr {
|
if err != tc.expectedParsingErr {
|
||||||
t.Fatalf("%d: Expected %v during parsing but got %v", i+1, tc.expectedParsingErr, err)
|
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()
|
err = lc.Validate()
|
||||||
if err != tc.expectedValidationErr {
|
if err != tc.expectedValidationErr {
|
||||||
t.Fatalf("%d: Expected %v during parsing but got %v", i+1, tc.expectedValidationErr, err)
|
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)
|
// 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",
|
objectName: "foodir/fooobject",
|
||||||
objectTags: "tag1=value1&tag2=value2",
|
objectTags: "tag1=value1&tag2=value2",
|
||||||
objectModTime: time.Now().UTC().Add(-24 * time.Hour), // Created 1 day ago
|
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)
|
// 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",
|
objectName: "foodir/fooobject",
|
||||||
objectTags: "tag1=value1&tag2=value2",
|
objectTags: "tag1=value1&tag2=value2",
|
||||||
objectModTime: time.Now().UTC().Add(-24 * time.Hour), // Created 1 day ago
|
objectModTime: time.Now().UTC().Add(-24 * time.Hour), // Created 1 day ago
|
||||||
expectedAction: DeleteAction,
|
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)
|
// 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>`,
|
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 (
|
import (
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
|
"io"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -31,8 +32,52 @@ type Tag struct {
|
|||||||
var (
|
var (
|
||||||
errInvalidTagKey = Errorf("The TagKey you have provided is invalid")
|
errInvalidTagKey = Errorf("The TagKey you have provided is invalid")
|
||||||
errInvalidTagValue = Errorf("The TagValue 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 {
|
func (tag Tag) String() string {
|
||||||
return tag.Key + "=" + tag.Value
|
return tag.Key + "=" + tag.Value
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user