supabase-cli/internal/storage/rm/rm.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
}