github.com/myhau/pulumi/pkg/v3@v3.70.2-0.20221116134521-f2775972e587/backend/filestate/bucket.go (about)

     1  package filestate
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io"
     7  	"path"
     8  	"path/filepath"
     9  
    10  	"github.com/pulumi/pulumi/sdk/v3/go/common/util/logging"
    11  	"gocloud.dev/blob"
    12  )
    13  
    14  // Bucket is a wrapper around an underlying gocloud blob.Bucket.  It ensures that we pass all paths
    15  // to it normalized to forward-slash form like it requires.
    16  type Bucket interface {
    17  	Copy(ctx context.Context, dstKey, srcKey string, opts *blob.CopyOptions) (err error)
    18  	Delete(ctx context.Context, key string) (err error)
    19  	List(opts *blob.ListOptions) *blob.ListIterator
    20  	SignedURL(ctx context.Context, key string, opts *blob.SignedURLOptions) (string, error)
    21  	ReadAll(ctx context.Context, key string) (_ []byte, err error)
    22  	WriteAll(ctx context.Context, key string, p []byte, opts *blob.WriterOptions) (err error)
    23  	Exists(ctx context.Context, key string) (bool, error)
    24  }
    25  
    26  // wrappedBucket encapsulates a true gocloud blob.Bucket, but ensures that all paths we send to it
    27  // are appropriately normalized to use forward slashes as required by it.  Without this, we may use
    28  // filepath.join which can make paths like `c:\temp\etc`.  gocloud's fileblob then converts those
    29  // backslashes to the hex string __0x5c__, breaking things on windows completely.
    30  type wrappedBucket struct {
    31  	bucket *blob.Bucket
    32  }
    33  
    34  func (b *wrappedBucket) Copy(ctx context.Context, dstKey, srcKey string, opts *blob.CopyOptions) (err error) {
    35  	return b.bucket.Copy(ctx, filepath.ToSlash(dstKey), filepath.ToSlash(srcKey), opts)
    36  }
    37  
    38  func (b *wrappedBucket) Delete(ctx context.Context, key string) (err error) {
    39  	return b.bucket.Delete(ctx, filepath.ToSlash(key))
    40  }
    41  
    42  func (b *wrappedBucket) List(opts *blob.ListOptions) *blob.ListIterator {
    43  	optsCopy := *opts
    44  	optsCopy.Prefix = filepath.ToSlash(opts.Prefix)
    45  	return b.bucket.List(&optsCopy)
    46  }
    47  
    48  func (b *wrappedBucket) SignedURL(ctx context.Context, key string, opts *blob.SignedURLOptions) (string, error) {
    49  	return b.bucket.SignedURL(ctx, filepath.ToSlash(key), opts)
    50  }
    51  
    52  func (b *wrappedBucket) ReadAll(ctx context.Context, key string) (_ []byte, err error) {
    53  	return b.bucket.ReadAll(ctx, filepath.ToSlash(key))
    54  }
    55  
    56  func (b *wrappedBucket) WriteAll(ctx context.Context, key string, p []byte, opts *blob.WriterOptions) (err error) {
    57  	return b.bucket.WriteAll(ctx, filepath.ToSlash(key), p, opts)
    58  }
    59  
    60  func (b *wrappedBucket) Exists(ctx context.Context, key string) (bool, error) {
    61  	return b.bucket.Exists(ctx, filepath.ToSlash(key))
    62  }
    63  
    64  // listBucket returns a list of all files in the bucket within a given directory. go-cloud sorts the results by key
    65  func listBucket(bucket Bucket, dir string) ([]*blob.ListObject, error) {
    66  	bucketIter := bucket.List(&blob.ListOptions{
    67  		Delimiter: "/",
    68  		Prefix:    dir + "/",
    69  	})
    70  
    71  	files := []*blob.ListObject{}
    72  
    73  	ctx := context.TODO()
    74  	for {
    75  		file, err := bucketIter.Next(ctx)
    76  		if err == io.EOF {
    77  			break
    78  		}
    79  		if err != nil {
    80  			return nil, fmt.Errorf("could not list bucket: %w", err)
    81  		}
    82  		files = append(files, file)
    83  	}
    84  
    85  	return files, nil
    86  }
    87  
    88  // objectName returns the filename of a ListObject (an object from a bucket).
    89  func objectName(obj *blob.ListObject) string {
    90  	_, filename := path.Split(obj.Key)
    91  	return filename
    92  }
    93  
    94  // removeAllByPrefix deletes all objects with a given prefix (i.e. filepath)
    95  func removeAllByPrefix(bucket Bucket, dir string) error {
    96  	files, err := listBucket(bucket, dir)
    97  	if err != nil {
    98  		return fmt.Errorf("unable to list bucket objects for removal: %w", err)
    99  	}
   100  
   101  	for _, file := range files {
   102  		err = bucket.Delete(context.TODO(), file.Key)
   103  		if err != nil {
   104  			logging.V(5).Infof("error deleting object: %v (%v) skipping", file.Key, err)
   105  		}
   106  	}
   107  
   108  	return nil
   109  }