lifecycle: Consider multiple tags filtering (#9775)

* lifecycle: Consider multiple tags filtering
* lifecycle: Disallow duplicated Key/Value xml in <Tag>
This commit is contained in:
Anis Elleuch 2020-06-05 18:30:10 +01:00 committed by GitHub
parent 4dd07e5763
commit e906b511e9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 109 additions and 11 deletions

View File

@ -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
}

View File

@ -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

View File

@ -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>`,

View File

@ -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
}