Files
seaweedfs/weed/s3api/s3api_object_handlers_retention.go
T
chrislu 9f88fd2ea5 fixes
With these comprehensive fixes, the s3-tests should now:
✅ Return InvalidBucketState (409 Conflict) for object lock operations on invalid buckets
✅ Return InvalidRetentionPeriod for invalid retention periods
✅ Return MalformedXML for malformed retention configurations
✅ Include VersionId in response headers when available
✅ Return proper HTTP status codes for all error conditions
✅ Handle all object lock validation errors consistently
The workflow should now pass significantly more object lock tests, bringing SeaweedFS's S3 object lock implementation much closer to AWS S3 compatibility standards.
2025-07-18 15:45:15 -07:00

141 lines
4.6 KiB
Go

package s3api
import (
"encoding/xml"
"errors"
"net/http"
"github.com/seaweedfs/seaweedfs/weed/glog"
"github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
"github.com/seaweedfs/seaweedfs/weed/s3api/s3err"
stats_collect "github.com/seaweedfs/seaweedfs/weed/stats"
)
// PutObjectRetentionHandler Put object Retention
// https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutObjectRetention.html
func (s3a *S3ApiServer) PutObjectRetentionHandler(w http.ResponseWriter, r *http.Request) {
bucket, object := s3_constants.GetBucketAndObject(r)
glog.V(3).Infof("PutObjectRetentionHandler %s %s", bucket, object)
// Check if Object Lock is available for this bucket (requires versioning)
if !s3a.handleObjectLockAvailabilityCheck(w, r, bucket, "PutObjectRetentionHandler") {
return
}
// Get version ID from query parameters
versionId := r.URL.Query().Get("versionId")
// Check for bypass governance retention header
bypassGovernance := r.Header.Get("x-amz-bypass-governance-retention") == "true"
// Parse retention configuration from request body
retention, err := parseObjectRetention(r)
if err != nil {
glog.Errorf("PutObjectRetentionHandler: failed to parse retention config: %v", err)
s3err.WriteErrorResponse(w, r, s3err.ErrMalformedXML)
return
}
// Validate retention configuration
if err := validateRetention(retention); err != nil {
glog.Errorf("PutObjectRetentionHandler: invalid retention config: %v", err)
s3err.WriteErrorResponse(w, r, mapValidationErrorToS3Error(err))
return
}
// Set retention on the object
if err := s3a.setObjectRetention(bucket, object, versionId, retention, bypassGovernance); err != nil {
glog.Errorf("PutObjectRetentionHandler: failed to set retention: %v", err)
// Handle specific error cases
if errors.Is(err, ErrObjectNotFound) || errors.Is(err, ErrVersionNotFound) || errors.Is(err, ErrLatestVersionNotFound) {
s3err.WriteErrorResponse(w, r, s3err.ErrNoSuchKey)
return
}
if errors.Is(err, ErrComplianceModeActive) || errors.Is(err, ErrGovernanceModeActive) {
// Return 403 Forbidden for retention mode changes without proper permissions
s3err.WriteErrorResponse(w, r, s3err.ErrAccessDenied)
return
}
s3err.WriteErrorResponse(w, r, s3err.ErrInternalError)
return
}
// Add VersionId to response headers if available (expected by s3-tests)
if versionId != "" {
w.Header().Set("x-amz-version-id", versionId)
}
// Record metrics
stats_collect.RecordBucketActiveTime(bucket)
// Return success (HTTP 200 with no body)
w.WriteHeader(http.StatusOK)
glog.V(3).Infof("PutObjectRetentionHandler: successfully set retention for %s/%s", bucket, object)
}
// GetObjectRetentionHandler Get object Retention
// https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetObjectRetention.html
func (s3a *S3ApiServer) GetObjectRetentionHandler(w http.ResponseWriter, r *http.Request) {
bucket, object := s3_constants.GetBucketAndObject(r)
glog.V(3).Infof("GetObjectRetentionHandler %s %s", bucket, object)
// Check if Object Lock is available for this bucket (requires versioning)
if !s3a.handleObjectLockAvailabilityCheck(w, r, bucket, "GetObjectRetentionHandler") {
return
}
// Get version ID from query parameters
versionId := r.URL.Query().Get("versionId")
// Get retention configuration for the object
retention, err := s3a.getObjectRetention(bucket, object, versionId)
if err != nil {
glog.Errorf("GetObjectRetentionHandler: failed to get retention: %v", err)
// Handle specific error cases
if errors.Is(err, ErrObjectNotFound) || errors.Is(err, ErrVersionNotFound) {
s3err.WriteErrorResponse(w, r, s3err.ErrNoSuchKey)
return
}
if errors.Is(err, ErrNoRetentionConfiguration) {
s3err.WriteErrorResponse(w, r, s3err.ErrNoSuchObjectLockConfiguration)
return
}
s3err.WriteErrorResponse(w, r, s3err.ErrInternalError)
return
}
// Marshal retention configuration to XML
retentionXML, err := xml.Marshal(retention)
if err != nil {
glog.Errorf("GetObjectRetentionHandler: failed to marshal retention: %v", err)
s3err.WriteErrorResponse(w, r, s3err.ErrInternalError)
return
}
// Set response headers
w.Header().Set("Content-Type", "application/xml")
w.WriteHeader(http.StatusOK)
// Write XML response
if _, err := w.Write([]byte(xml.Header)); err != nil {
glog.Errorf("GetObjectRetentionHandler: failed to write XML header: %v", err)
return
}
if _, err := w.Write(retentionXML); err != nil {
glog.Errorf("GetObjectRetentionHandler: failed to write retention XML: %v", err)
return
}
// Record metrics
stats_collect.RecordBucketActiveTime(bucket)
glog.V(3).Infof("GetObjectRetentionHandler: successfully retrieved retention for %s/%s", bucket, object)
}