aboutsummaryrefslogtreecommitdiff
path: root/weed/s3api/s3api_object_handlers_list.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_list.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_list.go')
-rw-r--r--weed/s3api/s3api_object_handlers_list.go93
1 files changed, 88 insertions, 5 deletions
diff --git a/weed/s3api/s3api_object_handlers_list.go b/weed/s3api/s3api_object_handlers_list.go
index bbb67d391..8a55db854 100644
--- a/weed/s3api/s3api_object_handlers_list.go
+++ b/weed/s3api/s3api_object_handlers_list.go
@@ -4,16 +4,17 @@ import (
"context"
"encoding/xml"
"fmt"
- "github.com/aws/aws-sdk-go/service/s3"
- "github.com/seaweedfs/seaweedfs/weed/glog"
- "github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
- "github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
- "github.com/seaweedfs/seaweedfs/weed/s3api/s3err"
"io"
"net/http"
"net/url"
"strconv"
"strings"
+
+ "github.com/aws/aws-sdk-go/service/s3"
+ "github.com/seaweedfs/seaweedfs/weed/glog"
+ "github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
+ "github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
+ "github.com/seaweedfs/seaweedfs/weed/s3api/s3err"
)
type OptionalString struct {
@@ -356,6 +357,9 @@ func (s3a *S3ApiServer) doListFilerEntries(client filer_pb.SeaweedFilerClient, d
return
}
+ // Track .versions directories found in this directory for later processing
+ var versionsDirs []string
+
for {
resp, recvErr := stream.Recv()
if recvErr != nil {
@@ -386,6 +390,14 @@ func (s3a *S3ApiServer) doListFilerEntries(client filer_pb.SeaweedFilerClient, d
if entry.Name == s3_constants.MultipartUploadsFolder { // FIXME no need to apply to all directories. this extra also affects maxKeys
continue
}
+
+ // Skip .versions directories in regular list operations but track them for logical object creation
+ if strings.HasSuffix(entry.Name, ".versions") {
+ glog.V(4).Infof("Found .versions directory: %s", entry.Name)
+ versionsDirs = append(versionsDirs, entry.Name)
+ continue
+ }
+
if delimiter != "/" || cursor.prefixEndsOnDelimiter {
if cursor.prefixEndsOnDelimiter {
cursor.prefixEndsOnDelimiter = false
@@ -425,6 +437,48 @@ func (s3a *S3ApiServer) doListFilerEntries(client filer_pb.SeaweedFilerClient, d
cursor.prefixEndsOnDelimiter = false
}
}
+
+ // After processing all regular entries, handle versioned objects
+ // Create logical entries for objects that have .versions directories
+ for _, versionsDir := range versionsDirs {
+ if cursor.maxKeys <= 0 {
+ cursor.isTruncated = true
+ break
+ }
+
+ // Extract object name from .versions directory name (remove .versions suffix)
+ baseObjectName := strings.TrimSuffix(versionsDir, ".versions")
+
+ // Construct full object path relative to bucket
+ // dir is something like "/buckets/sea-test-1/Veeam/Backup/vbr/Config"
+ // we need to get the path relative to bucket: "Veeam/Backup/vbr/Config/Owner"
+ bucketPath := strings.TrimPrefix(dir, s3a.option.BucketsPath+"/")
+ bucketName := strings.Split(bucketPath, "/")[0]
+
+ // Remove bucket name from path to get directory within bucket
+ bucketRelativePath := strings.Join(strings.Split(bucketPath, "/")[1:], "/")
+
+ var fullObjectPath string
+ if bucketRelativePath == "" {
+ // Object is at bucket root
+ fullObjectPath = baseObjectName
+ } else {
+ // Object is in subdirectory
+ fullObjectPath = bucketRelativePath + "/" + baseObjectName
+ }
+
+ glog.V(4).Infof("Processing versioned object: baseObjectName=%s, bucketRelativePath=%s, fullObjectPath=%s",
+ baseObjectName, bucketRelativePath, fullObjectPath)
+
+ // Get the latest version information for this object
+ if latestVersionEntry, latestVersionErr := s3a.getLatestVersionEntryForListOperation(bucketName, fullObjectPath); latestVersionErr == nil {
+ glog.V(4).Infof("Creating logical entry for versioned object: %s", fullObjectPath)
+ eachEntryFn(dir, latestVersionEntry)
+ } else {
+ glog.V(4).Infof("Failed to get latest version for %s: %v", fullObjectPath, latestVersionErr)
+ }
+ }
+
return
}
@@ -513,3 +567,32 @@ func (s3a *S3ApiServer) ensureDirectoryAllEmpty(filerClient filer_pb.SeaweedFile
return true, nil
}
+
+// getLatestVersionEntryForListOperation gets the latest version of an object and creates a logical entry for list operations
+// This is used to show versioned objects as logical object names in regular list operations
+func (s3a *S3ApiServer) getLatestVersionEntryForListOperation(bucket, object string) (*filer_pb.Entry, error) {
+ // Get the latest version entry
+ latestVersionEntry, err := s3a.getLatestObjectVersion(bucket, object)
+ if err != nil {
+ return nil, fmt.Errorf("failed to get latest version: %w", err)
+ }
+
+ // Check if this is a delete marker (should not be shown in regular list)
+ if latestVersionEntry.Extended != nil {
+ if deleteMarker, exists := latestVersionEntry.Extended[s3_constants.ExtDeleteMarkerKey]; exists && string(deleteMarker) == "true" {
+ return nil, fmt.Errorf("latest version is a delete marker")
+ }
+ }
+
+ // Create a logical entry that appears to be stored at the object path (not the versioned path)
+ // This allows the list operation to show the logical object name while preserving all metadata
+ logicalEntry := &filer_pb.Entry{
+ Name: strings.TrimPrefix(object, "/"),
+ IsDirectory: false,
+ Attributes: latestVersionEntry.Attributes,
+ Extended: latestVersionEntry.Extended,
+ Chunks: latestVersionEntry.Chunks,
+ }
+
+ return logicalEntry, nil
+}