mirror of
https://github.com/minio/minio.git
synced 2025-11-07 12:52:58 -05:00
Support bucket versioning (#9377)
- Implement a new xl.json 2.0.0 format to support, this moves the entire marshaling logic to POSIX layer, top layer always consumes a common FileInfo construct which simplifies the metadata reads. - Implement list object versions - Migrate to siphash from crchash for new deployments for object placements. Fixes #2111
This commit is contained in:
@@ -22,10 +22,11 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
errLifecycleInvalidDate = Errorf("Date must be provided in ISO 8601 format")
|
||||
errLifecycleInvalidDays = Errorf("Days must be positive integer when used with Expiration")
|
||||
errLifecycleInvalidExpiration = Errorf("At least one of Days or Date should be present inside Expiration")
|
||||
errLifecycleDateNotMidnight = Errorf("'Date' must be at midnight GMT")
|
||||
errLifecycleInvalidDate = Errorf("Date must be provided in ISO 8601 format")
|
||||
errLifecycleInvalidDays = Errorf("Days must be positive integer when used with Expiration")
|
||||
errLifecycleInvalidExpiration = Errorf("At least one of Days or Date should be present inside Expiration")
|
||||
errLifecycleInvalidDeleteMarker = Errorf("Delete marker cannot be specified with Days or Date in a Lifecycle Expiration Policy")
|
||||
errLifecycleDateNotMidnight = Errorf("'Date' must be at midnight GMT")
|
||||
)
|
||||
|
||||
// ExpirationDays is a type alias to unmarshal Days in Expiration
|
||||
@@ -96,17 +97,49 @@ func (eDate *ExpirationDate) MarshalXML(e *xml.Encoder, startElement xml.StartEl
|
||||
return e.EncodeElement(eDate.Format(time.RFC3339), startElement)
|
||||
}
|
||||
|
||||
// ExpireDeleteMarker represents value of ExpiredObjectDeleteMarker field in Expiration XML element.
|
||||
type ExpireDeleteMarker bool
|
||||
|
||||
// Expiration - expiration actions for a rule in lifecycle configuration.
|
||||
type Expiration struct {
|
||||
XMLName xml.Name `xml:"Expiration"`
|
||||
Days ExpirationDays `xml:"Days,omitempty"`
|
||||
Date ExpirationDate `xml:"Date,omitempty"`
|
||||
XMLName xml.Name `xml:"Expiration"`
|
||||
Days ExpirationDays `xml:"Days,omitempty"`
|
||||
Date ExpirationDate `xml:"Date,omitempty"`
|
||||
DeleteMarker ExpireDeleteMarker `xml:"ExpiredObjectDeleteMarker,omitempty"`
|
||||
}
|
||||
|
||||
// UnmarshalXML parses delete marker and validates if it is set.
|
||||
func (b *ExpireDeleteMarker) UnmarshalXML(d *xml.Decoder, startElement xml.StartElement) error {
|
||||
if !*b {
|
||||
return nil
|
||||
}
|
||||
var deleteMarker bool
|
||||
err := d.DecodeElement(&deleteMarker, &startElement)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*b = ExpireDeleteMarker(deleteMarker)
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalXML encodes delete marker boolean into an XML form.
|
||||
func (b *ExpireDeleteMarker) MarshalXML(e *xml.Encoder, startElement xml.StartElement) error {
|
||||
if !*b {
|
||||
return nil
|
||||
}
|
||||
return e.EncodeElement(*b, startElement)
|
||||
}
|
||||
|
||||
// Validate - validates the "Expiration" element
|
||||
func (e Expiration) Validate() error {
|
||||
// DeleteMarker cannot be specified if date or dates are specified.
|
||||
if (!e.IsDateNull() || !e.IsDateNull()) && bool(e.DeleteMarker) {
|
||||
return errLifecycleInvalidDeleteMarker
|
||||
}
|
||||
|
||||
// Neither expiration days or date is specified
|
||||
if e.IsDaysNull() && e.IsDateNull() {
|
||||
// if delete marker is false one of them should be specified
|
||||
if !bool(e.DeleteMarker) && e.IsDaysNull() && e.IsDateNull() {
|
||||
return errLifecycleInvalidExpiration
|
||||
}
|
||||
|
||||
@@ -114,6 +147,7 @@ func (e Expiration) Validate() error {
|
||||
if !e.IsDaysNull() && !e.IsDateNull() {
|
||||
return errLifecycleInvalidExpiration
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -132,8 +132,8 @@ func (lc Lifecycle) Validate() error {
|
||||
|
||||
// FilterActionableRules returns the rules actions that need to be executed
|
||||
// after evaluating prefix/tag filtering
|
||||
func (lc Lifecycle) FilterActionableRules(objName, objTags string) []Rule {
|
||||
if objName == "" {
|
||||
func (lc Lifecycle) FilterActionableRules(obj ObjectOpts) []Rule {
|
||||
if obj.Name == "" {
|
||||
return nil
|
||||
}
|
||||
var rules []Rule
|
||||
@@ -141,30 +141,86 @@ func (lc Lifecycle) FilterActionableRules(objName, objTags string) []Rule {
|
||||
if rule.Status == Disabled {
|
||||
continue
|
||||
}
|
||||
if !strings.HasPrefix(objName, rule.Prefix()) {
|
||||
if !strings.HasPrefix(obj.Name, rule.Prefix()) {
|
||||
continue
|
||||
}
|
||||
tags := strings.Split(objTags, "&")
|
||||
if rule.Filter.TestTags(tags) {
|
||||
// Indicates whether MinIO will remove a delete marker with no
|
||||
// noncurrent versions. If set to true, the delete marker will
|
||||
// be expired; if set to false the policy takes no action. This
|
||||
// cannot be specified with Days or Date in a Lifecycle
|
||||
// Expiration Policy.
|
||||
if rule.Expiration.DeleteMarker {
|
||||
rules = append(rules, rule)
|
||||
continue
|
||||
}
|
||||
// The NoncurrentVersionExpiration action requests MinIO to expire
|
||||
// noncurrent versions of objects 100 days after the objects become
|
||||
// noncurrent.
|
||||
if !rule.NoncurrentVersionExpiration.IsDaysNull() {
|
||||
rules = append(rules, rule)
|
||||
continue
|
||||
}
|
||||
if rule.Filter.TestTags(strings.Split(obj.UserTags, "&")) {
|
||||
rules = append(rules, rule)
|
||||
}
|
||||
}
|
||||
return rules
|
||||
}
|
||||
|
||||
// ObjectOpts provides information to deduce the lifecycle actions
|
||||
// which can be triggered on the resultant object.
|
||||
type ObjectOpts struct {
|
||||
Name string
|
||||
UserTags string
|
||||
ModTime time.Time
|
||||
VersionID string
|
||||
IsLatest bool
|
||||
DeleteMarker bool
|
||||
}
|
||||
|
||||
// ComputeAction returns the action to perform by evaluating all lifecycle rules
|
||||
// against the object name and its modification time.
|
||||
func (lc Lifecycle) ComputeAction(objName, objTags string, modTime time.Time) (action Action) {
|
||||
action = NoneAction
|
||||
if modTime.IsZero() {
|
||||
return
|
||||
func (lc Lifecycle) ComputeAction(obj ObjectOpts) Action {
|
||||
var action = NoneAction
|
||||
if obj.ModTime.IsZero() {
|
||||
return action
|
||||
}
|
||||
|
||||
_, expiryTime := lc.PredictExpiryTime(objName, modTime, objTags)
|
||||
if !expiryTime.IsZero() && time.Now().After(expiryTime) {
|
||||
return DeleteAction
|
||||
for _, rule := range lc.FilterActionableRules(obj) {
|
||||
if obj.DeleteMarker && obj.IsLatest && bool(rule.Expiration.DeleteMarker) {
|
||||
// Indicates whether MinIO will remove a delete marker with no noncurrent versions.
|
||||
// Only latest marker is removed. If set to true, the delete marker will be expired;
|
||||
// if set to false the policy takes no action. This cannot be specified with Days or
|
||||
// Date in a Lifecycle Expiration Policy.
|
||||
return DeleteAction
|
||||
}
|
||||
|
||||
if !rule.NoncurrentVersionExpiration.IsDaysNull() {
|
||||
if obj.VersionID != "" && !obj.IsLatest {
|
||||
// Non current versions should be deleted.
|
||||
if time.Now().After(expectedExpiryTime(obj.ModTime, rule.NoncurrentVersionExpiration.NoncurrentDays)) {
|
||||
return DeleteAction
|
||||
}
|
||||
return NoneAction
|
||||
}
|
||||
return NoneAction
|
||||
}
|
||||
|
||||
// All other expiration only applies to latest versions.
|
||||
if obj.IsLatest {
|
||||
switch {
|
||||
case !rule.Expiration.IsDateNull():
|
||||
if time.Now().UTC().After(rule.Expiration.Date.Time) {
|
||||
action = DeleteAction
|
||||
}
|
||||
case !rule.Expiration.IsDaysNull():
|
||||
if time.Now().UTC().After(expectedExpiryTime(obj.ModTime, rule.Expiration.Days)) {
|
||||
action = DeleteAction
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
return action
|
||||
}
|
||||
|
||||
// expectedExpiryTime calculates the expiry date/time based on a object modtime.
|
||||
@@ -179,13 +235,22 @@ func expectedExpiryTime(modTime time.Time, days ExpirationDays) time.Time {
|
||||
|
||||
// PredictExpiryTime returns the expiry date/time of a given object
|
||||
// after evaluting the current lifecycle document.
|
||||
func (lc Lifecycle) PredictExpiryTime(objName string, modTime time.Time, objTags string) (string, time.Time) {
|
||||
func (lc Lifecycle) PredictExpiryTime(obj ObjectOpts) (string, time.Time) {
|
||||
if obj.DeleteMarker {
|
||||
// We don't need to send any x-amz-expiration for delete marker.
|
||||
return "", time.Time{}
|
||||
}
|
||||
|
||||
var finalExpiryDate time.Time
|
||||
var finalExpiryRuleID string
|
||||
|
||||
// Iterate over all actionable rules and find the earliest
|
||||
// expiration date and its associated rule ID.
|
||||
for _, rule := range lc.FilterActionableRules(objName, objTags) {
|
||||
for _, rule := range lc.FilterActionableRules(obj) {
|
||||
if !rule.NoncurrentVersionExpiration.IsDaysNull() && !obj.IsLatest && obj.VersionID != "" {
|
||||
return rule.ID, expectedExpiryTime(time.Now(), ExpirationDays(rule.NoncurrentVersionExpiration.NoncurrentDays))
|
||||
}
|
||||
|
||||
if !rule.Expiration.IsDateNull() {
|
||||
if finalExpiryDate.IsZero() || finalExpiryDate.After(rule.Expiration.Date.Time) {
|
||||
finalExpiryRuleID = rule.ID
|
||||
@@ -193,7 +258,7 @@ func (lc Lifecycle) PredictExpiryTime(objName string, modTime time.Time, objTags
|
||||
}
|
||||
}
|
||||
if !rule.Expiration.IsDaysNull() {
|
||||
expectedExpiry := expectedExpiryTime(modTime, rule.Expiration.Days)
|
||||
expectedExpiry := expectedExpiryTime(obj.ModTime, rule.Expiration.Days)
|
||||
if finalExpiryDate.IsZero() || finalExpiryDate.After(expectedExpiry) {
|
||||
finalExpiryRuleID = rule.ID
|
||||
finalExpiryDate = expectedExpiry
|
||||
|
||||
@@ -263,21 +263,21 @@ func TestComputeActions(t *testing.T) {
|
||||
},
|
||||
// Too early to remove (test Date)
|
||||
{
|
||||
inputConfig: `<LifecycleConfiguration><Rule><Filter><Prefix>foodir/</Prefix></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><Prefix>foodir/</Prefix></Filter><Status>Enabled</Status><Expiration><Date>` + time.Now().UTC().Truncate(24*time.Hour).Add(24*time.Hour).Format(time.RFC3339) + `</Date></Expiration></Rule></LifecycleConfiguration>`,
|
||||
objectName: "foodir/fooobject",
|
||||
objectModTime: time.Now().UTC().Add(-24 * time.Hour), // Created 1 day ago
|
||||
expectedAction: NoneAction,
|
||||
},
|
||||
// Should remove (test Days)
|
||||
{
|
||||
inputConfig: `<LifecycleConfiguration><Rule><Filter><Prefix>foodir/</Prefix></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><Prefix>foodir/</Prefix></Filter><Status>Enabled</Status><Expiration><Date>` + time.Now().UTC().Truncate(24*time.Hour).Add(-24*time.Hour).Format(time.RFC3339) + `</Date></Expiration></Rule></LifecycleConfiguration>`,
|
||||
objectName: "foodir/fooobject",
|
||||
objectModTime: time.Now().UTC().Add(-24 * time.Hour), // Created 1 day ago
|
||||
expectedAction: DeleteAction,
|
||||
},
|
||||
// Should remove (Tags match)
|
||||
{
|
||||
inputConfig: `<LifecycleConfiguration><Rule><Filter><And><Prefix>foodir/</Prefix><Tag><Key>tag1</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>tag1</Key><Value>value1</Value></Tag></And></Filter><Status>Enabled</Status><Expiration><Date>` + time.Now().UTC().Truncate(24*time.Hour).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
|
||||
@@ -310,7 +310,7 @@ func TestComputeActions(t *testing.T) {
|
||||
|
||||
// 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().UTC().Truncate(24*time.Hour).Add(-24*time.Hour).Format(time.RFC3339) + `</Date></Expiration></Rule></LifecycleConfiguration>`,
|
||||
objectName: "foodir/fooobject",
|
||||
objectTags: "tag1=value1",
|
||||
objectModTime: time.Now().UTC().Add(-24 * time.Hour), // Created 1 day ago
|
||||
@@ -333,14 +333,20 @@ func TestComputeActions(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range testCases {
|
||||
t.Run(fmt.Sprintf("Test %d", i+1), func(t *testing.T) {
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
t.Run("", func(t *testing.T) {
|
||||
lc, err := ParseLifecycleConfig(bytes.NewReader([]byte(tc.inputConfig)))
|
||||
if err != nil {
|
||||
t.Fatalf("%d: Got unexpected error: %v", i+1, err)
|
||||
t.Fatalf("Got unexpected error: %v", err)
|
||||
}
|
||||
if resultAction := lc.ComputeAction(tc.objectName, tc.objectTags, tc.objectModTime); resultAction != tc.expectedAction {
|
||||
t.Fatalf("%d: Expected action: `%v`, got: `%v`", i+1, tc.expectedAction, resultAction)
|
||||
if resultAction := lc.ComputeAction(ObjectOpts{
|
||||
Name: tc.objectName,
|
||||
UserTags: tc.objectTags,
|
||||
ModTime: tc.objectModTime,
|
||||
IsLatest: true,
|
||||
}); resultAction != tc.expectedAction {
|
||||
t.Fatalf("Expected action: `%v`, got: `%v`", tc.expectedAction, resultAction)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -22,26 +22,31 @@ import (
|
||||
|
||||
// NoncurrentVersionExpiration - an action for lifecycle configuration rule.
|
||||
type NoncurrentVersionExpiration struct {
|
||||
XMLName xml.Name `xml:"NoncurrentVersionExpiration"`
|
||||
NoncurrentDays int `xml:"NoncurrentDays,omitempty"`
|
||||
XMLName xml.Name `xml:"NoncurrentVersionExpiration"`
|
||||
NoncurrentDays ExpirationDays `xml:"NoncurrentDays,omitempty"`
|
||||
}
|
||||
|
||||
// NoncurrentVersionTransition - an action for lifecycle configuration rule.
|
||||
type NoncurrentVersionTransition struct {
|
||||
NoncurrentDays int `xml:"NoncurrentDays"`
|
||||
StorageClass string `xml:"StorageClass"`
|
||||
NoncurrentDays ExpirationDays `xml:"NoncurrentDays"`
|
||||
StorageClass string `xml:"StorageClass"`
|
||||
}
|
||||
|
||||
var (
|
||||
errNoncurrentVersionExpirationUnsupported = Errorf("Specifying <NoncurrentVersionExpiration></NoncurrentVersionExpiration> is not supported")
|
||||
errNoncurrentVersionTransitionUnsupported = Errorf("Specifying <NoncurrentVersionTransition></NoncurrentVersionTransition> is not supported")
|
||||
)
|
||||
|
||||
// UnmarshalXML is extended to indicate lack of support for
|
||||
// NoncurrentVersionExpiration xml tag in object lifecycle
|
||||
// configuration
|
||||
func (n NoncurrentVersionExpiration) UnmarshalXML(d *xml.Decoder, startElement xml.StartElement) error {
|
||||
return errNoncurrentVersionExpirationUnsupported
|
||||
// MarshalXML if non-current days not set returns empty tags
|
||||
func (n *NoncurrentVersionExpiration) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
|
||||
if n.NoncurrentDays == ExpirationDays(0) {
|
||||
return nil
|
||||
}
|
||||
return e.EncodeElement(&n, start)
|
||||
}
|
||||
|
||||
// IsDaysNull returns true if days field is null
|
||||
func (n NoncurrentVersionExpiration) IsDaysNull() bool {
|
||||
return n.NoncurrentDays == ExpirationDays(0)
|
||||
}
|
||||
|
||||
// UnmarshalXML is extended to indicate lack of support for
|
||||
@@ -54,11 +59,8 @@ func (n NoncurrentVersionTransition) UnmarshalXML(d *xml.Decoder, startElement x
|
||||
// MarshalXML is extended to leave out
|
||||
// <NoncurrentVersionTransition></NoncurrentVersionTransition> tags
|
||||
func (n NoncurrentVersionTransition) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalXML is extended to leave out
|
||||
// <NoncurrentVersionExpiration></NoncurrentVersionExpiration> tags
|
||||
func (n NoncurrentVersionExpiration) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
|
||||
return nil
|
||||
if n.NoncurrentDays == ExpirationDays(0) {
|
||||
return nil
|
||||
}
|
||||
return e.EncodeElement(&n, start)
|
||||
}
|
||||
|
||||
@@ -25,8 +25,7 @@ import (
|
||||
// TestUnsupportedRules checks if Rule xml with unsuported tags return
|
||||
// appropriate errors on parsing
|
||||
func TestUnsupportedRules(t *testing.T) {
|
||||
// NoncurrentVersionTransition, NoncurrentVersionExpiration
|
||||
// and Transition tags aren't supported
|
||||
// NoncurrentVersionTransition, and Transition tags aren't supported
|
||||
unsupportedTestCases := []struct {
|
||||
inputXML string
|
||||
expectedErr error
|
||||
@@ -37,13 +36,6 @@ func TestUnsupportedRules(t *testing.T) {
|
||||
</Rule>`,
|
||||
expectedErr: errNoncurrentVersionTransitionUnsupported,
|
||||
},
|
||||
{ // Rule with unsupported NoncurrentVersionExpiration
|
||||
|
||||
inputXML: ` <Rule>
|
||||
<NoncurrentVersionExpiration></NoncurrentVersionExpiration>
|
||||
</Rule>`,
|
||||
expectedErr: errNoncurrentVersionExpirationUnsupported,
|
||||
},
|
||||
{ // Rule with unsupported Transition action
|
||||
inputXML: ` <Rule>
|
||||
<Transition></Transition>
|
||||
|
||||
@@ -125,23 +125,48 @@ const (
|
||||
PutBucketEncryptionAction = "s3:PutEncryptionConfiguration"
|
||||
// GetBucketEncryptionAction - GetBucketEncryption REST API action
|
||||
GetBucketEncryptionAction = "s3:GetEncryptionConfiguration"
|
||||
|
||||
// PutBucketVersioningAction - PutBucketVersioning REST API action
|
||||
PutBucketVersioningAction = "s3:PutBucketVersioning"
|
||||
// GetBucketVersioningAction - GetBucketVersioning REST API action
|
||||
GetBucketVersioningAction = "s3:GetBucketVersioning"
|
||||
|
||||
// DeleteObjectVersionAction - DeleteObjectVersion Rest API action.
|
||||
DeleteObjectVersionAction = "s3:DeleteObjectVersion"
|
||||
|
||||
// DeleteObjectVersionTaggingAction - DeleteObjectVersionTagging Rest API action.
|
||||
DeleteObjectVersionTaggingAction = "s3:DeleteObjectVersionTagging"
|
||||
|
||||
// GetObjectVersionAction - GetObjectVersionAction Rest API action.
|
||||
GetObjectVersionAction = "s3:GetObjectVersion"
|
||||
|
||||
// GetObjectVersionTaggingAction - GetObjectVersionTagging Rest API action.
|
||||
GetObjectVersionTaggingAction = "s3:GetObjectVersionTagging"
|
||||
|
||||
// PutObjectVersionTaggingAction - PutObjectVersionTagging Rest API action.
|
||||
PutObjectVersionTaggingAction = "s3:PutObjectVersionTagging"
|
||||
)
|
||||
|
||||
// List of all supported object actions.
|
||||
var supportedObjectActions = map[Action]struct{}{
|
||||
AbortMultipartUploadAction: {},
|
||||
DeleteObjectAction: {},
|
||||
GetObjectAction: {},
|
||||
ListMultipartUploadPartsAction: {},
|
||||
PutObjectAction: {},
|
||||
BypassGovernanceRetentionAction: {},
|
||||
PutObjectRetentionAction: {},
|
||||
GetObjectRetentionAction: {},
|
||||
PutObjectLegalHoldAction: {},
|
||||
GetObjectLegalHoldAction: {},
|
||||
GetObjectTaggingAction: {},
|
||||
PutObjectTaggingAction: {},
|
||||
DeleteObjectTaggingAction: {},
|
||||
AbortMultipartUploadAction: {},
|
||||
DeleteObjectAction: {},
|
||||
GetObjectAction: {},
|
||||
ListMultipartUploadPartsAction: {},
|
||||
PutObjectAction: {},
|
||||
BypassGovernanceRetentionAction: {},
|
||||
PutObjectRetentionAction: {},
|
||||
GetObjectRetentionAction: {},
|
||||
PutObjectLegalHoldAction: {},
|
||||
GetObjectLegalHoldAction: {},
|
||||
GetObjectTaggingAction: {},
|
||||
PutObjectTaggingAction: {},
|
||||
DeleteObjectTaggingAction: {},
|
||||
GetObjectVersionAction: {},
|
||||
GetObjectVersionTaggingAction: {},
|
||||
DeleteObjectVersionAction: {},
|
||||
DeleteObjectVersionTaggingAction: {},
|
||||
PutObjectVersionTaggingAction: {},
|
||||
}
|
||||
|
||||
// isObjectAction - returns whether action is object type or not.
|
||||
@@ -181,12 +206,19 @@ var supportedActions = map[Action]struct{}{
|
||||
GetBucketObjectLockConfigurationAction: {},
|
||||
PutBucketTaggingAction: {},
|
||||
GetBucketTaggingAction: {},
|
||||
GetObjectVersionAction: {},
|
||||
GetObjectVersionTaggingAction: {},
|
||||
DeleteObjectVersionAction: {},
|
||||
DeleteObjectVersionTaggingAction: {},
|
||||
PutObjectVersionTaggingAction: {},
|
||||
BypassGovernanceRetentionAction: {},
|
||||
GetObjectTaggingAction: {},
|
||||
PutObjectTaggingAction: {},
|
||||
DeleteObjectTaggingAction: {},
|
||||
PutBucketEncryptionAction: {},
|
||||
GetBucketEncryptionAction: {},
|
||||
PutBucketVersioningAction: {},
|
||||
GetBucketVersioningAction: {},
|
||||
}
|
||||
|
||||
// IsValid - checks if action is valid or not.
|
||||
@@ -246,7 +278,6 @@ var actionConditionKeyMap = map[Action]condition.KeySet{
|
||||
append([]condition.Key{
|
||||
condition.S3XAmzServerSideEncryption,
|
||||
condition.S3XAmzServerSideEncryptionCustomerAlgorithm,
|
||||
condition.S3XAmzStorageClass,
|
||||
}, condition.CommonKeys...)...),
|
||||
|
||||
HeadBucketAction: condition.NewKeySet(condition.CommonKeys...),
|
||||
@@ -308,4 +339,22 @@ var actionConditionKeyMap = map[Action]condition.KeySet{
|
||||
PutObjectTaggingAction: condition.NewKeySet(condition.CommonKeys...),
|
||||
GetObjectTaggingAction: condition.NewKeySet(condition.CommonKeys...),
|
||||
DeleteObjectTaggingAction: condition.NewKeySet(condition.CommonKeys...),
|
||||
|
||||
PutObjectVersionTaggingAction: condition.NewKeySet(condition.CommonKeys...),
|
||||
GetObjectVersionAction: condition.NewKeySet(
|
||||
append([]condition.Key{
|
||||
condition.S3VersionID,
|
||||
}, condition.CommonKeys...)...),
|
||||
GetObjectVersionTaggingAction: condition.NewKeySet(
|
||||
append([]condition.Key{
|
||||
condition.S3VersionID,
|
||||
}, condition.CommonKeys...)...),
|
||||
DeleteObjectVersionAction: condition.NewKeySet(
|
||||
append([]condition.Key{
|
||||
condition.S3VersionID,
|
||||
}, condition.CommonKeys...)...),
|
||||
DeleteObjectVersionTaggingAction: condition.NewKeySet(
|
||||
append([]condition.Key{
|
||||
condition.S3VersionID,
|
||||
}, condition.CommonKeys...)...),
|
||||
}
|
||||
|
||||
@@ -59,6 +59,10 @@ const (
|
||||
// S3Delimiter - key representing delimiter query parameter of ListBucket API only.
|
||||
S3Delimiter Key = "s3:delimiter"
|
||||
|
||||
// S3VersionID - Enables you to limit the permission for the
|
||||
// s3:PutObjectVersionTagging action to a specific object version.
|
||||
S3VersionID Key = "s3:versionid"
|
||||
|
||||
// S3MaxKeys - key representing max-keys query parameter of ListBucket API only.
|
||||
S3MaxKeys Key = "s3:max-keys"
|
||||
|
||||
|
||||
44
pkg/bucket/versioning/error.go
Normal file
44
pkg/bucket/versioning/error.go
Normal file
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* 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 versioning
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Error is the generic type for any error happening during tag
|
||||
// parsing.
|
||||
type Error struct {
|
||||
err error
|
||||
}
|
||||
|
||||
// Errorf - formats according to a format specifier and returns
|
||||
// the string as a value that satisfies error of type tagging.Error
|
||||
func Errorf(format string, a ...interface{}) error {
|
||||
return Error{err: fmt.Errorf(format, a...)}
|
||||
}
|
||||
|
||||
// Unwrap the internal error.
|
||||
func (e Error) Unwrap() error { return e.err }
|
||||
|
||||
// Error 'error' compatible method.
|
||||
func (e Error) Error() string {
|
||||
if e.err == nil {
|
||||
return "versioning: cause <nil>"
|
||||
}
|
||||
return e.err.Error()
|
||||
}
|
||||
79
pkg/bucket/versioning/versioning.go
Normal file
79
pkg/bucket/versioning/versioning.go
Normal file
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* 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 versioning
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"io"
|
||||
)
|
||||
|
||||
// State - enabled/disabled/suspended states
|
||||
// for multifactor and status of versioning.
|
||||
type State string
|
||||
|
||||
// Various supported states
|
||||
const (
|
||||
Enabled State = "Enabled"
|
||||
// Disabled State = "Disabled" only used by MFA Delete not supported yet.
|
||||
Suspended State = "Suspended"
|
||||
)
|
||||
|
||||
// Versioning - Configuration for bucket versioning.
|
||||
type Versioning struct {
|
||||
XMLNS string `xml:"xmlns,attr,omitempty"`
|
||||
XMLName xml.Name `xml:"VersioningConfiguration"`
|
||||
// MFADelete State `xml:"MFADelete,omitempty"` // not supported yet.
|
||||
Status State `xml:"Status,omitempty"`
|
||||
}
|
||||
|
||||
// Validate - validates the versioning configuration
|
||||
func (v Versioning) Validate() error {
|
||||
// Not supported yet
|
||||
// switch v.MFADelete {
|
||||
// case Enabled, Disabled:
|
||||
// default:
|
||||
// return Errorf("unsupported MFADelete state %s", v.MFADelete)
|
||||
// }
|
||||
switch v.Status {
|
||||
case Enabled, Suspended:
|
||||
default:
|
||||
return Errorf("unsupported Versioning status %s", v.Status)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Enabled - returns true if versioning is enabled
|
||||
func (v Versioning) Enabled() bool {
|
||||
return v.Status == Enabled
|
||||
}
|
||||
|
||||
// Suspended - returns true if versioning is suspended
|
||||
func (v Versioning) Suspended() bool {
|
||||
return v.Status == Suspended
|
||||
}
|
||||
|
||||
// ParseConfig - parses data in given reader to VersioningConfiguration.
|
||||
func ParseConfig(reader io.Reader) (*Versioning, error) {
|
||||
var v Versioning
|
||||
if err := xml.NewDecoder(reader).Decode(&v); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := v.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &v, nil
|
||||
}
|
||||
Reference in New Issue
Block a user