github.com/supabase/cli@v1.168.1/internal/storage/rm/rm.go (about) 1 package rm 2 3 import ( 4 "context" 5 "fmt" 6 "os" 7 "strings" 8 9 "github.com/go-errors/errors" 10 "github.com/spf13/afero" 11 "github.com/supabase/cli/internal/storage/client" 12 "github.com/supabase/cli/internal/storage/cp" 13 "github.com/supabase/cli/internal/storage/ls" 14 "github.com/supabase/cli/internal/utils" 15 "github.com/supabase/cli/internal/utils/flags" 16 "github.com/supabase/cli/pkg/storage" 17 ) 18 19 var ( 20 errMissingObject = errors.New("Object not found") 21 errMissingBucket = errors.New("You must specify a bucket to delete.") 22 errMissingFlag = errors.New("You must specify -r flag to delete directories.") 23 ) 24 25 type PrefixGroup struct { 26 Bucket string 27 Prefixes []string 28 } 29 30 func Run(ctx context.Context, paths []string, recursive bool, fsys afero.Fs) error { 31 // Group paths by buckets 32 groups := map[string][]string{} 33 for _, objectPath := range paths { 34 remotePath, err := client.ParseStorageURL(objectPath) 35 if err != nil { 36 return err 37 } 38 bucket, prefix := client.SplitBucketPrefix(remotePath) 39 // Ignore attempts to delete all buckets 40 if len(bucket) == 0 { 41 return errors.New(errMissingBucket) 42 } 43 if cp.IsDir(prefix) && !recursive { 44 return errors.New(errMissingFlag) 45 } 46 groups[bucket] = append(groups[bucket], prefix) 47 } 48 api, err := client.NewStorageAPI(ctx, flags.ProjectRef) 49 if err != nil { 50 return err 51 } 52 for bucket, prefixes := range groups { 53 confirm := fmt.Sprintf("Confirm deleting files in bucket %v?", utils.Bold(bucket)) 54 if shouldDelete := utils.NewConsole().PromptYesNo(confirm, false); !shouldDelete { 55 continue 56 } 57 // Always try deleting first in case the paths resolve to extensionless files 58 fmt.Fprintln(os.Stderr, "Deleting objects:", prefixes) 59 removed, err := api.DeleteObjects(ctx, bucket, prefixes) 60 if err != nil { 61 return err 62 } 63 set := map[string]struct{}{} 64 for _, object := range removed { 65 set[object.Name] = struct{}{} 66 } 67 for _, prefix := range prefixes { 68 if _, ok := set[prefix]; ok { 69 continue 70 } 71 if !recursive { 72 fmt.Fprintln(os.Stderr, "Object not found:", prefix) 73 continue 74 } 75 if len(prefix) > 0 { 76 prefix += "/" 77 } 78 if err := RemoveStoragePathAll(ctx, api, bucket, prefix); err != nil { 79 return err 80 } 81 } 82 } 83 return nil 84 } 85 86 // Expects prefix to be terminated by "/" or "" 87 func RemoveStoragePathAll(ctx context.Context, api storage.StorageAPI, bucket, prefix string) error { 88 // We must remove one directory at a time to avoid breaking pagination result 89 queue := make([]string, 0) 90 queue = append(queue, prefix) 91 for len(queue) > 0 { 92 dirPrefix := queue[len(queue)-1] 93 queue = queue[:len(queue)-1] 94 paths, err := ls.ListStoragePaths(ctx, api, fmt.Sprintf("/%s/%s", bucket, dirPrefix)) 95 if err != nil { 96 return err 97 } 98 if len(paths) == 0 && len(prefix) > 0 { 99 return errors.Errorf("%w: %s/%s", errMissingObject, bucket, prefix) 100 } 101 var files []string 102 for _, objectName := range paths { 103 objectPrefix := dirPrefix + objectName 104 if strings.HasSuffix(objectName, "/") { 105 queue = append(queue, objectPrefix) 106 } else { 107 files = append(files, objectPrefix) 108 } 109 } 110 if len(files) > 0 { 111 fmt.Fprintln(os.Stderr, "Deleting objects:", files) 112 if _, err := api.DeleteObjects(ctx, bucket, files); err != nil { 113 return err 114 } 115 } 116 } 117 if len(prefix) == 0 { 118 fmt.Fprintln(os.Stderr, "Deleting bucket:", bucket) 119 if data, err := api.DeleteBucket(ctx, bucket); err == nil { 120 fmt.Fprintln(os.Stderr, data.Message) 121 } else if strings.Contains(err.Error(), `"error":"Bucket not found"`) { 122 fmt.Fprintln(os.Stderr, "Bucket not found:", bucket) 123 } else { 124 return err 125 } 126 } 127 return nil 128 }