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  }