From ddac7ea41a888c8fb4f96f805fce5c89a264a722 Mon Sep 17 00:00:00 2001 From: Jamie Banks Date: Wed, 10 Jun 2026 23:44:50 +0100 Subject: [PATCH] feat: update S3 delete operation to recursively remove objects under a directory prefix --- internal/protocols/s3/s3.go | 53 ++++++++++++++++++++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/internal/protocols/s3/s3.go b/internal/protocols/s3/s3.go index cbded41..3bcd7e2 100644 --- a/internal/protocols/s3/s3.go +++ b/internal/protocols/s3/s3.go @@ -12,6 +12,7 @@ import ( "github.com/aws/aws-sdk-go-v2/config" "github.com/aws/aws-sdk-go-v2/credentials" "github.com/aws/aws-sdk-go-v2/service/s3" + "github.com/aws/aws-sdk-go-v2/service/s3/types" appcfg "goexplore/internal/config" "goexplore/internal/explorer" ) @@ -243,11 +244,61 @@ func (e *S3Explorer) Delete(path string) error { return err } + // Try to delete the exact object (might be a file or a folder marker) _, err = e.client.DeleteObject(context.TODO(), &s3.DeleteObjectInput{ Bucket: aws.String(bucket), Key: aws.String(subpath), }) - return err + if err != nil { + // Ignore error; it might just be a prefix with no exact marker + } + + // Also treat subpath as a folder prefix and delete all matching objects + prefix := subpath + if !strings.HasSuffix(prefix, "/") { + prefix += "/" + } + + var continuationToken *string + for { + listInput := &s3.ListObjectsV2Input{ + Bucket: aws.String(bucket), + Prefix: aws.String(prefix), + ContinuationToken: continuationToken, + } + listOutput, err := e.client.ListObjectsV2(context.TODO(), listInput) + if err != nil { + return err + } + + if len(listOutput.Contents) > 0 { + var objectsToDelete []types.ObjectIdentifier + for _, obj := range listOutput.Contents { + objectsToDelete = append(objectsToDelete, types.ObjectIdentifier{ + Key: obj.Key, + }) + } + + _, err = e.client.DeleteObjects(context.TODO(), &s3.DeleteObjectsInput{ + Bucket: aws.String(bucket), + Delete: &types.Delete{ + Objects: objectsToDelete, + Quiet: aws.Bool(true), + }, + }) + if err != nil { + return err + } + } + + if listOutput.IsTruncated != nil && *listOutput.IsTruncated { + continuationToken = listOutput.NextContinuationToken + } else { + break + } + } + + return nil } func (e *S3Explorer) Rename(src, dst string) error {