diff options
| author | Chris Lu <chrislusf@users.noreply.github.com> | 2025-07-21 00:23:22 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-07-21 00:23:22 -0700 |
| commit | c196d03951a75d3b8976f556cb0400e5b522edeb (patch) | |
| tree | 21618d3645e44fd763648067b1db900cd3fa985e /weed/s3api/s3api_object_handlers_list.go | |
| parent | bfe68984d5b5dff29083aeb6f79401a530feb510 (diff) | |
| download | seaweedfs-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.go | 93 |
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 +} |
