From 0a4ec5ba15eea501cd205551809e8a4c5fcea50a Mon Sep 17 00:00:00 2001 From: Copilot Date: Fri, 27 Mar 2026 23:43:23 -0700 Subject: [PATCH] s3api: extend lifecycle XML types with NoncurrentVersionExpiration, AbortIncompleteMultipartUpload Add missing S3 lifecycle rule types to the XML data model: - NoncurrentVersionExpiration with NoncurrentDays and NewerNoncurrentVersions - NoncurrentVersionTransition with NoncurrentDays and StorageClass - AbortIncompleteMultipartUpload with DaysAfterInitiation - Filter.ObjectSizeGreaterThan and ObjectSizeLessThan - And.ObjectSizeGreaterThan and ObjectSizeLessThan - Filter.UnmarshalXML to properly parse Tag, And, and size filter elements Each new type follows the existing set-field pattern for conditional XML marshaling. No behavior changes - these types are not yet wired into handlers or the lifecycle worker. --- weed/s3api/s3api_policy.go | 182 ++++++++++++++++++++++++++++++++++--- 1 file changed, 170 insertions(+), 12 deletions(-) diff --git a/weed/s3api/s3api_policy.go b/weed/s3api/s3api_policy.go index dab2e3f02..20745381e 100644 --- a/weed/s3api/s3api_policy.go +++ b/weed/s3api/s3api_policy.go @@ -22,13 +22,16 @@ type Lifecycle struct { // Rule - a rule for lifecycle configuration. type Rule struct { - XMLName xml.Name `xml:"Rule"` - ID string `xml:"ID,omitempty"` - Status ruleStatus `xml:"Status"` - Filter Filter `xml:"Filter,omitempty"` - Prefix Prefix `xml:"Prefix,omitempty"` - Expiration Expiration `xml:"Expiration,omitempty"` - Transition Transition `xml:"Transition,omitempty"` + XMLName xml.Name `xml:"Rule"` + ID string `xml:"ID,omitempty"` + Status ruleStatus `xml:"Status"` + Filter Filter `xml:"Filter,omitempty"` + Prefix Prefix `xml:"Prefix,omitempty"` + Expiration Expiration `xml:"Expiration,omitempty"` + Transition Transition `xml:"Transition,omitempty"` + NoncurrentVersionExpiration NoncurrentVersionExpiration `xml:"NoncurrentVersionExpiration,omitempty"` + NoncurrentVersionTransition NoncurrentVersionTransition `xml:"NoncurrentVersionTransition,omitempty"` + AbortIncompleteMultipartUpload AbortIncompleteMultipartUpload `xml:"AbortIncompleteMultipartUpload,omitempty"` } // Filter - a filter for a lifecycle configuration Rule. @@ -43,6 +46,9 @@ type Filter struct { Tag Tag tagSet bool + + ObjectSizeGreaterThan int64 + ObjectSizeLessThan int64 } // Prefix holds the prefix xml tag in and @@ -80,17 +86,83 @@ func (f Filter) MarshalXML(e *xml.Encoder, start xml.StartElement) error { if err := e.EncodeToken(start); err != nil { return err } - if err := e.EncodeElement(f.Prefix, xml.StartElement{Name: xml.Name{Local: "Prefix"}}); err != nil { - return err + if f.andSet { + if err := e.EncodeElement(f.And, xml.StartElement{Name: xml.Name{Local: "And"}}); err != nil { + return err + } + } else if f.tagSet { + if err := e.EncodeElement(f.Tag, xml.StartElement{Name: xml.Name{Local: "Tag"}}); err != nil { + return err + } + } else { + if err := e.EncodeElement(f.Prefix, xml.StartElement{Name: xml.Name{Local: "Prefix"}}); err != nil { + return err + } + } + if f.ObjectSizeGreaterThan > 0 { + if err := e.EncodeElement(f.ObjectSizeGreaterThan, xml.StartElement{Name: xml.Name{Local: "ObjectSizeGreaterThan"}}); err != nil { + return err + } + } + if f.ObjectSizeLessThan > 0 { + if err := e.EncodeElement(f.ObjectSizeLessThan, xml.StartElement{Name: xml.Name{Local: "ObjectSizeLessThan"}}); err != nil { + return err + } } return e.EncodeToken(xml.EndElement{Name: start.Name}) } +// UnmarshalXML decodes Filter from XML, handling all child elements. +func (f *Filter) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + f.set = true + for { + tok, err := d.Token() + if err != nil { + return err + } + switch t := tok.(type) { + case xml.StartElement: + switch t.Name.Local { + case "Prefix": + if err := d.DecodeElement(&f.Prefix, &t); err != nil { + return err + } + case "Tag": + f.tagSet = true + if err := d.DecodeElement(&f.Tag, &t); err != nil { + return err + } + case "And": + f.andSet = true + if err := d.DecodeElement(&f.And, &t); err != nil { + return err + } + case "ObjectSizeGreaterThan": + if err := d.DecodeElement(&f.ObjectSizeGreaterThan, &t); err != nil { + return err + } + case "ObjectSizeLessThan": + if err := d.DecodeElement(&f.ObjectSizeLessThan, &t); err != nil { + return err + } + default: + if err := d.Skip(); err != nil { + return err + } + } + case xml.EndElement: + return nil + } + } +} + // And - a tag to combine a prefix and multiple tags for lifecycle configuration rule. type And struct { - XMLName xml.Name `xml:"And"` - Prefix Prefix `xml:"Prefix,omitempty"` - Tags []Tag `xml:"Tag,omitempty"` + XMLName xml.Name `xml:"And"` + Prefix Prefix `xml:"Prefix,omitempty"` + Tags []Tag `xml:"Tag,omitempty"` + ObjectSizeGreaterThan int64 `xml:"ObjectSizeGreaterThan,omitempty"` + ObjectSizeLessThan int64 `xml:"ObjectSizeLessThan,omitempty"` } // Expiration - expiration actions for a rule in lifecycle configuration. @@ -162,3 +234,89 @@ func (t Transition) MarshalXML(enc *xml.Encoder, start xml.StartElement) error { // TransitionDays is a type alias to unmarshal Days in Transition type TransitionDays int + +// NoncurrentVersionExpiration - expiration actions for non-current object versions. +type NoncurrentVersionExpiration struct { + XMLName xml.Name `xml:"NoncurrentVersionExpiration"` + NoncurrentDays int `xml:"NoncurrentDays,omitempty"` + NewerNoncurrentVersions int `xml:"NewerNoncurrentVersions,omitempty"` + + set bool +} + +// MarshalXML encodes NoncurrentVersionExpiration field into an XML form. +func (n NoncurrentVersionExpiration) MarshalXML(enc *xml.Encoder, start xml.StartElement) error { + if !n.set { + return nil + } + type wrapper NoncurrentVersionExpiration + return enc.EncodeElement(wrapper(n), start) +} + +func (n *NoncurrentVersionExpiration) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + type wrapper NoncurrentVersionExpiration + var w wrapper + if err := d.DecodeElement(&w, &start); err != nil { + return err + } + *n = NoncurrentVersionExpiration(w) + n.set = true + return nil +} + +// NoncurrentVersionTransition - transition actions for non-current object versions. +type NoncurrentVersionTransition struct { + XMLName xml.Name `xml:"NoncurrentVersionTransition"` + NoncurrentDays int `xml:"NoncurrentDays,omitempty"` + StorageClass string `xml:"StorageClass,omitempty"` + + set bool +} + +// MarshalXML encodes NoncurrentVersionTransition field into an XML form. +func (n NoncurrentVersionTransition) MarshalXML(enc *xml.Encoder, start xml.StartElement) error { + if !n.set { + return nil + } + type wrapper NoncurrentVersionTransition + return enc.EncodeElement(wrapper(n), start) +} + +func (n *NoncurrentVersionTransition) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + type wrapper NoncurrentVersionTransition + var w wrapper + if err := d.DecodeElement(&w, &start); err != nil { + return err + } + *n = NoncurrentVersionTransition(w) + n.set = true + return nil +} + +// AbortIncompleteMultipartUpload - abort action for incomplete multipart uploads. +type AbortIncompleteMultipartUpload struct { + XMLName xml.Name `xml:"AbortIncompleteMultipartUpload"` + DaysAfterInitiation int `xml:"DaysAfterInitiation,omitempty"` + + set bool +} + +// MarshalXML encodes AbortIncompleteMultipartUpload field into an XML form. +func (a AbortIncompleteMultipartUpload) MarshalXML(enc *xml.Encoder, start xml.StartElement) error { + if !a.set { + return nil + } + type wrapper AbortIncompleteMultipartUpload + return enc.EncodeElement(wrapper(a), start) +} + +func (a *AbortIncompleteMultipartUpload) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + type wrapper AbortIncompleteMultipartUpload + var w wrapper + if err := d.DecodeElement(&w, &start); err != nil { + return err + } + *a = AbortIncompleteMultipartUpload(w) + a.set = true + return nil +}