1 package storage
2
3
4
5
6 import (
7 "errors"
8 "fmt"
9 "net/http"
10 "net/url"
11 "strings"
12 "time"
13 )
14
15 const (
16 blobCopyStatusPending = "pending"
17 blobCopyStatusSuccess = "success"
18 blobCopyStatusAborted = "aborted"
19 blobCopyStatusFailed = "failed"
20 )
21
22
23 type CopyOptions struct {
24 Timeout uint
25 Source CopyOptionsConditions
26 Destiny CopyOptionsConditions
27 RequestID string
28 }
29
30
31 type IncrementalCopyOptions struct {
32 Timeout uint
33 Destination IncrementalCopyOptionsConditions
34 RequestID string
35 }
36
37
38 type CopyOptionsConditions struct {
39 LeaseID string
40 IfModifiedSince *time.Time
41 IfUnmodifiedSince *time.Time
42 IfMatch string
43 IfNoneMatch string
44 }
45
46
47 type IncrementalCopyOptionsConditions struct {
48 IfModifiedSince *time.Time
49 IfUnmodifiedSince *time.Time
50 IfMatch string
51 IfNoneMatch string
52 }
53
54
55
56
57
58
59
60 func (b *Blob) Copy(sourceBlob string, options *CopyOptions) error {
61 copyID, err := b.StartCopy(sourceBlob, options)
62 if err != nil {
63 return err
64 }
65
66 return b.WaitForCopy(copyID)
67 }
68
69
70
71
72
73
74 func (b *Blob) StartCopy(sourceBlob string, options *CopyOptions) (string, error) {
75 params := url.Values{}
76 headers := b.Container.bsc.client.getStandardHeaders()
77 headers["x-ms-copy-source"] = sourceBlob
78 headers = b.Container.bsc.client.addMetadataToHeaders(headers, b.Metadata)
79
80 if options != nil {
81 params = addTimeout(params, options.Timeout)
82 headers = addToHeaders(headers, "x-ms-client-request-id", options.RequestID)
83
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
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
98 resp, err := b.Container.bsc.client.exec(http.MethodPut, uri, headers, nil, b.Container.bsc.auth)
99 if err != nil {
100 return "", err
101 }
102 defer drainRespBody(resp)
103
104 if err := checkRespCode(resp, []int{http.StatusAccepted, http.StatusCreated}); err != nil {
105 return "", err
106 }
107
108 copyID := resp.Header.Get("x-ms-copy-id")
109 if copyID == "" {
110 return "", errors.New("Got empty copy id header")
111 }
112 return copyID, nil
113 }
114
115
116 type AbortCopyOptions struct {
117 Timeout uint
118 LeaseID string `header:"x-ms-lease-id"`
119 RequestID string `header:"x-ms-client-request-id"`
120 }
121
122
123
124
125
126 func (b *Blob) AbortCopy(copyID string, options *AbortCopyOptions) error {
127 params := url.Values{
128 "comp": {"copy"},
129 "copyid": {copyID},
130 }
131 headers := b.Container.bsc.client.getStandardHeaders()
132 headers["x-ms-copy-action"] = "abort"
133
134 if options != nil {
135 params = addTimeout(params, options.Timeout)
136 headers = mergeHeaders(headers, headersFromStruct(*options))
137 }
138 uri := b.Container.bsc.client.getEndpoint(blobServiceName, b.buildPath(), params)
139
140 resp, err := b.Container.bsc.client.exec(http.MethodPut, uri, headers, nil, b.Container.bsc.auth)
141 if err != nil {
142 return err
143 }
144 defer drainRespBody(resp)
145 return checkRespCode(resp, []int{http.StatusNoContent})
146 }
147
148
149 func (b *Blob) WaitForCopy(copyID string) error {
150 for {
151 err := b.GetProperties(nil)
152 if err != nil {
153 return err
154 }
155
156 if b.Properties.CopyID != copyID {
157 return errBlobCopyIDMismatch
158 }
159
160 switch b.Properties.CopyStatus {
161 case blobCopyStatusSuccess:
162 return nil
163 case blobCopyStatusPending:
164 continue
165 case blobCopyStatusAborted:
166 return errBlobCopyAborted
167 case blobCopyStatusFailed:
168 return fmt.Errorf("storage: blob copy failed. Id=%s Description=%s", b.Properties.CopyID, b.Properties.CopyStatusDescription)
169 default:
170 return fmt.Errorf("storage: unhandled blob copy status: '%s'", b.Properties.CopyStatus)
171 }
172 }
173 }
174
175
176
177
178
179
180 func (b *Blob) IncrementalCopyBlob(sourceBlobURL string, snapshotTime time.Time, options *IncrementalCopyOptions) (string, error) {
181 params := url.Values{"comp": {"incrementalcopy"}}
182
183
184 snapshotTimeFormatted := snapshotTime.Format("2006-01-02T15:04:05.0000000Z")
185 u, err := url.Parse(sourceBlobURL)
186 if err != nil {
187 return "", err
188 }
189 query := u.Query()
190 query.Add("snapshot", snapshotTimeFormatted)
191 encodedQuery := query.Encode()
192 encodedQuery = strings.Replace(encodedQuery, "%3A", ":", -1)
193 u.RawQuery = encodedQuery
194 snapshotURL := u.String()
195
196 headers := b.Container.bsc.client.getStandardHeaders()
197 headers["x-ms-copy-source"] = snapshotURL
198
199 if options != nil {
200 addTimeout(params, options.Timeout)
201 headers = addToHeaders(headers, "x-ms-client-request-id", options.RequestID)
202 headers = addTimeToHeaders(headers, "x-ms-if-modified-since", options.Destination.IfModifiedSince)
203 headers = addTimeToHeaders(headers, "x-ms-if-unmodified-since", options.Destination.IfUnmodifiedSince)
204 headers = addToHeaders(headers, "x-ms-if-match", options.Destination.IfMatch)
205 headers = addToHeaders(headers, "x-ms-if-none-match", options.Destination.IfNoneMatch)
206 }
207
208
209 uri := b.Container.bsc.client.getEndpoint(blobServiceName, b.buildPath(), params)
210
211 resp, err := b.Container.bsc.client.exec(http.MethodPut, uri, headers, nil, b.Container.bsc.auth)
212 if err != nil {
213 return "", err
214 }
215 defer drainRespBody(resp)
216
217 if err := checkRespCode(resp, []int{http.StatusAccepted}); err != nil {
218 return "", err
219 }
220
221 copyID := resp.Header.Get("x-ms-copy-id")
222 if copyID == "" {
223 return "", errors.New("Got empty copy id header")
224 }
225 return copyID, nil
226 }
227
View as plain text