131 lines
3.6 KiB
Go
131 lines
3.6 KiB
Go
package rm
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
|
|
"github.com/go-errors/errors"
|
|
"github.com/spf13/afero"
|
|
"github.com/supabase/cli/internal/storage/client"
|
|
"github.com/supabase/cli/internal/storage/cp"
|
|
"github.com/supabase/cli/internal/storage/ls"
|
|
"github.com/supabase/cli/internal/utils"
|
|
"github.com/supabase/cli/internal/utils/flags"
|
|
"github.com/supabase/cli/pkg/storage"
|
|
)
|
|
|
|
var (
|
|
errMissingObject = errors.New("Object not found")
|
|
errMissingBucket = errors.New("You must specify a bucket to delete.")
|
|
errMissingFlag = errors.New("You must specify -r flag to delete directories.")
|
|
)
|
|
|
|
type PrefixGroup struct {
|
|
Bucket string
|
|
Prefixes []string
|
|
}
|
|
|
|
func Run(ctx context.Context, paths []string, recursive bool, fsys afero.Fs) error {
|
|
// Group paths by buckets
|
|
groups := map[string][]string{}
|
|
for _, objectPath := range paths {
|
|
remotePath, err := client.ParseStorageURL(objectPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
bucket, prefix := client.SplitBucketPrefix(remotePath)
|
|
// Ignore attempts to delete all buckets
|
|
if len(bucket) == 0 {
|
|
return errors.New(errMissingBucket)
|
|
}
|
|
if cp.IsDir(prefix) && !recursive {
|
|
return errors.New(errMissingFlag)
|
|
}
|
|
groups[bucket] = append(groups[bucket], prefix)
|
|
}
|
|
api, err := client.NewStorageAPI(ctx, flags.ProjectRef)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for bucket, prefixes := range groups {
|
|
confirm := fmt.Sprintf("Confirm deleting files in bucket %v?", utils.Bold(bucket))
|
|
if shouldDelete, err := utils.NewConsole().PromptYesNo(ctx, confirm, false); err != nil {
|
|
return err
|
|
} else if !shouldDelete {
|
|
continue
|
|
}
|
|
// Always try deleting first in case the paths resolve to extensionless files
|
|
fmt.Fprintln(os.Stderr, "Deleting objects:", prefixes)
|
|
removed, err := api.DeleteObjects(ctx, bucket, prefixes)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
set := map[string]struct{}{}
|
|
for _, object := range removed {
|
|
set[object.Name] = struct{}{}
|
|
}
|
|
for _, prefix := range prefixes {
|
|
if _, ok := set[prefix]; ok {
|
|
continue
|
|
}
|
|
if !recursive {
|
|
fmt.Fprintln(os.Stderr, "Object not found:", prefix)
|
|
continue
|
|
}
|
|
if len(prefix) > 0 {
|
|
prefix += "/"
|
|
}
|
|
if err := RemoveStoragePathAll(ctx, api, bucket, prefix); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Expects prefix to be terminated by "/" or ""
|
|
func RemoveStoragePathAll(ctx context.Context, api storage.StorageAPI, bucket, prefix string) error {
|
|
// We must remove one directory at a time to avoid breaking pagination result
|
|
queue := make([]string, 0)
|
|
queue = append(queue, prefix)
|
|
for len(queue) > 0 {
|
|
dirPrefix := queue[len(queue)-1]
|
|
queue = queue[:len(queue)-1]
|
|
paths, err := ls.ListStoragePaths(ctx, api, fmt.Sprintf("/%s/%s", bucket, dirPrefix))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(paths) == 0 && len(prefix) > 0 {
|
|
return errors.Errorf("%w: %s/%s", errMissingObject, bucket, prefix)
|
|
}
|
|
var files []string
|
|
for _, objectName := range paths {
|
|
objectPrefix := dirPrefix + objectName
|
|
if strings.HasSuffix(objectName, "/") {
|
|
queue = append(queue, objectPrefix)
|
|
} else {
|
|
files = append(files, objectPrefix)
|
|
}
|
|
}
|
|
if len(files) > 0 {
|
|
fmt.Fprintln(os.Stderr, "Deleting objects:", files)
|
|
if _, err := api.DeleteObjects(ctx, bucket, files); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
if len(prefix) == 0 {
|
|
fmt.Fprintln(os.Stderr, "Deleting bucket:", bucket)
|
|
if data, err := api.DeleteBucket(ctx, bucket); err == nil {
|
|
fmt.Fprintln(os.Stderr, data.Message)
|
|
} else if strings.Contains(err.Error(), `"error":"Bucket not found"`) {
|
|
fmt.Fprintln(os.Stderr, "Bucket not found:", bucket)
|
|
} else {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|