api-remove.go 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  1. /*
  2. * MinIO Go Library for Amazon S3 Compatible Cloud Storage
  3. * Copyright 2015-2017 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 s3cli
  18. import (
  19. "bytes"
  20. "context"
  21. "encoding/xml"
  22. "io"
  23. "net/http"
  24. "net/url"
  25. "github.com/minio/minio-go/v6/pkg/s3utils"
  26. )
  27. // RemoveBucket deletes the bucket name.
  28. //
  29. // All objects (including all object versions and delete markers).
  30. // in the bucket must be deleted before successfully attempting this request.
  31. func (c Client) RemoveBucket(bucketName string) error {
  32. // Input validation.
  33. if err := s3utils.CheckValidBucketName(bucketName); err != nil {
  34. return err
  35. }
  36. // Execute DELETE on bucket.
  37. resp, err := c.executeMethod(context.Background(), "DELETE", requestMetadata{
  38. bucketName: bucketName,
  39. contentSHA256Hex: emptySHA256Hex,
  40. })
  41. defer closeResponse(resp)
  42. if err != nil {
  43. return err
  44. }
  45. if resp != nil {
  46. if resp.StatusCode != http.StatusNoContent {
  47. return httpRespToErrorResponse(resp, bucketName, "")
  48. }
  49. }
  50. // Remove the location from cache on a successful delete.
  51. c.bucketLocCache.Delete(bucketName)
  52. return nil
  53. }
  54. // RemoveObject remove an object from a bucket.
  55. func (c Client) RemoveObject(bucketName, objectName string) error {
  56. // Input validation.
  57. if err := s3utils.CheckValidBucketName(bucketName); err != nil {
  58. return err
  59. }
  60. if err := s3utils.CheckValidObjectName(objectName); err != nil {
  61. return err
  62. }
  63. // Execute DELETE on objectName.
  64. resp, err := c.executeMethod(context.Background(), "DELETE", requestMetadata{
  65. bucketName: bucketName,
  66. objectName: objectName,
  67. contentSHA256Hex: emptySHA256Hex,
  68. })
  69. defer closeResponse(resp)
  70. if err != nil {
  71. return err
  72. }
  73. if resp != nil {
  74. // if some unexpected error happened and max retry is reached, we want to let client know
  75. if resp.StatusCode != http.StatusNoContent {
  76. return httpRespToErrorResponse(resp, bucketName, objectName)
  77. }
  78. }
  79. // DeleteObject always responds with http '204' even for
  80. // objects which do not exist. So no need to handle them
  81. // specifically.
  82. return nil
  83. }
  84. // RemoveObjectError - container of Multi Delete S3 API error
  85. type RemoveObjectError struct {
  86. ObjectName string
  87. Err error
  88. }
  89. // generateRemoveMultiObjects - generate the XML request for remove multi objects request
  90. func generateRemoveMultiObjectsRequest(objects []string) []byte {
  91. rmObjects := []deleteObject{}
  92. for _, obj := range objects {
  93. rmObjects = append(rmObjects, deleteObject{Key: obj})
  94. }
  95. xmlBytes, _ := xml.Marshal(deleteMultiObjects{Objects: rmObjects, Quiet: true})
  96. return xmlBytes
  97. }
  98. // processRemoveMultiObjectsResponse - parse the remove multi objects web service
  99. // and return the success/failure result status for each object
  100. func processRemoveMultiObjectsResponse(body io.Reader, objects []string, errorCh chan<- RemoveObjectError) {
  101. // Parse multi delete XML response
  102. rmResult := &deleteMultiObjectsResult{}
  103. err := xmlDecoder(body, rmResult)
  104. if err != nil {
  105. errorCh <- RemoveObjectError{ObjectName: "", Err: err}
  106. return
  107. }
  108. // Fill deletion that returned an error.
  109. for _, obj := range rmResult.UnDeletedObjects {
  110. errorCh <- RemoveObjectError{
  111. ObjectName: obj.Key,
  112. Err: ErrorResponse{
  113. Code: obj.Code,
  114. Message: obj.Message,
  115. },
  116. }
  117. }
  118. }
  119. // RemoveObjectsWithContext - Identical to RemoveObjects call, but accepts context to facilitate request cancellation.
  120. func (c Client) RemoveObjectsWithContext(ctx context.Context, bucketName string, objectsCh <-chan string) <-chan RemoveObjectError {
  121. errorCh := make(chan RemoveObjectError, 1)
  122. // Validate if bucket name is valid.
  123. if err := s3utils.CheckValidBucketName(bucketName); err != nil {
  124. defer close(errorCh)
  125. errorCh <- RemoveObjectError{
  126. Err: err,
  127. }
  128. return errorCh
  129. }
  130. // Validate objects channel to be properly allocated.
  131. if objectsCh == nil {
  132. defer close(errorCh)
  133. errorCh <- RemoveObjectError{
  134. Err: ErrInvalidArgument("Objects channel cannot be nil"),
  135. }
  136. return errorCh
  137. }
  138. // Generate and call MultiDelete S3 requests based on entries received from objectsCh
  139. go func(errorCh chan<- RemoveObjectError) {
  140. maxEntries := 1000
  141. finish := false
  142. urlValues := make(url.Values)
  143. urlValues.Set("delete", "")
  144. // Close error channel when Multi delete finishes.
  145. defer close(errorCh)
  146. // Loop over entries by 1000 and call MultiDelete requests
  147. for {
  148. if finish {
  149. break
  150. }
  151. count := 0
  152. var batch []string
  153. // Try to gather 1000 entries
  154. for object := range objectsCh {
  155. batch = append(batch, object)
  156. if count++; count >= maxEntries {
  157. break
  158. }
  159. }
  160. if count == 0 {
  161. // Multi Objects Delete API doesn't accept empty object list, quit immediately
  162. break
  163. }
  164. if count < maxEntries {
  165. // We didn't have 1000 entries, so this is the last batch
  166. finish = true
  167. }
  168. // Generate remove multi objects XML request
  169. removeBytes := generateRemoveMultiObjectsRequest(batch)
  170. // Execute GET on bucket to list objects.
  171. resp, err := c.executeMethod(ctx, "POST", requestMetadata{
  172. bucketName: bucketName,
  173. queryValues: urlValues,
  174. contentBody: bytes.NewReader(removeBytes),
  175. contentLength: int64(len(removeBytes)),
  176. contentMD5Base64: sumMD5Base64(removeBytes),
  177. contentSHA256Hex: sum256Hex(removeBytes),
  178. })
  179. if resp != nil {
  180. if resp.StatusCode != http.StatusOK {
  181. e := httpRespToErrorResponse(resp, bucketName, "")
  182. errorCh <- RemoveObjectError{ObjectName: "", Err: e}
  183. }
  184. }
  185. if err != nil {
  186. for _, b := range batch {
  187. errorCh <- RemoveObjectError{ObjectName: b, Err: err}
  188. }
  189. continue
  190. }
  191. // Process multiobjects remove xml response
  192. processRemoveMultiObjectsResponse(resp.Body, batch, errorCh)
  193. closeResponse(resp)
  194. }
  195. }(errorCh)
  196. return errorCh
  197. }
  198. // RemoveObjects removes multiple objects from a bucket.
  199. // The list of objects to remove are received from objectsCh.
  200. // Remove failures are sent back via error channel.
  201. func (c Client) RemoveObjects(bucketName string, objectsCh <-chan string) <-chan RemoveObjectError {
  202. return c.RemoveObjectsWithContext(context.Background(), bucketName, objectsCh)
  203. }
  204. // RemoveIncompleteUpload aborts an partially uploaded object.
  205. func (c Client) RemoveIncompleteUpload(bucketName, objectName string) error {
  206. // Input validation.
  207. if err := s3utils.CheckValidBucketName(bucketName); err != nil {
  208. return err
  209. }
  210. if err := s3utils.CheckValidObjectName(objectName); err != nil {
  211. return err
  212. }
  213. // Find multipart upload ids of the object to be aborted.
  214. uploadIDs, err := c.findUploadIDs(bucketName, objectName)
  215. if err != nil {
  216. return err
  217. }
  218. for _, uploadID := range uploadIDs {
  219. // abort incomplete multipart upload, based on the upload id passed.
  220. err := c.AbortMultipartUpload(context.Background(), bucketName, objectName, uploadID)
  221. if err != nil {
  222. return err
  223. }
  224. }
  225. return nil
  226. }
  227. // abortMultipartUpload aborts a multipart upload for the given
  228. // uploadID, all previously uploaded parts are deleted.
  229. func (c Client) AbortMultipartUpload(ctx context.Context, bucketName, objectName, uploadID string) error {
  230. // Input validation.
  231. if err := s3utils.CheckValidBucketName(bucketName); err != nil {
  232. return err
  233. }
  234. if err := s3utils.CheckValidObjectName(objectName); err != nil {
  235. return err
  236. }
  237. // Initialize url queries.
  238. urlValues := make(url.Values)
  239. urlValues.Set("uploadId", uploadID)
  240. // Execute DELETE on multipart upload.
  241. resp, err := c.executeMethod(ctx, "DELETE", requestMetadata{
  242. bucketName: bucketName,
  243. objectName: objectName,
  244. queryValues: urlValues,
  245. contentSHA256Hex: emptySHA256Hex,
  246. })
  247. defer closeResponse(resp)
  248. if err != nil {
  249. return err
  250. }
  251. if resp != nil {
  252. if resp.StatusCode != http.StatusNoContent {
  253. // Abort has no response body, handle it for any errors.
  254. var errorResponse ErrorResponse
  255. switch resp.StatusCode {
  256. case http.StatusNotFound:
  257. // This is needed specifically for abort and it cannot
  258. // be converged into default case.
  259. errorResponse = ErrorResponse{
  260. Code: "NoSuchUpload",
  261. Message: "The specified multipart upload does not exist.",
  262. BucketName: bucketName,
  263. Key: objectName,
  264. RequestID: resp.Header.Get("x-amz-request-id"),
  265. HostID: resp.Header.Get("x-amz-id-2"),
  266. Region: resp.Header.Get("x-amz-bucket-region"),
  267. }
  268. default:
  269. return httpRespToErrorResponse(resp, bucketName, objectName)
  270. }
  271. return errorResponse
  272. }
  273. }
  274. return nil
  275. }