aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/github.com/minio/minio-go/v7/api-remove.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/minio/minio-go/v7/api-remove.go')
-rw-r--r--vendor/github.com/minio/minio-go/v7/api-remove.go548
1 files changed, 548 insertions, 0 deletions
diff --git a/vendor/github.com/minio/minio-go/v7/api-remove.go b/vendor/github.com/minio/minio-go/v7/api-remove.go
new file mode 100644
index 0000000..9c0ac44
--- /dev/null
+++ b/vendor/github.com/minio/minio-go/v7/api-remove.go
@@ -0,0 +1,548 @@
1/*
2 * MinIO Go Library for Amazon S3 Compatible Cloud Storage
3 * Copyright 2015-2020 MinIO, Inc.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18package minio
19
20import (
21 "bytes"
22 "context"
23 "encoding/xml"
24 "io"
25 "net/http"
26 "net/url"
27 "time"
28
29 "github.com/minio/minio-go/v7/pkg/s3utils"
30)
31
32//revive:disable
33
34// Deprecated: BucketOptions will be renamed to RemoveBucketOptions in future versions.
35type BucketOptions = RemoveBucketOptions
36
37//revive:enable
38
39// RemoveBucketOptions special headers to purge buckets, only
40// useful when endpoint is MinIO
41type RemoveBucketOptions struct {
42 ForceDelete bool
43}
44
45// RemoveBucketWithOptions deletes the bucket name.
46//
47// All objects (including all object versions and delete markers)
48// in the bucket will be deleted forcibly if bucket options set
49// ForceDelete to 'true'.
50func (c *Client) RemoveBucketWithOptions(ctx context.Context, bucketName string, opts RemoveBucketOptions) error {
51 // Input validation.
52 if err := s3utils.CheckValidBucketName(bucketName); err != nil {
53 return err
54 }
55
56 // Build headers.
57 headers := make(http.Header)
58 if opts.ForceDelete {
59 headers.Set(minIOForceDelete, "true")
60 }
61
62 // Execute DELETE on bucket.
63 resp, err := c.executeMethod(ctx, http.MethodDelete, requestMetadata{
64 bucketName: bucketName,
65 contentSHA256Hex: emptySHA256Hex,
66 customHeader: headers,
67 })
68 defer closeResponse(resp)
69 if err != nil {
70 return err
71 }
72 if resp != nil {
73 if resp.StatusCode != http.StatusNoContent {
74 return httpRespToErrorResponse(resp, bucketName, "")
75 }
76 }
77
78 // Remove the location from cache on a successful delete.
79 c.bucketLocCache.Delete(bucketName)
80 return nil
81}
82
83// RemoveBucket deletes the bucket name.
84//
85// All objects (including all object versions and delete markers).
86// in the bucket must be deleted before successfully attempting this request.
87func (c *Client) RemoveBucket(ctx context.Context, bucketName string) error {
88 // Input validation.
89 if err := s3utils.CheckValidBucketName(bucketName); err != nil {
90 return err
91 }
92 // Execute DELETE on bucket.
93 resp, err := c.executeMethod(ctx, http.MethodDelete, requestMetadata{
94 bucketName: bucketName,
95 contentSHA256Hex: emptySHA256Hex,
96 })
97 defer closeResponse(resp)
98 if err != nil {
99 return err
100 }
101 if resp != nil {
102 if resp.StatusCode != http.StatusNoContent {
103 return httpRespToErrorResponse(resp, bucketName, "")
104 }
105 }
106
107 // Remove the location from cache on a successful delete.
108 c.bucketLocCache.Delete(bucketName)
109
110 return nil
111}
112
113// AdvancedRemoveOptions intended for internal use by replication
114type AdvancedRemoveOptions struct {
115 ReplicationDeleteMarker bool
116 ReplicationStatus ReplicationStatus
117 ReplicationMTime time.Time
118 ReplicationRequest bool
119 ReplicationValidityCheck bool // check permissions
120}
121
122// RemoveObjectOptions represents options specified by user for RemoveObject call
123type RemoveObjectOptions struct {
124 ForceDelete bool
125 GovernanceBypass bool
126 VersionID string
127 Internal AdvancedRemoveOptions
128}
129
130// RemoveObject removes an object from a bucket.
131func (c *Client) RemoveObject(ctx context.Context, bucketName, objectName string, opts RemoveObjectOptions) error {
132 // Input validation.
133 if err := s3utils.CheckValidBucketName(bucketName); err != nil {
134 return err
135 }
136 if err := s3utils.CheckValidObjectName(objectName); err != nil {
137 return err
138 }
139
140 res := c.removeObject(ctx, bucketName, objectName, opts)
141 return res.Err
142}
143
144func (c *Client) removeObject(ctx context.Context, bucketName, objectName string, opts RemoveObjectOptions) RemoveObjectResult {
145 // Get resources properly escaped and lined up before
146 // using them in http request.
147 urlValues := make(url.Values)
148
149 if opts.VersionID != "" {
150 urlValues.Set("versionId", opts.VersionID)
151 }
152
153 // Build headers.
154 headers := make(http.Header)
155
156 if opts.GovernanceBypass {
157 // Set the bypass goverenance retention header
158 headers.Set(amzBypassGovernance, "true")
159 }
160 if opts.Internal.ReplicationDeleteMarker {
161 headers.Set(minIOBucketReplicationDeleteMarker, "true")
162 }
163 if !opts.Internal.ReplicationMTime.IsZero() {
164 headers.Set(minIOBucketSourceMTime, opts.Internal.ReplicationMTime.Format(time.RFC3339Nano))
165 }
166 if !opts.Internal.ReplicationStatus.Empty() {
167 headers.Set(amzBucketReplicationStatus, string(opts.Internal.ReplicationStatus))
168 }
169 if opts.Internal.ReplicationRequest {
170 headers.Set(minIOBucketReplicationRequest, "true")
171 }
172 if opts.Internal.ReplicationValidityCheck {
173 headers.Set(minIOBucketReplicationCheck, "true")
174 }
175 if opts.ForceDelete {
176 headers.Set(minIOForceDelete, "true")
177 }
178 // Execute DELETE on objectName.
179 resp, err := c.executeMethod(ctx, http.MethodDelete, requestMetadata{
180 bucketName: bucketName,
181 objectName: objectName,
182 contentSHA256Hex: emptySHA256Hex,
183 queryValues: urlValues,
184 customHeader: headers,
185 })
186 defer closeResponse(resp)
187 if err != nil {
188 return RemoveObjectResult{Err: err}
189 }
190 if resp != nil {
191 // if some unexpected error happened and max retry is reached, we want to let client know
192 if resp.StatusCode != http.StatusNoContent {
193 err := httpRespToErrorResponse(resp, bucketName, objectName)
194 return RemoveObjectResult{Err: err}
195 }
196 }
197
198 // DeleteObject always responds with http '204' even for
199 // objects which do not exist. So no need to handle them
200 // specifically.
201 return RemoveObjectResult{
202 ObjectName: objectName,
203 ObjectVersionID: opts.VersionID,
204 DeleteMarker: resp.Header.Get("x-amz-delete-marker") == "true",
205 DeleteMarkerVersionID: resp.Header.Get("x-amz-version-id"),
206 }
207}
208
209// RemoveObjectError - container of Multi Delete S3 API error
210type RemoveObjectError struct {
211 ObjectName string
212 VersionID string
213 Err error
214}
215
216// RemoveObjectResult - container of Multi Delete S3 API result
217type RemoveObjectResult struct {
218 ObjectName string
219 ObjectVersionID string
220
221 DeleteMarker bool
222 DeleteMarkerVersionID string
223
224 Err error
225}
226
227// generateRemoveMultiObjects - generate the XML request for remove multi objects request
228func generateRemoveMultiObjectsRequest(objects []ObjectInfo) []byte {
229 delObjects := []deleteObject{}
230 for _, obj := range objects {
231 delObjects = append(delObjects, deleteObject{
232 Key: obj.Key,
233 VersionID: obj.VersionID,
234 })
235 }
236 xmlBytes, _ := xml.Marshal(deleteMultiObjects{Objects: delObjects, Quiet: false})
237 return xmlBytes
238}
239
240// processRemoveMultiObjectsResponse - parse the remove multi objects web service
241// and return the success/failure result status for each object
242func processRemoveMultiObjectsResponse(body io.Reader, resultCh chan<- RemoveObjectResult) {
243 // Parse multi delete XML response
244 rmResult := &deleteMultiObjectsResult{}
245 err := xmlDecoder(body, rmResult)
246 if err != nil {
247 resultCh <- RemoveObjectResult{ObjectName: "", Err: err}
248 return
249 }
250
251 // Fill deletion that returned success
252 for _, obj := range rmResult.DeletedObjects {
253 resultCh <- RemoveObjectResult{
254 ObjectName: obj.Key,
255 // Only filled with versioned buckets
256 ObjectVersionID: obj.VersionID,
257 DeleteMarker: obj.DeleteMarker,
258 DeleteMarkerVersionID: obj.DeleteMarkerVersionID,
259 }
260 }
261
262 // Fill deletion that returned an error.
263 for _, obj := range rmResult.UnDeletedObjects {
264 // Version does not exist is not an error ignore and continue.
265 switch obj.Code {
266 case "InvalidArgument", "NoSuchVersion":
267 continue
268 }
269 resultCh <- RemoveObjectResult{
270 ObjectName: obj.Key,
271 ObjectVersionID: obj.VersionID,
272 Err: ErrorResponse{
273 Code: obj.Code,
274 Message: obj.Message,
275 },
276 }
277 }
278}
279
280// RemoveObjectsOptions represents options specified by user for RemoveObjects call
281type RemoveObjectsOptions struct {
282 GovernanceBypass bool
283}
284
285// RemoveObjects removes multiple objects from a bucket while
286// it is possible to specify objects versions which are received from
287// objectsCh. Remove failures are sent back via error channel.
288func (c *Client) RemoveObjects(ctx context.Context, bucketName string, objectsCh <-chan ObjectInfo, opts RemoveObjectsOptions) <-chan RemoveObjectError {
289 errorCh := make(chan RemoveObjectError, 1)
290
291 // Validate if bucket name is valid.
292 if err := s3utils.CheckValidBucketName(bucketName); err != nil {
293 defer close(errorCh)
294 errorCh <- RemoveObjectError{
295 Err: err,
296 }
297 return errorCh
298 }
299 // Validate objects channel to be properly allocated.
300 if objectsCh == nil {
301 defer close(errorCh)
302 errorCh <- RemoveObjectError{
303 Err: errInvalidArgument("Objects channel cannot be nil"),
304 }
305 return errorCh
306 }
307
308 resultCh := make(chan RemoveObjectResult, 1)
309 go c.removeObjects(ctx, bucketName, objectsCh, resultCh, opts)
310 go func() {
311 defer close(errorCh)
312 for res := range resultCh {
313 // Send only errors to the error channel
314 if res.Err == nil {
315 continue
316 }
317 errorCh <- RemoveObjectError{
318 ObjectName: res.ObjectName,
319 VersionID: res.ObjectVersionID,
320 Err: res.Err,
321 }
322 }
323 }()
324
325 return errorCh
326}
327
328// RemoveObjectsWithResult removes multiple objects from a bucket while
329// it is possible to specify objects versions which are received from
330// objectsCh. Remove results, successes and failures are sent back via
331// RemoveObjectResult channel
332func (c *Client) RemoveObjectsWithResult(ctx context.Context, bucketName string, objectsCh <-chan ObjectInfo, opts RemoveObjectsOptions) <-chan RemoveObjectResult {
333 resultCh := make(chan RemoveObjectResult, 1)
334
335 // Validate if bucket name is valid.
336 if err := s3utils.CheckValidBucketName(bucketName); err != nil {
337 defer close(resultCh)
338 resultCh <- RemoveObjectResult{
339 Err: err,
340 }
341 return resultCh
342 }
343 // Validate objects channel to be properly allocated.
344 if objectsCh == nil {
345 defer close(resultCh)
346 resultCh <- RemoveObjectResult{
347 Err: errInvalidArgument("Objects channel cannot be nil"),
348 }
349 return resultCh
350 }
351
352 go c.removeObjects(ctx, bucketName, objectsCh, resultCh, opts)
353 return resultCh
354}
355
356// Return true if the character is within the allowed characters in an XML 1.0 document
357// The list of allowed characters can be found here: https://www.w3.org/TR/xml/#charsets
358func validXMLChar(r rune) (ok bool) {
359 return r == 0x09 ||
360 r == 0x0A ||
361 r == 0x0D ||
362 r >= 0x20 && r <= 0xD7FF ||
363 r >= 0xE000 && r <= 0xFFFD ||
364 r >= 0x10000 && r <= 0x10FFFF
365}
366
367func hasInvalidXMLChar(str string) bool {
368 for _, s := range str {
369 if !validXMLChar(s) {
370 return true
371 }
372 }
373 return false
374}
375
376// Generate and call MultiDelete S3 requests based on entries received from objectsCh
377func (c *Client) removeObjects(ctx context.Context, bucketName string, objectsCh <-chan ObjectInfo, resultCh chan<- RemoveObjectResult, opts RemoveObjectsOptions) {
378 maxEntries := 1000
379 finish := false
380 urlValues := make(url.Values)
381 urlValues.Set("delete", "")
382
383 // Close result channel when Multi delete finishes.
384 defer close(resultCh)
385
386 // Loop over entries by 1000 and call MultiDelete requests
387 for {
388 if finish {
389 break
390 }
391 count := 0
392 var batch []ObjectInfo
393
394 // Try to gather 1000 entries
395 for object := range objectsCh {
396 if hasInvalidXMLChar(object.Key) {
397 // Use single DELETE so the object name will be in the request URL instead of the multi-delete XML document.
398 removeResult := c.removeObject(ctx, bucketName, object.Key, RemoveObjectOptions{
399 VersionID: object.VersionID,
400 GovernanceBypass: opts.GovernanceBypass,
401 })
402 if err := removeResult.Err; err != nil {
403 // Version does not exist is not an error ignore and continue.
404 switch ToErrorResponse(err).Code {
405 case "InvalidArgument", "NoSuchVersion":
406 continue
407 }
408 resultCh <- removeResult
409 }
410
411 resultCh <- removeResult
412 continue
413 }
414
415 batch = append(batch, object)
416 if count++; count >= maxEntries {
417 break
418 }
419 }
420 if count == 0 {
421 // Multi Objects Delete API doesn't accept empty object list, quit immediately
422 break
423 }
424 if count < maxEntries {
425 // We didn't have 1000 entries, so this is the last batch
426 finish = true
427 }
428
429 // Build headers.
430 headers := make(http.Header)
431 if opts.GovernanceBypass {
432 // Set the bypass goverenance retention header
433 headers.Set(amzBypassGovernance, "true")
434 }
435
436 // Generate remove multi objects XML request
437 removeBytes := generateRemoveMultiObjectsRequest(batch)
438 // Execute GET on bucket to list objects.
439 resp, err := c.executeMethod(ctx, http.MethodPost, requestMetadata{
440 bucketName: bucketName,
441 queryValues: urlValues,
442 contentBody: bytes.NewReader(removeBytes),
443 contentLength: int64(len(removeBytes)),
444 contentMD5Base64: sumMD5Base64(removeBytes),
445 contentSHA256Hex: sum256Hex(removeBytes),
446 customHeader: headers,
447 })
448 if resp != nil {
449 if resp.StatusCode != http.StatusOK {
450 e := httpRespToErrorResponse(resp, bucketName, "")
451 resultCh <- RemoveObjectResult{ObjectName: "", Err: e}
452 }
453 }
454 if err != nil {
455 for _, b := range batch {
456 resultCh <- RemoveObjectResult{
457 ObjectName: b.Key,
458 ObjectVersionID: b.VersionID,
459 Err: err,
460 }
461 }
462 continue
463 }
464
465 // Process multiobjects remove xml response
466 processRemoveMultiObjectsResponse(resp.Body, resultCh)
467
468 closeResponse(resp)
469 }
470}
471
472// RemoveIncompleteUpload aborts an partially uploaded object.
473func (c *Client) RemoveIncompleteUpload(ctx context.Context, bucketName, objectName string) error {
474 // Input validation.
475 if err := s3utils.CheckValidBucketName(bucketName); err != nil {
476 return err
477 }
478 if err := s3utils.CheckValidObjectName(objectName); err != nil {
479 return err
480 }
481 // Find multipart upload ids of the object to be aborted.
482 uploadIDs, err := c.findUploadIDs(ctx, bucketName, objectName)
483 if err != nil {
484 return err
485 }
486
487 for _, uploadID := range uploadIDs {
488 // abort incomplete multipart upload, based on the upload id passed.
489 err := c.abortMultipartUpload(ctx, bucketName, objectName, uploadID)
490 if err != nil {
491 return err
492 }
493 }
494
495 return nil
496}
497
498// abortMultipartUpload aborts a multipart upload for the given
499// uploadID, all previously uploaded parts are deleted.
500func (c *Client) abortMultipartUpload(ctx context.Context, bucketName, objectName, uploadID string) error {
501 // Input validation.
502 if err := s3utils.CheckValidBucketName(bucketName); err != nil {
503 return err
504 }
505 if err := s3utils.CheckValidObjectName(objectName); err != nil {
506 return err
507 }
508
509 // Initialize url queries.
510 urlValues := make(url.Values)
511 urlValues.Set("uploadId", uploadID)
512
513 // Execute DELETE on multipart upload.
514 resp, err := c.executeMethod(ctx, http.MethodDelete, requestMetadata{
515 bucketName: bucketName,
516 objectName: objectName,
517 queryValues: urlValues,
518 contentSHA256Hex: emptySHA256Hex,
519 })
520 defer closeResponse(resp)
521 if err != nil {
522 return err
523 }
524 if resp != nil {
525 if resp.StatusCode != http.StatusNoContent {
526 // Abort has no response body, handle it for any errors.
527 var errorResponse ErrorResponse
528 switch resp.StatusCode {
529 case http.StatusNotFound:
530 // This is needed specifically for abort and it cannot
531 // be converged into default case.
532 errorResponse = ErrorResponse{
533 Code: "NoSuchUpload",
534 Message: "The specified multipart upload does not exist.",
535 BucketName: bucketName,
536 Key: objectName,
537 RequestID: resp.Header.Get("x-amz-request-id"),
538 HostID: resp.Header.Get("x-amz-id-2"),
539 Region: resp.Header.Get("x-amz-bucket-region"),
540 }
541 default:
542 return httpRespToErrorResponse(resp, bucketName, objectName)
543 }
544 return errorResponse
545 }
546 }
547 return nil
548}