storj.io/uplink@v1.13.0/bucket.go (about) 1 // Copyright (C) 2020 Storj Labs, Inc. 2 // See LICENSE for copying information. 3 4 package uplink 5 6 import ( 7 "context" 8 "errors" 9 "time" 10 11 "github.com/zeebo/errs" 12 13 "storj.io/common/errs2" 14 "storj.io/common/rpc/rpcstatus" 15 "storj.io/uplink/private/metaclient" 16 ) 17 18 // ErrBucketNameInvalid is returned when the bucket name is invalid. 19 var ErrBucketNameInvalid = errors.New("bucket name invalid") 20 21 // ErrBucketAlreadyExists is returned when the bucket already exists during creation. 22 var ErrBucketAlreadyExists = errors.New("bucket already exists") 23 24 // ErrBucketNotEmpty is returned when the bucket is not empty during deletion. 25 var ErrBucketNotEmpty = errors.New("bucket not empty") 26 27 // ErrBucketNotFound is returned when the bucket is not found. 28 var ErrBucketNotFound = errors.New("bucket not found") 29 30 // Bucket contains information about the bucket. 31 type Bucket struct { 32 Name string 33 Created time.Time 34 } 35 36 // StatBucket returns information about a bucket. 37 func (project *Project) StatBucket(ctx context.Context, bucket string) (info *Bucket, err error) { 38 defer mon.Task()(&ctx)(&err) 39 40 db, err := project.dialMetainfoDB(ctx) 41 if err != nil { 42 return nil, convertKnownErrors(err, bucket, "") 43 } 44 defer func() { err = errs.Combine(err, db.Close()) }() 45 46 b, err := db.GetBucket(ctx, bucket) 47 if err != nil { 48 return nil, convertKnownErrors(err, bucket, "") 49 } 50 51 return &Bucket{ 52 Name: b.Name, 53 Created: b.Created, 54 }, nil 55 } 56 57 // CreateBucket creates a new bucket. 58 // 59 // When bucket already exists it returns a valid Bucket and ErrBucketExists. 60 func (project *Project) CreateBucket(ctx context.Context, bucket string) (created *Bucket, err error) { 61 defer mon.Task()(&ctx)(&err) 62 63 db, err := project.dialMetainfoDB(ctx) 64 if err != nil { 65 return nil, convertKnownErrors(err, bucket, "") 66 } 67 defer func() { err = errs.Combine(err, db.Close()) }() 68 69 b, err := db.CreateBucket(ctx, bucket) 70 if err != nil { 71 if metaclient.ErrNoBucket.Has(err) { 72 return nil, errwrapf("%w (%q)", ErrBucketNameInvalid, bucket) 73 } 74 if errs2.IsRPC(err, rpcstatus.AlreadyExists) { 75 // TODO: Ideally, the satellite should return the existing bucket when this error occurs. 76 existing, err := project.StatBucket(ctx, bucket) 77 if err != nil { 78 return existing, errs.Combine(errwrapf("%w (%q)", ErrBucketAlreadyExists, bucket), convertKnownErrors(err, bucket, "")) 79 } 80 return existing, errwrapf("%w (%q)", ErrBucketAlreadyExists, bucket) 81 } 82 if errs2.IsRPC(err, rpcstatus.InvalidArgument) { 83 return nil, errwrapf("%w (%q)", ErrBucketNameInvalid, bucket) 84 } 85 return nil, convertKnownErrors(err, bucket, "") 86 } 87 88 return &Bucket{ 89 Name: b.Name, 90 Created: b.Created, 91 }, nil 92 } 93 94 // EnsureBucket ensures that a bucket exists or creates a new one. 95 // 96 // When bucket already exists it returns a valid Bucket and no error. 97 func (project *Project) EnsureBucket(ctx context.Context, bucket string) (ensured *Bucket, err error) { 98 defer mon.Task()(&ctx)(&err) 99 100 ensured, err = project.CreateBucket(ctx, bucket) 101 if err != nil && !errors.Is(err, ErrBucketAlreadyExists) { 102 return nil, convertKnownErrors(err, bucket, "") 103 } 104 105 return ensured, nil 106 } 107 108 // DeleteBucket deletes a bucket. 109 // 110 // When bucket is not empty it returns ErrBucketNotEmpty. 111 func (project *Project) DeleteBucket(ctx context.Context, bucket string) (deleted *Bucket, err error) { 112 defer mon.Task()(&ctx)(&err) 113 114 db, err := project.dialMetainfoDB(ctx) 115 if err != nil { 116 return nil, convertKnownErrors(err, bucket, "") 117 } 118 defer func() { err = errs.Combine(err, db.Close()) }() 119 120 existing, err := db.DeleteBucket(ctx, bucket, false) 121 if err != nil { 122 if errs2.IsRPC(err, rpcstatus.FailedPrecondition) { 123 return nil, errwrapf("%w (%q)", ErrBucketNotEmpty, bucket) 124 } 125 return nil, convertKnownErrors(err, bucket, "") 126 } 127 128 if len(existing.Name) == 0 { 129 return &Bucket{Name: bucket}, nil 130 } 131 132 return &Bucket{ 133 Name: existing.Name, 134 Created: existing.Created, 135 }, nil 136 } 137 138 // DeleteBucketWithObjects deletes a bucket and all objects within that bucket. 139 func (project *Project) DeleteBucketWithObjects(ctx context.Context, bucket string) (deleted *Bucket, err error) { 140 defer mon.Task()(&ctx)(&err) 141 142 db, err := project.dialMetainfoDB(ctx) 143 if err != nil { 144 return nil, convertKnownErrors(err, bucket, "") 145 } 146 defer func() { err = errs.Combine(err, db.Close()) }() 147 148 existing, err := db.DeleteBucket(ctx, bucket, true) 149 if err != nil { 150 return nil, convertKnownErrors(err, bucket, "") 151 } 152 153 if len(existing.Name) == 0 { 154 return &Bucket{Name: bucket}, nil 155 } 156 157 return &Bucket{ 158 Name: existing.Name, 159 Created: existing.Created, 160 }, nil 161 }