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 }