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  }