copyblob.go 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  1. package storage
  2. // Copyright 2017 Microsoft Corporation
  3. //
  4. // Licensed under the Apache License, Version 2.0 (the "License");
  5. // you may not use this file except in compliance with the License.
  6. // You may obtain a copy of the License at
  7. //
  8. // http://www.apache.org/licenses/LICENSE-2.0
  9. //
  10. // Unless required by applicable law or agreed to in writing, software
  11. // distributed under the License is distributed on an "AS IS" BASIS,
  12. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. // See the License for the specific language governing permissions and
  14. // limitations under the License.
  15. import (
  16. "errors"
  17. "fmt"
  18. "net/http"
  19. "net/url"
  20. "strings"
  21. "time"
  22. )
  23. const (
  24. blobCopyStatusPending = "pending"
  25. blobCopyStatusSuccess = "success"
  26. blobCopyStatusAborted = "aborted"
  27. blobCopyStatusFailed = "failed"
  28. )
  29. // CopyOptions includes the options for a copy blob operation
  30. type CopyOptions struct {
  31. Timeout uint
  32. Source CopyOptionsConditions
  33. Destiny CopyOptionsConditions
  34. RequestID string
  35. }
  36. // IncrementalCopyOptions includes the options for an incremental copy blob operation
  37. type IncrementalCopyOptions struct {
  38. Timeout uint
  39. Destination IncrementalCopyOptionsConditions
  40. RequestID string
  41. }
  42. // CopyOptionsConditions includes some conditional options in a copy blob operation
  43. type CopyOptionsConditions struct {
  44. LeaseID string
  45. IfModifiedSince *time.Time
  46. IfUnmodifiedSince *time.Time
  47. IfMatch string
  48. IfNoneMatch string
  49. }
  50. // IncrementalCopyOptionsConditions includes some conditional options in a copy blob operation
  51. type IncrementalCopyOptionsConditions struct {
  52. IfModifiedSince *time.Time
  53. IfUnmodifiedSince *time.Time
  54. IfMatch string
  55. IfNoneMatch string
  56. }
  57. // Copy starts a blob copy operation and waits for the operation to
  58. // complete. sourceBlob parameter must be a canonical URL to the blob (can be
  59. // obtained using the GetURL method.) There is no SLA on blob copy and therefore
  60. // this helper method works faster on smaller files.
  61. //
  62. // See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/Copy-Blob
  63. func (b *Blob) Copy(sourceBlob string, options *CopyOptions) error {
  64. copyID, err := b.StartCopy(sourceBlob, options)
  65. if err != nil {
  66. return err
  67. }
  68. return b.WaitForCopy(copyID)
  69. }
  70. // StartCopy starts a blob copy operation.
  71. // sourceBlob parameter must be a canonical URL to the blob (can be
  72. // obtained using the GetURL method.)
  73. //
  74. // See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/Copy-Blob
  75. func (b *Blob) StartCopy(sourceBlob string, options *CopyOptions) (string, error) {
  76. params := url.Values{}
  77. headers := b.Container.bsc.client.getStandardHeaders()
  78. headers["x-ms-copy-source"] = sourceBlob
  79. headers = b.Container.bsc.client.addMetadataToHeaders(headers, b.Metadata)
  80. if options != nil {
  81. params = addTimeout(params, options.Timeout)
  82. headers = addToHeaders(headers, "x-ms-client-request-id", options.RequestID)
  83. // source
  84. headers = addToHeaders(headers, "x-ms-source-lease-id", options.Source.LeaseID)
  85. headers = addTimeToHeaders(headers, "x-ms-source-if-modified-since", options.Source.IfModifiedSince)
  86. headers = addTimeToHeaders(headers, "x-ms-source-if-unmodified-since", options.Source.IfUnmodifiedSince)
  87. headers = addToHeaders(headers, "x-ms-source-if-match", options.Source.IfMatch)
  88. headers = addToHeaders(headers, "x-ms-source-if-none-match", options.Source.IfNoneMatch)
  89. //destiny
  90. headers = addToHeaders(headers, "x-ms-lease-id", options.Destiny.LeaseID)
  91. headers = addTimeToHeaders(headers, "x-ms-if-modified-since", options.Destiny.IfModifiedSince)
  92. headers = addTimeToHeaders(headers, "x-ms-if-unmodified-since", options.Destiny.IfUnmodifiedSince)
  93. headers = addToHeaders(headers, "x-ms-if-match", options.Destiny.IfMatch)
  94. headers = addToHeaders(headers, "x-ms-if-none-match", options.Destiny.IfNoneMatch)
  95. }
  96. uri := b.Container.bsc.client.getEndpoint(blobServiceName, b.buildPath(), params)
  97. resp, err := b.Container.bsc.client.exec(http.MethodPut, uri, headers, nil, b.Container.bsc.auth)
  98. if err != nil {
  99. return "", err
  100. }
  101. defer drainRespBody(resp)
  102. if err := checkRespCode(resp, []int{http.StatusAccepted, http.StatusCreated}); err != nil {
  103. return "", err
  104. }
  105. copyID := resp.Header.Get("x-ms-copy-id")
  106. if copyID == "" {
  107. return "", errors.New("Got empty copy id header")
  108. }
  109. return copyID, nil
  110. }
  111. // AbortCopyOptions includes the options for an abort blob operation
  112. type AbortCopyOptions struct {
  113. Timeout uint
  114. LeaseID string `header:"x-ms-lease-id"`
  115. RequestID string `header:"x-ms-client-request-id"`
  116. }
  117. // AbortCopy aborts a BlobCopy which has already been triggered by the StartBlobCopy function.
  118. // copyID is generated from StartBlobCopy function.
  119. // currentLeaseID is required IF the destination blob has an active lease on it.
  120. // See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/Abort-Copy-Blob
  121. func (b *Blob) AbortCopy(copyID string, options *AbortCopyOptions) error {
  122. params := url.Values{
  123. "comp": {"copy"},
  124. "copyid": {copyID},
  125. }
  126. headers := b.Container.bsc.client.getStandardHeaders()
  127. headers["x-ms-copy-action"] = "abort"
  128. if options != nil {
  129. params = addTimeout(params, options.Timeout)
  130. headers = mergeHeaders(headers, headersFromStruct(*options))
  131. }
  132. uri := b.Container.bsc.client.getEndpoint(blobServiceName, b.buildPath(), params)
  133. resp, err := b.Container.bsc.client.exec(http.MethodPut, uri, headers, nil, b.Container.bsc.auth)
  134. if err != nil {
  135. return err
  136. }
  137. defer drainRespBody(resp)
  138. return checkRespCode(resp, []int{http.StatusNoContent})
  139. }
  140. // WaitForCopy loops until a BlobCopy operation is completed (or fails with error)
  141. func (b *Blob) WaitForCopy(copyID string) error {
  142. for {
  143. err := b.GetProperties(nil)
  144. if err != nil {
  145. return err
  146. }
  147. if b.Properties.CopyID != copyID {
  148. return errBlobCopyIDMismatch
  149. }
  150. switch b.Properties.CopyStatus {
  151. case blobCopyStatusSuccess:
  152. return nil
  153. case blobCopyStatusPending:
  154. continue
  155. case blobCopyStatusAborted:
  156. return errBlobCopyAborted
  157. case blobCopyStatusFailed:
  158. return fmt.Errorf("storage: blob copy failed. Id=%s Description=%s", b.Properties.CopyID, b.Properties.CopyStatusDescription)
  159. default:
  160. return fmt.Errorf("storage: unhandled blob copy status: '%s'", b.Properties.CopyStatus)
  161. }
  162. }
  163. }
  164. // IncrementalCopyBlob copies a snapshot of a source blob and copies to referring blob
  165. // sourceBlob parameter must be a valid snapshot URL of the original blob.
  166. // THe original blob mut be public, or use a Shared Access Signature.
  167. //
  168. // See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/incremental-copy-blob .
  169. func (b *Blob) IncrementalCopyBlob(sourceBlobURL string, snapshotTime time.Time, options *IncrementalCopyOptions) (string, error) {
  170. params := url.Values{"comp": {"incrementalcopy"}}
  171. // need formatting to 7 decimal places so it's friendly to Windows and *nix
  172. snapshotTimeFormatted := snapshotTime.Format("2006-01-02T15:04:05.0000000Z")
  173. u, err := url.Parse(sourceBlobURL)
  174. if err != nil {
  175. return "", err
  176. }
  177. query := u.Query()
  178. query.Add("snapshot", snapshotTimeFormatted)
  179. encodedQuery := query.Encode()
  180. encodedQuery = strings.Replace(encodedQuery, "%3A", ":", -1)
  181. u.RawQuery = encodedQuery
  182. snapshotURL := u.String()
  183. headers := b.Container.bsc.client.getStandardHeaders()
  184. headers["x-ms-copy-source"] = snapshotURL
  185. if options != nil {
  186. addTimeout(params, options.Timeout)
  187. headers = addToHeaders(headers, "x-ms-client-request-id", options.RequestID)
  188. headers = addTimeToHeaders(headers, "x-ms-if-modified-since", options.Destination.IfModifiedSince)
  189. headers = addTimeToHeaders(headers, "x-ms-if-unmodified-since", options.Destination.IfUnmodifiedSince)
  190. headers = addToHeaders(headers, "x-ms-if-match", options.Destination.IfMatch)
  191. headers = addToHeaders(headers, "x-ms-if-none-match", options.Destination.IfNoneMatch)
  192. }
  193. // get URI of destination blob
  194. uri := b.Container.bsc.client.getEndpoint(blobServiceName, b.buildPath(), params)
  195. resp, err := b.Container.bsc.client.exec(http.MethodPut, uri, headers, nil, b.Container.bsc.auth)
  196. if err != nil {
  197. return "", err
  198. }
  199. defer drainRespBody(resp)
  200. if err := checkRespCode(resp, []int{http.StatusAccepted}); err != nil {
  201. return "", err
  202. }
  203. copyID := resp.Header.Get("x-ms-copy-id")
  204. if copyID == "" {
  205. return "", errors.New("Got empty copy id header")
  206. }
  207. return copyID, nil
  208. }