Switch empty-folder cleanup to bucket policy (#8292)

* Fix Spark _temporary cleanup and add issue #8285 regression test

* Generalize empty folder cleanup for Spark temp artifacts

* Revert synchronous folder pruning and add cleanup diagnostics

* Add actionable empty-folder cleanup diagnostics

* Fix Spark temp marker cleanup in async folder cleaner

* Fix Spark temp cleanup with implicit directory markers

* Keep explicit directory markers non-implicit

* logging

* more logs

* Switch empty-folder cleanup to bucket policy

* Seaweed-X-Amz-Allow-Empty-Folders

* less logs

* go vet

* less logs

* refactoring
This commit is contained in:
Chris Lu
2026-02-10 18:38:38 -08:00
committed by GitHub
parent 5c365e7090
commit b57429ef2e
16 changed files with 798 additions and 157 deletions

33
weed/util/buckets.go Normal file
View File

@@ -0,0 +1,33 @@
package util
import "strings"
// ExtractBucketPath returns the bucket path under basePath that contains target.
// If requireChild is true, the target must include additional segments beyond the bucket itself.
func ExtractBucketPath(basePath, target string, requireChild bool) (string, bool) {
cleanBase := strings.TrimSuffix(basePath, "/")
if cleanBase == "" {
return "", false
}
prefix := cleanBase + "/"
if !strings.HasPrefix(target, prefix) {
return "", false
}
rest := strings.TrimPrefix(target, prefix)
if rest == "" {
return "", false
}
bucketName, _, found := strings.Cut(rest, "/")
if bucketName == "" {
return "", false
}
if requireChild && !found {
return "", false
}
return prefix + bucketName, true
}

63
weed/util/buckets_test.go Normal file
View File

@@ -0,0 +1,63 @@
package util
import "testing"
func TestExtractBucketPath(t *testing.T) {
for _, tt := range []struct {
name string
base string
target string
requireChild bool
expected string
ok bool
}{
{
name: "child paths return bucket",
base: "/buckets",
target: "/buckets/test/folder/file",
requireChild: true,
expected: "/buckets/test",
ok: true,
},
{
name: "bucket root without child fails when required",
base: "/buckets",
target: "/buckets/test",
requireChild: true,
ok: false,
},
{
name: "bucket root allowed when not required",
base: "/buckets",
target: "/buckets/test",
requireChild: false,
expected: "/buckets/test",
ok: true,
},
{
name: "path outside buckets fails",
base: "/buckets",
target: "/data/test/folder",
requireChild: true,
ok: false,
},
{
name: "trailing slash on base is normalized",
base: "/buckets/",
target: "/buckets/test/sub",
requireChild: true,
expected: "/buckets/test",
ok: true,
},
} {
t.Run(tt.name, func(t *testing.T) {
got, ok := ExtractBucketPath(tt.base, tt.target, tt.requireChild)
if ok != tt.ok {
t.Fatalf("expected ok=%v, got %v", tt.ok, ok)
}
if got != tt.expected {
t.Fatalf("expected path %q, got %q", tt.expected, got)
}
})
}
}