api-remove.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467
  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. package minio
  18. import (
  19. "bytes"
  20. "context"
  21. "encoding/xml"
  22. "io"
  23. "net/http"
  24. "net/url"
  25. "time"
  26. "github.com/minio/minio-go/v7/pkg/s3utils"
  27. )
  28. // BucketOptions special headers to purge buckets, only
  29. // useful when endpoint is MinIO
  30. type BucketOptions struct {
  31. ForceDelete bool
  32. }
  33. // RemoveBucketWithOptions deletes the bucket name.
  34. //
  35. // All objects (including all object versions and delete markers)
  36. // in the bucket will be deleted forcibly if bucket options set
  37. // ForceDelete to 'true'.
  38. func (c Client) RemoveBucketWithOptions(ctx context.Context, bucketName string, opts BucketOptions) error {
  39. // Input validation.
  40. if err := s3utils.CheckValidBucketName(bucketName); err != nil {
  41. return err
  42. }
  43. // Build headers.
  44. headers := make(http.Header)
  45. if opts.ForceDelete {
  46. headers.Set(minIOForceDelete, "true")
  47. }
  48. // Execute DELETE on bucket.
  49. resp, err := c.executeMethod(ctx, http.MethodDelete, requestMetadata{
  50. bucketName: bucketName,
  51. contentSHA256Hex: emptySHA256Hex,
  52. customHeader: headers,
  53. })
  54. defer closeResponse(resp)
  55. if err != nil {
  56. return err
  57. }
  58. if resp != nil {
  59. if resp.StatusCode != http.StatusNoContent {
  60. return httpRespToErrorResponse(resp, bucketName, "")
  61. }
  62. }
  63. // Remove the location from cache on a successful delete.
  64. c.bucketLocCache.Delete(bucketName)
  65. return nil
  66. }
  67. // RemoveBucket deletes the bucket name.
  68. //
  69. // All objects (including all object versions and delete markers).
  70. // in the bucket must be deleted before successfully attempting this request.
  71. func (c Client) RemoveBucket(ctx context.Context, bucketName string) error {
  72. // Input validation.
  73. if err := s3utils.CheckValidBucketName(bucketName); err != nil {
  74. return err
  75. }
  76. // Execute DELETE on bucket.
  77. resp, err := c.executeMethod(ctx, http.MethodDelete, requestMetadata{
  78. bucketName: bucketName,
  79. contentSHA256Hex: emptySHA256Hex,
  80. })
  81. defer closeResponse(resp)
  82. if err != nil {
  83. return err
  84. }
  85. if resp != nil {
  86. if resp.StatusCode != http.StatusNoContent {
  87. return httpRespToErrorResponse(resp, bucketName, "")
  88. }
  89. }
  90. // Remove the location from cache on a successful delete.
  91. c.bucketLocCache.Delete(bucketName)
  92. return nil
  93. }
  94. // AdvancedRemoveOptions intended for internal use by replication
  95. type AdvancedRemoveOptions struct {
  96. ReplicationDeleteMarker bool
  97. ReplicationStatus ReplicationStatus
  98. ReplicationMTime time.Time
  99. ReplicationRequest bool
  100. }
  101. // RemoveObjectOptions represents options specified by user for RemoveObject call
  102. type RemoveObjectOptions struct {
  103. ForceDelete bool
  104. GovernanceBypass bool
  105. VersionID string
  106. Internal AdvancedRemoveOptions
  107. }
  108. // RemoveObject removes an object from a bucket.
  109. func (c Client) RemoveObject(ctx context.Context, bucketName, objectName string, opts RemoveObjectOptions) error {
  110. // Input validation.
  111. if err := s3utils.CheckValidBucketName(bucketName); err != nil {
  112. return err
  113. }
  114. if err := s3utils.CheckValidObjectName(objectName); err != nil {
  115. return err
  116. }
  117. return c.removeObject(ctx, bucketName, objectName, opts)
  118. }
  119. func (c Client) removeObject(ctx context.Context, bucketName, objectName string, opts RemoveObjectOptions) error {
  120. // Get resources properly escaped and lined up before
  121. // using them in http request.
  122. urlValues := make(url.Values)
  123. if opts.VersionID != "" {
  124. urlValues.Set("versionId", opts.VersionID)
  125. }
  126. // Build headers.
  127. headers := make(http.Header)
  128. if opts.GovernanceBypass {
  129. // Set the bypass goverenance retention header
  130. headers.Set(amzBypassGovernance, "true")
  131. }
  132. if opts.Internal.ReplicationDeleteMarker {
  133. headers.Set(minIOBucketReplicationDeleteMarker, "true")
  134. }
  135. if !opts.Internal.ReplicationMTime.IsZero() {
  136. headers.Set(minIOBucketSourceMTime, opts.Internal.ReplicationMTime.Format(time.RFC3339Nano))
  137. }
  138. if !opts.Internal.ReplicationStatus.Empty() {
  139. headers.Set(amzBucketReplicationStatus, string(opts.Internal.ReplicationStatus))
  140. }
  141. if opts.Internal.ReplicationRequest {
  142. headers.Set(minIOBucketReplicationRequest, "")
  143. }
  144. if opts.ForceDelete {
  145. headers.Set(minIOForceDelete, "true")
  146. }
  147. // Execute DELETE on objectName.
  148. resp, err := c.executeMethod(ctx, http.MethodDelete, requestMetadata{
  149. bucketName: bucketName,
  150. objectName: objectName,
  151. contentSHA256Hex: emptySHA256Hex,
  152. queryValues: urlValues,
  153. customHeader: headers,
  154. })
  155. defer closeResponse(resp)
  156. if err != nil {
  157. return err
  158. }
  159. if resp != nil {
  160. // if some unexpected error happened and max retry is reached, we want to let client know
  161. if resp.StatusCode != http.StatusNoContent {
  162. return httpRespToErrorResponse(resp, bucketName, objectName)
  163. }
  164. }
  165. // DeleteObject always responds with http '204' even for
  166. // objects which do not exist. So no need to handle them
  167. // specifically.
  168. return nil
  169. }
  170. // RemoveObjectError - container of Multi Delete S3 API error
  171. type RemoveObjectError struct {
  172. ObjectName string
  173. VersionID string
  174. Err error
  175. }
  176. // generateRemoveMultiObjects - generate the XML request for remove multi objects request
  177. func generateRemoveMultiObjectsRequest(objects []ObjectInfo) []byte {
  178. delObjects := []deleteObject{}
  179. for _, obj := range objects {
  180. delObjects = append(delObjects, deleteObject{
  181. Key: obj.Key,
  182. VersionID: obj.VersionID,
  183. })
  184. }
  185. xmlBytes, _ := xml.Marshal(deleteMultiObjects{Objects: delObjects, Quiet: true})
  186. return xmlBytes
  187. }
  188. // processRemoveMultiObjectsResponse - parse the remove multi objects web service
  189. // and return the success/failure result status for each object
  190. func processRemoveMultiObjectsResponse(body io.Reader, objects []ObjectInfo, errorCh chan<- RemoveObjectError) {
  191. // Parse multi delete XML response
  192. rmResult := &deleteMultiObjectsResult{}
  193. err := xmlDecoder(body, rmResult)
  194. if err != nil {
  195. errorCh <- RemoveObjectError{ObjectName: "", Err: err}
  196. return
  197. }
  198. // Fill deletion that returned an error.
  199. for _, obj := range rmResult.UnDeletedObjects {
  200. // Version does not exist is not an error ignore and continue.
  201. switch obj.Code {
  202. case "InvalidArgument", "NoSuchVersion":
  203. continue
  204. }
  205. errorCh <- RemoveObjectError{
  206. ObjectName: obj.Key,
  207. VersionID: obj.VersionID,
  208. Err: ErrorResponse{
  209. Code: obj.Code,
  210. Message: obj.Message,
  211. },
  212. }
  213. }
  214. }
  215. // RemoveObjectsOptions represents options specified by user for RemoveObjects call
  216. type RemoveObjectsOptions struct {
  217. GovernanceBypass bool
  218. }
  219. // RemoveObjects removes multiple objects from a bucket while
  220. // it is possible to specify objects versions which are received from
  221. // objectsCh. Remove failures are sent back via error channel.
  222. func (c Client) RemoveObjects(ctx context.Context, bucketName string, objectsCh <-chan ObjectInfo, opts RemoveObjectsOptions) <-chan RemoveObjectError {
  223. errorCh := make(chan RemoveObjectError, 1)
  224. // Validate if bucket name is valid.
  225. if err := s3utils.CheckValidBucketName(bucketName); err != nil {
  226. defer close(errorCh)
  227. errorCh <- RemoveObjectError{
  228. Err: err,
  229. }
  230. return errorCh
  231. }
  232. // Validate objects channel to be properly allocated.
  233. if objectsCh == nil {
  234. defer close(errorCh)
  235. errorCh <- RemoveObjectError{
  236. Err: errInvalidArgument("Objects channel cannot be nil"),
  237. }
  238. return errorCh
  239. }
  240. go c.removeObjects(ctx, bucketName, objectsCh, errorCh, opts)
  241. return errorCh
  242. }
  243. // Return true if the character is within the allowed characters in an XML 1.0 document
  244. // The list of allowed characters can be found here: https://www.w3.org/TR/xml/#charsets
  245. func validXMLChar(r rune) (ok bool) {
  246. return r == 0x09 ||
  247. r == 0x0A ||
  248. r == 0x0D ||
  249. r >= 0x20 && r <= 0xD7FF ||
  250. r >= 0xE000 && r <= 0xFFFD ||
  251. r >= 0x10000 && r <= 0x10FFFF
  252. }
  253. func hasInvalidXMLChar(str string) bool {
  254. for _, s := range str {
  255. if !validXMLChar(s) {
  256. return true
  257. }
  258. }
  259. return false
  260. }
  261. // Generate and call MultiDelete S3 requests based on entries received from objectsCh
  262. func (c Client) removeObjects(ctx context.Context, bucketName string, objectsCh <-chan ObjectInfo, errorCh chan<- RemoveObjectError, opts RemoveObjectsOptions) {
  263. maxEntries := 1000
  264. finish := false
  265. urlValues := make(url.Values)
  266. urlValues.Set("delete", "")
  267. // Close error channel when Multi delete finishes.
  268. defer close(errorCh)
  269. // Loop over entries by 1000 and call MultiDelete requests
  270. for {
  271. if finish {
  272. break
  273. }
  274. count := 0
  275. var batch []ObjectInfo
  276. // Try to gather 1000 entries
  277. for object := range objectsCh {
  278. if hasInvalidXMLChar(object.Key) {
  279. // Use single DELETE so the object name will be in the request URL instead of the multi-delete XML document.
  280. err := c.removeObject(ctx, bucketName, object.Key, RemoveObjectOptions{
  281. VersionID: object.VersionID,
  282. GovernanceBypass: opts.GovernanceBypass,
  283. })
  284. if err != nil {
  285. // Version does not exist is not an error ignore and continue.
  286. switch ToErrorResponse(err).Code {
  287. case "InvalidArgument", "NoSuchVersion":
  288. continue
  289. }
  290. errorCh <- RemoveObjectError{
  291. ObjectName: object.Key,
  292. VersionID: object.VersionID,
  293. Err: err,
  294. }
  295. }
  296. continue
  297. }
  298. batch = append(batch, object)
  299. if count++; count >= maxEntries {
  300. break
  301. }
  302. }
  303. if count == 0 {
  304. // Multi Objects Delete API doesn't accept empty object list, quit immediately
  305. break
  306. }
  307. if count < maxEntries {
  308. // We didn't have 1000 entries, so this is the last batch
  309. finish = true
  310. }
  311. // Build headers.
  312. headers := make(http.Header)
  313. if opts.GovernanceBypass {
  314. // Set the bypass goverenance retention header
  315. headers.Set(amzBypassGovernance, "true")
  316. }
  317. // Generate remove multi objects XML request
  318. removeBytes := generateRemoveMultiObjectsRequest(batch)
  319. // Execute GET on bucket to list objects.
  320. resp, err := c.executeMethod(ctx, http.MethodPost, requestMetadata{
  321. bucketName: bucketName,
  322. queryValues: urlValues,
  323. contentBody: bytes.NewReader(removeBytes),
  324. contentLength: int64(len(removeBytes)),
  325. contentMD5Base64: sumMD5Base64(removeBytes),
  326. contentSHA256Hex: sum256Hex(removeBytes),
  327. customHeader: headers,
  328. })
  329. if resp != nil {
  330. if resp.StatusCode != http.StatusOK {
  331. e := httpRespToErrorResponse(resp, bucketName, "")
  332. errorCh <- RemoveObjectError{ObjectName: "", Err: e}
  333. }
  334. }
  335. if err != nil {
  336. for _, b := range batch {
  337. errorCh <- RemoveObjectError{
  338. ObjectName: b.Key,
  339. VersionID: b.VersionID,
  340. Err: err,
  341. }
  342. }
  343. continue
  344. }
  345. // Process multiobjects remove xml response
  346. processRemoveMultiObjectsResponse(resp.Body, batch, errorCh)
  347. closeResponse(resp)
  348. }
  349. }
  350. // RemoveIncompleteUpload aborts an partially uploaded object.
  351. func (c Client) RemoveIncompleteUpload(ctx context.Context, bucketName, objectName string) error {
  352. // Input validation.
  353. if err := s3utils.CheckValidBucketName(bucketName); err != nil {
  354. return err
  355. }
  356. if err := s3utils.CheckValidObjectName(objectName); err != nil {
  357. return err
  358. }
  359. // Find multipart upload ids of the object to be aborted.
  360. uploadIDs, err := c.findUploadIDs(ctx, bucketName, objectName)
  361. if err != nil {
  362. return err
  363. }
  364. for _, uploadID := range uploadIDs {
  365. // abort incomplete multipart upload, based on the upload id passed.
  366. err := c.abortMultipartUpload(ctx, bucketName, objectName, uploadID)
  367. if err != nil {
  368. return err
  369. }
  370. }
  371. return nil
  372. }
  373. // abortMultipartUpload aborts a multipart upload for the given
  374. // uploadID, all previously uploaded parts are deleted.
  375. func (c Client) abortMultipartUpload(ctx context.Context, bucketName, objectName, uploadID string) error {
  376. // Input validation.
  377. if err := s3utils.CheckValidBucketName(bucketName); err != nil {
  378. return err
  379. }
  380. if err := s3utils.CheckValidObjectName(objectName); err != nil {
  381. return err
  382. }
  383. // Initialize url queries.
  384. urlValues := make(url.Values)
  385. urlValues.Set("uploadId", uploadID)
  386. // Execute DELETE on multipart upload.
  387. resp, err := c.executeMethod(ctx, http.MethodDelete, requestMetadata{
  388. bucketName: bucketName,
  389. objectName: objectName,
  390. queryValues: urlValues,
  391. contentSHA256Hex: emptySHA256Hex,
  392. })
  393. defer closeResponse(resp)
  394. if err != nil {
  395. return err
  396. }
  397. if resp != nil {
  398. if resp.StatusCode != http.StatusNoContent {
  399. // Abort has no response body, handle it for any errors.
  400. var errorResponse ErrorResponse
  401. switch resp.StatusCode {
  402. case http.StatusNotFound:
  403. // This is needed specifically for abort and it cannot
  404. // be converged into default case.
  405. errorResponse = ErrorResponse{
  406. Code: "NoSuchUpload",
  407. Message: "The specified multipart upload does not exist.",
  408. BucketName: bucketName,
  409. Key: objectName,
  410. RequestID: resp.Header.Get("x-amz-request-id"),
  411. HostID: resp.Header.Get("x-amz-id-2"),
  412. Region: resp.Header.Get("x-amz-bucket-region"),
  413. }
  414. default:
  415. return httpRespToErrorResponse(resp, bucketName, objectName)
  416. }
  417. return errorResponse
  418. }
  419. }
  420. return nil
  421. }