github.com/aavshr/aws-sdk-go@v1.41.3/service/s3/s3manager/batch.go (about)

     1  package s3manager
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io"
     7  
     8  	"github.com/aavshr/aws-sdk-go/aws"
     9  	"github.com/aavshr/aws-sdk-go/aws/awserr"
    10  	"github.com/aavshr/aws-sdk-go/aws/client"
    11  	"github.com/aavshr/aws-sdk-go/aws/request"
    12  	"github.com/aavshr/aws-sdk-go/service/s3"
    13  	"github.com/aavshr/aws-sdk-go/service/s3/s3iface"
    14  )
    15  
    16  const (
    17  	// DefaultBatchSize is the batch size we initialize when constructing a batch delete client.
    18  	// This value is used when calling DeleteObjects. This represents how many objects to delete
    19  	// per DeleteObjects call.
    20  	DefaultBatchSize = 100
    21  )
    22  
    23  // BatchError will contain the key and bucket of the object that failed to
    24  // either upload or download.
    25  type BatchError struct {
    26  	Errors  Errors
    27  	code    string
    28  	message string
    29  }
    30  
    31  // Errors is a typed alias for a slice of errors to satisfy the error
    32  // interface.
    33  type Errors []Error
    34  
    35  func (errs Errors) Error() string {
    36  	buf := bytes.NewBuffer(nil)
    37  	for i, err := range errs {
    38  		buf.WriteString(err.Error())
    39  		if i+1 < len(errs) {
    40  			buf.WriteString("\n")
    41  		}
    42  	}
    43  	return buf.String()
    44  }
    45  
    46  // Error will contain the original error, bucket, and key of the operation that failed
    47  // during batch operations.
    48  type Error struct {
    49  	OrigErr error
    50  	Bucket  *string
    51  	Key     *string
    52  }
    53  
    54  func newError(err error, bucket, key *string) Error {
    55  	return Error{
    56  		err,
    57  		bucket,
    58  		key,
    59  	}
    60  }
    61  
    62  func (err *Error) Error() string {
    63  	origErr := ""
    64  	if err.OrigErr != nil {
    65  		origErr = ":\n" + err.OrigErr.Error()
    66  	}
    67  	return fmt.Sprintf("failed to perform batch operation on %q to %q%s",
    68  		aws.StringValue(err.Key),
    69  		aws.StringValue(err.Bucket),
    70  		origErr,
    71  	)
    72  }
    73  
    74  // NewBatchError will return a BatchError that satisfies the awserr.Error interface.
    75  func NewBatchError(code, message string, err []Error) awserr.Error {
    76  	return &BatchError{
    77  		Errors:  err,
    78  		code:    code,
    79  		message: message,
    80  	}
    81  }
    82  
    83  // Code will return the code associated with the batch error.
    84  func (err *BatchError) Code() string {
    85  	return err.code
    86  }
    87  
    88  // Message will return the message associated with the batch error.
    89  func (err *BatchError) Message() string {
    90  	return err.message
    91  }
    92  
    93  func (err *BatchError) Error() string {
    94  	return awserr.SprintError(err.Code(), err.Message(), "", err.Errors)
    95  }
    96  
    97  // OrigErr will return the original error. Which, in this case, will always be nil
    98  // for batched operations.
    99  func (err *BatchError) OrigErr() error {
   100  	return err.Errors
   101  }
   102  
   103  // BatchDeleteIterator is an interface that uses the scanner pattern to
   104  // iterate through what needs to be deleted.
   105  type BatchDeleteIterator interface {
   106  	Next() bool
   107  	Err() error
   108  	DeleteObject() BatchDeleteObject
   109  }
   110  
   111  // DeleteListIterator is an alternative iterator for the BatchDelete client. This will
   112  // iterate through a list of objects and delete the objects.
   113  //
   114  // Example:
   115  //	iter := &s3manager.DeleteListIterator{
   116  //		Client: svc,
   117  //		Input: &s3.ListObjectsInput{
   118  //			Bucket:  aws.String("bucket"),
   119  //			MaxKeys: aws.Int64(5),
   120  //		},
   121  //		Paginator: request.Pagination{
   122  //			NewRequest: func() (*request.Request, error) {
   123  //				var inCpy *ListObjectsInput
   124  //				if input != nil {
   125  //					tmp := *input
   126  //					inCpy = &tmp
   127  //				}
   128  //				req, _ := c.ListObjectsRequest(inCpy)
   129  //				return req, nil
   130  //			},
   131  //		},
   132  //	}
   133  //
   134  //	batcher := s3manager.NewBatchDeleteWithClient(svc)
   135  //	if err := batcher.Delete(aws.BackgroundContext(), iter); err != nil {
   136  //		return err
   137  //	}
   138  type DeleteListIterator struct {
   139  	Bucket    *string
   140  	Paginator request.Pagination
   141  	objects   []*s3.Object
   142  }
   143  
   144  // NewDeleteListIterator will return a new DeleteListIterator.
   145  func NewDeleteListIterator(svc s3iface.S3API, input *s3.ListObjectsInput, opts ...func(*DeleteListIterator)) BatchDeleteIterator {
   146  	iter := &DeleteListIterator{
   147  		Bucket: input.Bucket,
   148  		Paginator: request.Pagination{
   149  			NewRequest: func() (*request.Request, error) {
   150  				var inCpy *s3.ListObjectsInput
   151  				if input != nil {
   152  					tmp := *input
   153  					inCpy = &tmp
   154  				}
   155  				req, _ := svc.ListObjectsRequest(inCpy)
   156  				return req, nil
   157  			},
   158  		},
   159  	}
   160  
   161  	for _, opt := range opts {
   162  		opt(iter)
   163  	}
   164  	return iter
   165  }
   166  
   167  // Next will use the S3API client to iterate through a list of objects.
   168  func (iter *DeleteListIterator) Next() bool {
   169  	if len(iter.objects) > 0 {
   170  		iter.objects = iter.objects[1:]
   171  	}
   172  
   173  	if len(iter.objects) == 0 && iter.Paginator.Next() {
   174  		iter.objects = iter.Paginator.Page().(*s3.ListObjectsOutput).Contents
   175  	}
   176  
   177  	return len(iter.objects) > 0
   178  }
   179  
   180  // Err will return the last known error from Next.
   181  func (iter *DeleteListIterator) Err() error {
   182  	return iter.Paginator.Err()
   183  }
   184  
   185  // DeleteObject will return the current object to be deleted.
   186  func (iter *DeleteListIterator) DeleteObject() BatchDeleteObject {
   187  	return BatchDeleteObject{
   188  		Object: &s3.DeleteObjectInput{
   189  			Bucket: iter.Bucket,
   190  			Key:    iter.objects[0].Key,
   191  		},
   192  	}
   193  }
   194  
   195  // BatchDelete will use the s3 package's service client to perform a batch
   196  // delete.
   197  type BatchDelete struct {
   198  	Client    s3iface.S3API
   199  	BatchSize int
   200  }
   201  
   202  // NewBatchDeleteWithClient will return a new delete client that can delete a batched amount of
   203  // objects.
   204  //
   205  // Example:
   206  //	batcher := s3manager.NewBatchDeleteWithClient(client, size)
   207  //
   208  //	objects := []BatchDeleteObject{
   209  //		{
   210  //			Object:	&s3.DeleteObjectInput {
   211  //				Key: aws.String("key"),
   212  //				Bucket: aws.String("bucket"),
   213  //			},
   214  //		},
   215  //	}
   216  //
   217  //	if err := batcher.Delete(aws.BackgroundContext(), &s3manager.DeleteObjectsIterator{
   218  //		Objects: objects,
   219  //	}); err != nil {
   220  //		return err
   221  //	}
   222  func NewBatchDeleteWithClient(client s3iface.S3API, options ...func(*BatchDelete)) *BatchDelete {
   223  	svc := &BatchDelete{
   224  		Client:    client,
   225  		BatchSize: DefaultBatchSize,
   226  	}
   227  
   228  	for _, opt := range options {
   229  		opt(svc)
   230  	}
   231  
   232  	return svc
   233  }
   234  
   235  // NewBatchDelete will return a new delete client that can delete a batched amount of
   236  // objects.
   237  //
   238  // Example:
   239  //	batcher := s3manager.NewBatchDelete(sess, size)
   240  //
   241  //	objects := []BatchDeleteObject{
   242  //		{
   243  //			Object:	&s3.DeleteObjectInput {
   244  //				Key: aws.String("key"),
   245  //				Bucket: aws.String("bucket"),
   246  //			},
   247  //		},
   248  //	}
   249  //
   250  //	if err := batcher.Delete(aws.BackgroundContext(), &s3manager.DeleteObjectsIterator{
   251  //		Objects: objects,
   252  //	}); err != nil {
   253  //		return err
   254  //	}
   255  func NewBatchDelete(c client.ConfigProvider, options ...func(*BatchDelete)) *BatchDelete {
   256  	client := s3.New(c)
   257  	return NewBatchDeleteWithClient(client, options...)
   258  }
   259  
   260  // BatchDeleteObject is a wrapper object for calling the batch delete operation.
   261  type BatchDeleteObject struct {
   262  	Object *s3.DeleteObjectInput
   263  	// After will run after each iteration during the batch process. This function will
   264  	// be executed whether or not the request was successful.
   265  	After func() error
   266  }
   267  
   268  // DeleteObjectsIterator is an interface that uses the scanner pattern to iterate
   269  // through a series of objects to be deleted.
   270  type DeleteObjectsIterator struct {
   271  	Objects []BatchDeleteObject
   272  	index   int
   273  	inc     bool
   274  }
   275  
   276  // Next will increment the default iterator's index and ensure that there
   277  // is another object to iterator to.
   278  func (iter *DeleteObjectsIterator) Next() bool {
   279  	if iter.inc {
   280  		iter.index++
   281  	} else {
   282  		iter.inc = true
   283  	}
   284  	return iter.index < len(iter.Objects)
   285  }
   286  
   287  // Err will return an error. Since this is just used to satisfy the BatchDeleteIterator interface
   288  // this will only return nil.
   289  func (iter *DeleteObjectsIterator) Err() error {
   290  	return nil
   291  }
   292  
   293  // DeleteObject will return the BatchDeleteObject at the current batched index.
   294  func (iter *DeleteObjectsIterator) DeleteObject() BatchDeleteObject {
   295  	object := iter.Objects[iter.index]
   296  	return object
   297  }
   298  
   299  // Delete will use the iterator to queue up objects that need to be deleted.
   300  // Once the batch size is met, this will call the deleteBatch function.
   301  func (d *BatchDelete) Delete(ctx aws.Context, iter BatchDeleteIterator) error {
   302  	var errs []Error
   303  	objects := []BatchDeleteObject{}
   304  	var input *s3.DeleteObjectsInput
   305  
   306  	for iter.Next() {
   307  		o := iter.DeleteObject()
   308  
   309  		if input == nil {
   310  			input = initDeleteObjectsInput(o.Object)
   311  		}
   312  
   313  		parity := hasParity(input, o)
   314  		if parity {
   315  			input.Delete.Objects = append(input.Delete.Objects, &s3.ObjectIdentifier{
   316  				Key:       o.Object.Key,
   317  				VersionId: o.Object.VersionId,
   318  			})
   319  			objects = append(objects, o)
   320  		}
   321  
   322  		if len(input.Delete.Objects) == d.BatchSize || !parity {
   323  			if err := deleteBatch(ctx, d, input, objects); err != nil {
   324  				errs = append(errs, err...)
   325  			}
   326  
   327  			objects = objects[:0]
   328  			input = nil
   329  
   330  			if !parity {
   331  				objects = append(objects, o)
   332  				input = initDeleteObjectsInput(o.Object)
   333  				input.Delete.Objects = append(input.Delete.Objects, &s3.ObjectIdentifier{
   334  					Key:       o.Object.Key,
   335  					VersionId: o.Object.VersionId,
   336  				})
   337  			}
   338  		}
   339  	}
   340  
   341  	// iter.Next() could return false (above) plus populate iter.Err()
   342  	if iter.Err() != nil {
   343  		errs = append(errs, newError(iter.Err(), nil, nil))
   344  	}
   345  
   346  	if input != nil && len(input.Delete.Objects) > 0 {
   347  		if err := deleteBatch(ctx, d, input, objects); err != nil {
   348  			errs = append(errs, err...)
   349  		}
   350  	}
   351  
   352  	if len(errs) > 0 {
   353  		return NewBatchError("BatchedDeleteIncomplete", "some objects have failed to be deleted.", errs)
   354  	}
   355  	return nil
   356  }
   357  
   358  func initDeleteObjectsInput(o *s3.DeleteObjectInput) *s3.DeleteObjectsInput {
   359  	return &s3.DeleteObjectsInput{
   360  		Bucket:       o.Bucket,
   361  		MFA:          o.MFA,
   362  		RequestPayer: o.RequestPayer,
   363  		Delete:       &s3.Delete{},
   364  	}
   365  }
   366  
   367  const (
   368  	// ErrDeleteBatchFailCode represents an error code which will be returned
   369  	// only when DeleteObjects.Errors has an error that does not contain a code.
   370  	ErrDeleteBatchFailCode       = "DeleteBatchError"
   371  	errDefaultDeleteBatchMessage = "failed to delete"
   372  )
   373  
   374  // deleteBatch will delete a batch of items in the objects parameters.
   375  func deleteBatch(ctx aws.Context, d *BatchDelete, input *s3.DeleteObjectsInput, objects []BatchDeleteObject) []Error {
   376  	errs := []Error{}
   377  
   378  	if result, err := d.Client.DeleteObjectsWithContext(ctx, input); err != nil {
   379  		for i := 0; i < len(input.Delete.Objects); i++ {
   380  			errs = append(errs, newError(err, input.Bucket, input.Delete.Objects[i].Key))
   381  		}
   382  	} else if len(result.Errors) > 0 {
   383  		for i := 0; i < len(result.Errors); i++ {
   384  			code := ErrDeleteBatchFailCode
   385  			msg := errDefaultDeleteBatchMessage
   386  			if result.Errors[i].Message != nil {
   387  				msg = *result.Errors[i].Message
   388  			}
   389  			if result.Errors[i].Code != nil {
   390  				code = *result.Errors[i].Code
   391  			}
   392  
   393  			errs = append(errs, newError(awserr.New(code, msg, err), input.Bucket, result.Errors[i].Key))
   394  		}
   395  	}
   396  	for _, object := range objects {
   397  		if object.After == nil {
   398  			continue
   399  		}
   400  		if err := object.After(); err != nil {
   401  			errs = append(errs, newError(err, object.Object.Bucket, object.Object.Key))
   402  		}
   403  	}
   404  
   405  	return errs
   406  }
   407  
   408  func hasParity(o1 *s3.DeleteObjectsInput, o2 BatchDeleteObject) bool {
   409  	if o1.Bucket != nil && o2.Object.Bucket != nil {
   410  		if *o1.Bucket != *o2.Object.Bucket {
   411  			return false
   412  		}
   413  	} else if o1.Bucket != o2.Object.Bucket {
   414  		return false
   415  	}
   416  
   417  	if o1.MFA != nil && o2.Object.MFA != nil {
   418  		if *o1.MFA != *o2.Object.MFA {
   419  			return false
   420  		}
   421  	} else if o1.MFA != o2.Object.MFA {
   422  		return false
   423  	}
   424  
   425  	if o1.RequestPayer != nil && o2.Object.RequestPayer != nil {
   426  		if *o1.RequestPayer != *o2.Object.RequestPayer {
   427  			return false
   428  		}
   429  	} else if o1.RequestPayer != o2.Object.RequestPayer {
   430  		return false
   431  	}
   432  
   433  	return true
   434  }
   435  
   436  // BatchDownloadIterator is an interface that uses the scanner pattern to iterate
   437  // through a series of objects to be downloaded.
   438  type BatchDownloadIterator interface {
   439  	Next() bool
   440  	Err() error
   441  	DownloadObject() BatchDownloadObject
   442  }
   443  
   444  // BatchDownloadObject contains all necessary information to run a batch operation once.
   445  type BatchDownloadObject struct {
   446  	Object *s3.GetObjectInput
   447  	Writer io.WriterAt
   448  	// After will run after each iteration during the batch process. This function will
   449  	// be executed whether or not the request was successful.
   450  	After func() error
   451  }
   452  
   453  // DownloadObjectsIterator implements the BatchDownloadIterator interface and allows for batched
   454  // download of objects.
   455  type DownloadObjectsIterator struct {
   456  	Objects []BatchDownloadObject
   457  	index   int
   458  	inc     bool
   459  }
   460  
   461  // Next will increment the default iterator's index and ensure that there
   462  // is another object to iterator to.
   463  func (batcher *DownloadObjectsIterator) Next() bool {
   464  	if batcher.inc {
   465  		batcher.index++
   466  	} else {
   467  		batcher.inc = true
   468  	}
   469  	return batcher.index < len(batcher.Objects)
   470  }
   471  
   472  // DownloadObject will return the BatchDownloadObject at the current batched index.
   473  func (batcher *DownloadObjectsIterator) DownloadObject() BatchDownloadObject {
   474  	object := batcher.Objects[batcher.index]
   475  	return object
   476  }
   477  
   478  // Err will return an error. Since this is just used to satisfy the BatchDeleteIterator interface
   479  // this will only return nil.
   480  func (batcher *DownloadObjectsIterator) Err() error {
   481  	return nil
   482  }
   483  
   484  // BatchUploadIterator is an interface that uses the scanner pattern to
   485  // iterate through what needs to be uploaded.
   486  type BatchUploadIterator interface {
   487  	Next() bool
   488  	Err() error
   489  	UploadObject() BatchUploadObject
   490  }
   491  
   492  // UploadObjectsIterator implements the BatchUploadIterator interface and allows for batched
   493  // upload of objects.
   494  type UploadObjectsIterator struct {
   495  	Objects []BatchUploadObject
   496  	index   int
   497  	inc     bool
   498  }
   499  
   500  // Next will increment the default iterator's index and ensure that there
   501  // is another object to iterator to.
   502  func (batcher *UploadObjectsIterator) Next() bool {
   503  	if batcher.inc {
   504  		batcher.index++
   505  	} else {
   506  		batcher.inc = true
   507  	}
   508  	return batcher.index < len(batcher.Objects)
   509  }
   510  
   511  // Err will return an error. Since this is just used to satisfy the BatchUploadIterator interface
   512  // this will only return nil.
   513  func (batcher *UploadObjectsIterator) Err() error {
   514  	return nil
   515  }
   516  
   517  // UploadObject will return the BatchUploadObject at the current batched index.
   518  func (batcher *UploadObjectsIterator) UploadObject() BatchUploadObject {
   519  	object := batcher.Objects[batcher.index]
   520  	return object
   521  }
   522  
   523  // BatchUploadObject contains all necessary information to run a batch operation once.
   524  type BatchUploadObject struct {
   525  	Object *UploadInput
   526  	// After will run after each iteration during the batch process. This function will
   527  	// be executed whether or not the request was successful.
   528  	After func() error
   529  }