aboutsummaryrefslogtreecommitdiff
path: root/weed/s3api/s3api_object_handlers_delete.go
diff options
context:
space:
mode:
authorChris Lu <chrislusf@users.noreply.github.com>2025-07-21 00:23:22 -0700
committerGitHub <noreply@github.com>2025-07-21 00:23:22 -0700
commitc196d03951a75d3b8976f556cb0400e5b522edeb (patch)
tree21618d3645e44fd763648067b1db900cd3fa985e /weed/s3api/s3api_object_handlers_delete.go
parentbfe68984d5b5dff29083aeb6f79401a530feb510 (diff)
downloadseaweedfs-c196d03951a75d3b8976f556cb0400e5b522edeb.tar.xz
seaweedfs-c196d03951a75d3b8976f556cb0400e5b522edeb.zip
fix listing object versions (#7006)
* fix listing object versions * Update s3api_object_versioning.go * Update s3_directory_versioning_test.go * check previous skipped tests * fix test_versioning_stack_delete_merkers * address test_bucket_list_return_data_versioning * Update s3_directory_versioning_test.go * fix test_versioning_concurrent_multi_object_delete * fix test_versioning_obj_suspend_versions test * fix empty owner * fix listing versioned objects * default owner * fix path
Diffstat (limited to 'weed/s3api/s3api_object_handlers_delete.go')
-rw-r--r--weed/s3api/s3api_object_handlers_delete.go116
1 files changed, 85 insertions, 31 deletions
diff --git a/weed/s3api/s3api_object_handlers_delete.go b/weed/s3api/s3api_object_handlers_delete.go
index 8cb5c04fe..3a2544710 100644
--- a/weed/s3api/s3api_object_handlers_delete.go
+++ b/weed/s3api/s3api_object_handlers_delete.go
@@ -32,8 +32,8 @@ func (s3a *S3ApiServer) DeleteObjectHandler(w http.ResponseWriter, r *http.Reque
// Check for specific version ID in query parameters
versionId := r.URL.Query().Get("versionId")
- // Check if versioning is configured for the bucket (Enabled or Suspended)
- versioningConfigured, err := s3a.isVersioningConfigured(bucket)
+ // Get detailed versioning state for proper handling of suspended vs enabled versioning
+ versioningState, err := s3a.getVersioningState(bucket)
if err != nil {
if err == filer_pb.ErrNotFound {
s3err.WriteErrorResponse(w, r, s3err.ErrNoSuchBucket)
@@ -44,14 +44,19 @@ func (s3a *S3ApiServer) DeleteObjectHandler(w http.ResponseWriter, r *http.Reque
return
}
+ versioningEnabled := (versioningState == s3_constants.VersioningEnabled)
+ versioningSuspended := (versioningState == s3_constants.VersioningSuspended)
+ versioningConfigured := (versioningState != "")
+
var auditLog *s3err.AccessLog
if s3err.Logger != nil {
auditLog = s3err.GetAccessLog(r, http.StatusNoContent, s3err.ErrNone)
}
if versioningConfigured {
- // Handle versioned delete
+ // Handle versioned delete based on specific versioning state
if versionId != "" {
+ // Delete specific version (same for both enabled and suspended)
// Check object lock permissions before deleting specific version
governanceBypassAllowed := s3a.evaluateGovernanceBypassRequest(r, bucket, object)
if err := s3a.enforceObjectLockProtections(r, bucket, object, versionId, governanceBypassAllowed); err != nil {
@@ -71,19 +76,44 @@ func (s3a *S3ApiServer) DeleteObjectHandler(w http.ResponseWriter, r *http.Reque
// Set version ID in response header
w.Header().Set("x-amz-version-id", versionId)
} else {
- // Create delete marker (logical delete)
- // AWS S3 behavior: Delete marker creation is NOT blocked by object retention
- // because it's a logical delete that doesn't actually remove the retained version
- deleteMarkerVersionId, err := s3a.createDeleteMarker(bucket, object)
- if err != nil {
- glog.Errorf("Failed to create delete marker: %v", err)
- s3err.WriteErrorResponse(w, r, s3err.ErrInternalError)
- return
- }
+ // Delete without version ID - behavior depends on versioning state
+ if versioningEnabled {
+ // Enabled versioning: Create delete marker (logical delete)
+ // AWS S3 behavior: Delete marker creation is NOT blocked by object retention
+ // because it's a logical delete that doesn't actually remove the retained version
+ deleteMarkerVersionId, err := s3a.createDeleteMarker(bucket, object)
+ if err != nil {
+ glog.Errorf("Failed to create delete marker: %v", err)
+ s3err.WriteErrorResponse(w, r, s3err.ErrInternalError)
+ return
+ }
+
+ // Set delete marker version ID in response header
+ w.Header().Set("x-amz-version-id", deleteMarkerVersionId)
+ w.Header().Set("x-amz-delete-marker", "true")
+ } else if versioningSuspended {
+ // Suspended versioning: Actually delete the "null" version object
+ glog.V(2).Infof("DeleteObjectHandler: deleting null version for suspended versioning %s/%s", bucket, object)
+
+ // Check object lock permissions before deleting "null" version
+ governanceBypassAllowed := s3a.evaluateGovernanceBypassRequest(r, bucket, object)
+ if err := s3a.enforceObjectLockProtections(r, bucket, object, "null", governanceBypassAllowed); err != nil {
+ glog.V(2).Infof("DeleteObjectHandler: object lock check failed for %s/%s: %v", bucket, object, err)
+ s3err.WriteErrorResponse(w, r, s3err.ErrAccessDenied)
+ return
+ }
+
+ // Delete the "null" version (the regular file)
+ err := s3a.deleteSpecificObjectVersion(bucket, object, "null")
+ if err != nil {
+ glog.Errorf("Failed to delete null version: %v", err)
+ s3err.WriteErrorResponse(w, r, s3err.ErrInternalError)
+ return
+ }
- // Set delete marker version ID in response header
- w.Header().Set("x-amz-version-id", deleteMarkerVersionId)
- w.Header().Set("x-amz-delete-marker", "true")
+ // Note: According to AWS S3 spec, suspended versioning should NOT return version ID headers
+ // The object is deleted but no version information is returned
+ }
}
} else {
// Handle regular delete (non-versioned)
@@ -203,8 +233,8 @@ func (s3a *S3ApiServer) DeleteMultipleObjectsHandler(w http.ResponseWriter, r *h
auditLog = s3err.GetAccessLog(r, http.StatusNoContent, s3err.ErrNone)
}
- // Check if versioning is configured for the bucket (needed for object lock checks)
- versioningConfigured, err := s3a.isVersioningConfigured(bucket)
+ // Get detailed versioning state for proper handling of suspended vs enabled versioning
+ versioningState, err := s3a.getVersioningState(bucket)
if err != nil {
if err == filer_pb.ErrNotFound {
s3err.WriteErrorResponse(w, r, s3err.ErrNoSuchBucket)
@@ -215,6 +245,10 @@ func (s3a *S3ApiServer) DeleteMultipleObjectsHandler(w http.ResponseWriter, r *h
return
}
+ versioningEnabled := (versioningState == s3_constants.VersioningEnabled)
+ versioningSuspended := (versioningState == s3_constants.VersioningSuspended)
+ versioningConfigured := (versioningState != "")
+
s3a.WithFilerClient(false, func(client filer_pb.SeaweedFilerClient) error {
// delete file entries
@@ -243,9 +277,9 @@ func (s3a *S3ApiServer) DeleteMultipleObjectsHandler(w http.ResponseWriter, r *h
var isDeleteMarker bool
if versioningConfigured {
- // Handle versioned delete
+ // Handle versioned delete based on specific versioning state
if object.VersionId != "" {
- // Delete specific version
+ // Delete specific version (same for both enabled and suspended)
err := s3a.deleteSpecificObjectVersion(bucket, object.Key, object.VersionId)
if err != nil {
deleteErrors = append(deleteErrors, DeleteError{
@@ -258,19 +292,39 @@ func (s3a *S3ApiServer) DeleteMultipleObjectsHandler(w http.ResponseWriter, r *h
}
deleteVersionId = object.VersionId
} else {
- // Create delete marker (logical delete)
- deleteMarkerVersionId, err := s3a.createDeleteMarker(bucket, object.Key)
- if err != nil {
- deleteErrors = append(deleteErrors, DeleteError{
- Code: "",
- Message: err.Error(),
- Key: object.Key,
- VersionId: object.VersionId,
- })
- continue
+ // Delete without version ID - behavior depends on versioning state
+ if versioningEnabled {
+ // Enabled versioning: Create delete marker (logical delete)
+ deleteMarkerVersionId, err := s3a.createDeleteMarker(bucket, object.Key)
+ if err != nil {
+ deleteErrors = append(deleteErrors, DeleteError{
+ Code: "",
+ Message: err.Error(),
+ Key: object.Key,
+ VersionId: object.VersionId,
+ })
+ continue
+ }
+ deleteVersionId = deleteMarkerVersionId
+ isDeleteMarker = true
+ } else if versioningSuspended {
+ // Suspended versioning: Actually delete the "null" version object
+ glog.V(2).Infof("DeleteMultipleObjectsHandler: deleting null version for suspended versioning %s/%s", bucket, object.Key)
+
+ err := s3a.deleteSpecificObjectVersion(bucket, object.Key, "null")
+ if err != nil {
+ deleteErrors = append(deleteErrors, DeleteError{
+ Code: "",
+ Message: err.Error(),
+ Key: object.Key,
+ VersionId: "null",
+ })
+ continue
+ }
+ deleteVersionId = "null"
+ // Note: For suspended versioning, we don't set isDeleteMarker=true
+ // because we actually deleted the object, not created a delete marker
}
- deleteVersionId = deleteMarkerVersionId
- isDeleteMarker = true
}
// Add to successful deletions with version info