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 }