3 changed files with 285 additions and 46 deletions
-
57weed/admin/dash/admin_server.go
-
46weed/admin/dash/bucket_management.go
-
228weed/s3api/object_lock_utils.go
@ -0,0 +1,228 @@ |
|||||
|
package s3api |
||||
|
|
||||
|
import ( |
||||
|
"encoding/xml" |
||||
|
"fmt" |
||||
|
"strconv" |
||||
|
"strings" |
||||
|
|
||||
|
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb" |
||||
|
"github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants" |
||||
|
) |
||||
|
|
||||
|
// ObjectLockUtils provides shared utilities for Object Lock configuration
|
||||
|
// These functions are used by both Admin UI and S3 API handlers to ensure consistency
|
||||
|
|
||||
|
// VersioningUtils provides shared utilities for bucket versioning configuration
|
||||
|
// These functions ensure Admin UI and S3 API use the same versioning keys
|
||||
|
|
||||
|
// StoreVersioningInExtended stores versioning configuration in entry extended attributes
|
||||
|
func StoreVersioningInExtended(entry *filer_pb.Entry, enabled bool) error { |
||||
|
if entry.Extended == nil { |
||||
|
entry.Extended = make(map[string][]byte) |
||||
|
} |
||||
|
|
||||
|
if enabled { |
||||
|
entry.Extended[s3_constants.ExtVersioningKey] = []byte(s3_constants.VersioningEnabled) |
||||
|
} else { |
||||
|
entry.Extended[s3_constants.ExtVersioningKey] = []byte(s3_constants.VersioningSuspended) |
||||
|
} |
||||
|
|
||||
|
return nil |
||||
|
} |
||||
|
|
||||
|
// LoadVersioningFromExtended loads versioning configuration from entry extended attributes
|
||||
|
func LoadVersioningFromExtended(entry *filer_pb.Entry) (bool, bool) { |
||||
|
if entry == nil || entry.Extended == nil { |
||||
|
return false, false // not found, default to suspended
|
||||
|
} |
||||
|
|
||||
|
// Check for S3 API compatible key
|
||||
|
if versioningBytes, exists := entry.Extended[s3_constants.ExtVersioningKey]; exists { |
||||
|
enabled := string(versioningBytes) == s3_constants.VersioningEnabled |
||||
|
return enabled, true |
||||
|
} |
||||
|
|
||||
|
return false, false // not found
|
||||
|
} |
||||
|
|
||||
|
// CreateObjectLockConfiguration creates a new ObjectLockConfiguration with the specified parameters
|
||||
|
func CreateObjectLockConfiguration(enabled bool, mode string, days int, years int) *ObjectLockConfiguration { |
||||
|
if !enabled { |
||||
|
return nil |
||||
|
} |
||||
|
|
||||
|
config := &ObjectLockConfiguration{ |
||||
|
ObjectLockEnabled: s3_constants.ObjectLockEnabled, |
||||
|
} |
||||
|
|
||||
|
// Add default retention rule if mode and period are specified
|
||||
|
if mode != "" && (days > 0 || years > 0) { |
||||
|
config.Rule = &ObjectLockRule{ |
||||
|
DefaultRetention: &DefaultRetention{ |
||||
|
Mode: mode, |
||||
|
Days: days, |
||||
|
Years: years, |
||||
|
}, |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return config |
||||
|
} |
||||
|
|
||||
|
// ObjectLockConfigurationToXML converts ObjectLockConfiguration to XML bytes
|
||||
|
func ObjectLockConfigurationToXML(config *ObjectLockConfiguration) ([]byte, error) { |
||||
|
if config == nil { |
||||
|
return nil, fmt.Errorf("object lock configuration is nil") |
||||
|
} |
||||
|
|
||||
|
return xml.Marshal(config) |
||||
|
} |
||||
|
|
||||
|
// XMLToObjectLockConfiguration parses XML bytes to ObjectLockConfiguration
|
||||
|
func XMLToObjectLockConfiguration(xmlData []byte) (*ObjectLockConfiguration, error) { |
||||
|
if len(xmlData) == 0 { |
||||
|
return nil, fmt.Errorf("XML data is empty") |
||||
|
} |
||||
|
|
||||
|
var config ObjectLockConfiguration |
||||
|
if err := xml.Unmarshal(xmlData, &config); err != nil { |
||||
|
return nil, fmt.Errorf("failed to parse Object Lock configuration XML: %w", err) |
||||
|
} |
||||
|
|
||||
|
return &config, nil |
||||
|
} |
||||
|
|
||||
|
// StoreObjectLockConfigurationInExtended stores Object Lock configuration in entry extended attributes
|
||||
|
func StoreObjectLockConfigurationInExtended(entry *filer_pb.Entry, config *ObjectLockConfiguration) error { |
||||
|
if entry.Extended == nil { |
||||
|
entry.Extended = make(map[string][]byte) |
||||
|
} |
||||
|
|
||||
|
if config == nil { |
||||
|
// Remove Object Lock configuration
|
||||
|
delete(entry.Extended, s3_constants.ExtObjectLockEnabledKey) |
||||
|
delete(entry.Extended, s3_constants.ExtObjectLockConfigKey) |
||||
|
return nil |
||||
|
} |
||||
|
|
||||
|
// Store the enabled flag
|
||||
|
entry.Extended[s3_constants.ExtObjectLockEnabledKey] = []byte(config.ObjectLockEnabled) |
||||
|
|
||||
|
// Store the full XML configuration
|
||||
|
configXML, err := ObjectLockConfigurationToXML(config) |
||||
|
if err != nil { |
||||
|
return fmt.Errorf("failed to marshal Object Lock configuration: %w", err) |
||||
|
} |
||||
|
entry.Extended[s3_constants.ExtObjectLockConfigKey] = configXML |
||||
|
|
||||
|
return nil |
||||
|
} |
||||
|
|
||||
|
// LoadObjectLockConfigurationFromExtended loads Object Lock configuration from entry extended attributes
|
||||
|
func LoadObjectLockConfigurationFromExtended(entry *filer_pb.Entry) (*ObjectLockConfiguration, bool) { |
||||
|
if entry == nil || entry.Extended == nil { |
||||
|
return nil, false |
||||
|
} |
||||
|
|
||||
|
// Check if Object Lock is enabled
|
||||
|
enabledBytes, exists := entry.Extended[s3_constants.ExtObjectLockEnabledKey] |
||||
|
if !exists { |
||||
|
return nil, false |
||||
|
} |
||||
|
|
||||
|
enabled := string(enabledBytes) |
||||
|
if enabled != s3_constants.ObjectLockEnabled && enabled != "true" { |
||||
|
return nil, false |
||||
|
} |
||||
|
|
||||
|
// Try to load full XML configuration
|
||||
|
if configXML, exists := entry.Extended[s3_constants.ExtObjectLockConfigKey]; exists { |
||||
|
if config, err := XMLToObjectLockConfiguration(configXML); err == nil { |
||||
|
return config, true |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Fallback: create minimal configuration for enabled Object Lock
|
||||
|
return &ObjectLockConfiguration{ |
||||
|
ObjectLockEnabled: s3_constants.ObjectLockEnabled, |
||||
|
}, true |
||||
|
} |
||||
|
|
||||
|
// ExtractObjectLockInfoFromConfig extracts basic Object Lock information from configuration
|
||||
|
// Returns: enabled, mode, duration (for UI display)
|
||||
|
func ExtractObjectLockInfoFromConfig(config *ObjectLockConfiguration) (bool, string, int32) { |
||||
|
if config == nil || config.ObjectLockEnabled != s3_constants.ObjectLockEnabled { |
||||
|
return false, "", 0 |
||||
|
} |
||||
|
|
||||
|
if config.Rule == nil || config.Rule.DefaultRetention == nil { |
||||
|
return true, "", 0 |
||||
|
} |
||||
|
|
||||
|
defaultRetention := config.Rule.DefaultRetention |
||||
|
|
||||
|
// Convert years to days for consistent representation
|
||||
|
days := defaultRetention.Days |
||||
|
if defaultRetention.Years > 0 { |
||||
|
days += defaultRetention.Years * 365 |
||||
|
} |
||||
|
|
||||
|
return true, defaultRetention.Mode, int32(days) |
||||
|
} |
||||
|
|
||||
|
// CreateObjectLockConfigurationFromParams creates ObjectLockConfiguration from individual parameters
|
||||
|
// This is a convenience function for Admin UI usage
|
||||
|
func CreateObjectLockConfigurationFromParams(enabled bool, mode string, duration int32) *ObjectLockConfiguration { |
||||
|
if !enabled { |
||||
|
return nil |
||||
|
} |
||||
|
|
||||
|
return CreateObjectLockConfiguration(enabled, mode, int(duration), 0) |
||||
|
} |
||||
|
|
||||
|
// ValidateObjectLockParameters validates Object Lock parameters before creating configuration
|
||||
|
func ValidateObjectLockParameters(enabled bool, mode string, duration int32) error { |
||||
|
if !enabled { |
||||
|
return nil |
||||
|
} |
||||
|
|
||||
|
if mode != s3_constants.RetentionModeGovernance && mode != s3_constants.RetentionModeCompliance { |
||||
|
return fmt.Errorf("invalid object lock mode: %s, must be GOVERNANCE or COMPLIANCE", mode) |
||||
|
} |
||||
|
|
||||
|
if duration <= 0 { |
||||
|
return fmt.Errorf("object lock duration must be greater than 0 days") |
||||
|
} |
||||
|
|
||||
|
if duration > MaxRetentionDays { |
||||
|
return fmt.Errorf("object lock duration exceeds maximum allowed days: %d", MaxRetentionDays) |
||||
|
} |
||||
|
|
||||
|
return nil |
||||
|
} |
||||
|
|
||||
|
// SimpleXMLParseObjectLockMode extracts mode from XML string using simple string parsing
|
||||
|
// This is used as a fallback when full XML parsing is not needed
|
||||
|
func SimpleXMLParseObjectLockMode(xmlStr string) string { |
||||
|
if strings.Contains(xmlStr, "<Mode>GOVERNANCE</Mode>") { |
||||
|
return "GOVERNANCE" |
||||
|
} else if strings.Contains(xmlStr, "<Mode>COMPLIANCE</Mode>") { |
||||
|
return "COMPLIANCE" |
||||
|
} |
||||
|
return "" |
||||
|
} |
||||
|
|
||||
|
// SimpleXMLParseObjectLockDays extracts days from XML string using simple string parsing
|
||||
|
// This is used as a fallback when full XML parsing is not needed
|
||||
|
func SimpleXMLParseObjectLockDays(xmlStr string) int32 { |
||||
|
if daysStart := strings.Index(xmlStr, "<Days>"); daysStart != -1 { |
||||
|
daysStart += 6 // length of "<Days>"
|
||||
|
if daysEnd := strings.Index(xmlStr[daysStart:], "</Days>"); daysEnd != -1 { |
||||
|
if duration, err := strconv.ParseInt(xmlStr[daysStart:daysStart+daysEnd], 10, 32); err == nil { |
||||
|
return int32(duration) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
return 0 |
||||
|
} |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue