14.2.1
by Julian Edwards
Add storage beginning |
1 |
// Copyright 2013 Canonical Ltd. This software is licensed under the
|
2 |
// GNU Lesser General Public License version 3 (see the file COPYING).
|
|
3 |
||
4 |
package gwacl |
|
5 |
||
14.2.13
by Julian Edwards
rvb's review suggestions |
6 |
// This file contains the operations necessary to work with the Azure
|
7 |
// file storage API. For more details, see
|
|
8 |
// http://msdn.microsoft.com/en-us/library/windowsazure/dd179355.aspx
|
|
9 |
||
50.2.2
by Gavin Panella
TODO for fixing documentation. |
10 |
// TODO Improve function documentation: the Go documentation convention is for
|
11 |
// function documentation to start out with the name of the function. This may
|
|
12 |
// have special significance for godoc.
|
|
13 |
||
14.2.3
by Julian Edwards
add TestComposeCanonicalizedResource |
14 |
import ( |
17.1.2
by Julian Edwards
Add addMD5Header |
15 |
"bytes"
|
14.2.10
by Julian Edwards
Add composeAuthHeader |
16 |
"crypto/hmac"
|
17 |
"crypto/sha256"
|
|
18 |
"encoding/base64"
|
|
101.1.2
by Jeroen Vermeulen
Smooth out some redundant error-checking. Create performRequest method, with its Parameter Object struct. Use HTTPStatus rather than int for HTTP statuses. |
19 |
"errors"
|
14.2.5
by Julian Edwards
add toLowerKeys |
20 |
"fmt"
|
42.1.1
by Julian Edwards
Add PutBlock |
21 |
"io"
|
17.1.2
by Julian Edwards
Add addMD5Header |
22 |
"io/ioutil"
|
14.2.3
by Julian Edwards
add TestComposeCanonicalizedResource |
23 |
"net/http"
|
14.2.5
by Julian Edwards
add toLowerKeys |
24 |
"net/url"
|
25 |
"sort"
|
|
14.2.3
by Julian Edwards
add TestComposeCanonicalizedResource |
26 |
"strings"
|
17.1.5
by Julian Edwards
Add Date header |
27 |
"time"
|
14.2.3
by Julian Edwards
add TestComposeCanonicalizedResource |
28 |
)
|
14.2.1
by Julian Edwards
Add storage beginning |
29 |
|
139.1.1
by Gavin Panella
Camel-case everything, clean-up some comments. |
30 |
var headersToSign = []string{ |
14.2.1
by Julian Edwards
Add storage beginning |
31 |
"Content-Encoding", |
32 |
"Content-Language", |
|
33 |
"Content-Length", |
|
34 |
"Content-MD5", |
|
35 |
"Content-Type", |
|
36 |
"Date", |
|
37 |
"If-Modified-Since", |
|
38 |
"If-Match", |
|
39 |
"If-None-Match", |
|
40 |
"If-Unmodified-Since", |
|
41 |
"Range", |
|
42 |
}
|
|
43 |
||
17.1.6
by Julian Edwards
allenap's review suggestions |
44 |
// Calculate the value required for an Authorization header.
|
139.1.1
by Gavin Panella
Camel-case everything, clean-up some comments. |
45 |
func composeAuthHeader(req *http.Request, accountName, accountKey string) string { |
46 |
signable := composeStringToSign(req, accountName) |
|
14.2.12
by Julian Edwards
small cleanups |
47 |
// Allegedly, this is already UTF8 encoded.
|
139.1.1
by Gavin Panella
Camel-case everything, clean-up some comments. |
48 |
decodedKey, err := base64.StdEncoding.DecodeString(accountKey) |
14.2.10
by Julian Edwards
Add composeAuthHeader |
49 |
if err != nil { |
50 |
panic(fmt.Errorf("invalid account key: %s", err)) |
|
51 |
}
|
|
139.1.1
by Gavin Panella
Camel-case everything, clean-up some comments. |
52 |
hash := hmac.New(sha256.New, decodedKey) |
14.2.10
by Julian Edwards
Add composeAuthHeader |
53 |
_, err = hash.Write([]byte(signable)) |
14.2.12
by Julian Edwards
small cleanups |
54 |
if err != nil { |
55 |
panic(fmt.Errorf("failed to write hash: %s", err)) |
|
56 |
}
|
|
14.2.10
by Julian Edwards
Add composeAuthHeader |
57 |
var hashed []byte |
58 |
hashed = hash.Sum(hashed) |
|
139.1.1
by Gavin Panella
Camel-case everything, clean-up some comments. |
59 |
b64Hashed := base64.StdEncoding.EncodeToString(hashed) |
60 |
return fmt.Sprintf("SharedKey %s:%s", accountName, b64Hashed) |
|
14.2.10
by Julian Edwards
Add composeAuthHeader |
61 |
}
|
62 |
||
17.1.6
by Julian Edwards
allenap's review suggestions |
63 |
// Calculate the string that needs to be HMAC signed. It is comprised of
|
139.1.1
by Gavin Panella
Camel-case everything, clean-up some comments. |
64 |
// the headers in headersToSign, x-ms-* headers and the URI params.
|
65 |
func composeStringToSign(req *http.Request, accountName string) string { |
|
17.1.6
by Julian Edwards
allenap's review suggestions |
66 |
// TODO: whitespace should be normalised in value strings.
|
14.2.9
by Julian Edwards
add composeStringToSign |
67 |
return fmt.Sprintf( |
17.1.4
by Julian Edwards
Add newlines consistently in each processed section |
68 |
"%s\n%s%s%s", req.Method, composeHeaders(req), |
17.1.3
by Julian Edwards
Add Canonicalized Headers computation |
69 |
composeCanonicalizedHeaders(req), |
139.1.1
by Gavin Panella
Camel-case everything, clean-up some comments. |
70 |
composeCanonicalizedResource(req, accountName)) |
14.2.1
by Julian Edwards
Add storage beginning |
71 |
}
|
72 |
||
14.2.5
by Julian Edwards
add toLowerKeys |
73 |
// toLowerKeys lower cases all map keys. If two keys exist, that differ
|
74 |
// by the case of their keys, the values will be concatenated.
|
|
75 |
func toLowerKeys(values url.Values) map[string][]string { |
|
76 |
m := make(map[string][]string) |
|
77 |
for k, v := range values { |
|
78 |
k = strings.ToLower(k) |
|
79 |
m[k] = append(m[k], v...) |
|
80 |
}
|
|
81 |
for _, v := range m { |
|
82 |
sort.Strings(v) |
|
83 |
}
|
|
84 |
return m |
|
85 |
}
|
|
86 |
||
17.1.6
by Julian Edwards
allenap's review suggestions |
87 |
// Encode the URI params as required by the API. They are lower-cased,
|
88 |
// sorted and formatted as param:value,value,...\nparam:value...
|
|
14.2.5
by Julian Edwards
add toLowerKeys |
89 |
func encodeParams(values map[string][]string) string { |
90 |
var keys []string |
|
91 |
values = toLowerKeys(values) |
|
92 |
for k := range values { |
|
93 |
keys = append(keys, k) |
|
94 |
}
|
|
95 |
sort.Strings(keys) |
|
96 |
var result []string |
|
97 |
for _, v := range keys { |
|
98 |
result = append(result, fmt.Sprintf("%v:%s", v, strings.Join(values[v], ","))) |
|
99 |
}
|
|
100 |
return strings.Join(result, "\n") |
|
101 |
}
|
|
102 |
||
17.1.6
by Julian Edwards
allenap's review suggestions |
103 |
// Calculate the headers required in the string to sign.
|
14.2.1
by Julian Edwards
Add storage beginning |
104 |
func composeHeaders(req *http.Request) string { |
14.2.9
by Julian Edwards
add composeStringToSign |
105 |
var result []string |
139.1.1
by Gavin Panella
Camel-case everything, clean-up some comments. |
106 |
for _, headerName := range headersToSign { |
107 |
result = append(result, req.Header.Get(headerName)+"\n") |
|
14.2.1
by Julian Edwards
Add storage beginning |
108 |
}
|
17.1.4
by Julian Edwards
Add newlines consistently in each processed section |
109 |
return strings.Join(result, "") |
14.2.1
by Julian Edwards
Add storage beginning |
110 |
}
|
111 |
||
17.1.6
by Julian Edwards
allenap's review suggestions |
112 |
// Calculate the x-ms-* headers, encode as for encodeParams.
|
17.1.3
by Julian Edwards
Add Canonicalized Headers computation |
113 |
func composeCanonicalizedHeaders(req *http.Request) string { |
114 |
var results []string |
|
139.1.1
by Gavin Panella
Camel-case everything, clean-up some comments. |
115 |
for headerName, values := range req.Header { |
116 |
headerName = strings.ToLower(headerName) |
|
117 |
if strings.HasPrefix(headerName, "x-ms-") { |
|
118 |
results = append(results, fmt.Sprintf("%v:%s\n", headerName, strings.Join(values, ","))) |
|
17.1.3
by Julian Edwards
Add Canonicalized Headers computation |
119 |
}
|
120 |
}
|
|
121 |
sort.Strings(results) |
|
122 |
return strings.Join(results, "") |
|
123 |
}
|
|
124 |
||
17.1.6
by Julian Edwards
allenap's review suggestions |
125 |
// Calculate the URI params and encode them in the string.
|
126 |
// See http://msdn.microsoft.com/en-us/library/windowsazure/dd179428.aspx
|
|
127 |
// for details of this encoding.
|
|
139.1.1
by Gavin Panella
Camel-case everything, clean-up some comments. |
128 |
func composeCanonicalizedResource(req *http.Request, accountName string) string { |
14.2.3
by Julian Edwards
add TestComposeCanonicalizedResource |
129 |
path := req.URL.Path |
130 |
if !strings.HasPrefix(path, "/") { |
|
131 |
path = "/" + path |
|
132 |
}
|
|
14.2.5
by Julian Edwards
add toLowerKeys |
133 |
|
14.2.8
by Julian Edwards
final tests |
134 |
values := req.URL.Query() |
139.1.1
by Gavin Panella
Camel-case everything, clean-up some comments. |
135 |
valuesLower := toLowerKeys(values) |
136 |
paramString := encodeParams(valuesLower) |
|
14.2.8
by Julian Edwards
final tests |
137 |
|
139.1.1
by Gavin Panella
Camel-case everything, clean-up some comments. |
138 |
result := "/" + accountName + path |
139 |
if paramString != "" { |
|
140 |
result += "\n" + paramString |
|
14.2.8
by Julian Edwards
final tests |
141 |
}
|
142 |
||
143 |
return result |
|
14.2.1
by Julian Edwards
Add storage beginning |
144 |
}
|
17.1.1
by Julian Edwards
Add addVersionHeader |
145 |
|
139.1.1
by Gavin Panella
Camel-case everything, clean-up some comments. |
146 |
// Take the passed msVersion string and add it to the request headers.
|
147 |
func addVersionHeader(req *http.Request, msVersion string) { |
|
148 |
req.Header.Set("x-ms-version", msVersion) |
|
17.1.1
by Julian Edwards
Add addVersionHeader |
149 |
}
|
17.1.2
by Julian Edwards
Add addMD5Header |
150 |
|
43.2.1
by Julian Edwards
Add content-length to outgoing requests |
151 |
// Calculate the mD5sum and content length for the request payload and add
|
152 |
// as the Content-MD5 header and Content-Length header respectively.
|
|
153 |
func addContentHeaders(req *http.Request) { |
|
154 |
if req.Body == nil { |
|
45.1.3
by Julian Edwards
changes to make PutBlock work - prevent Go from chunking the data |
155 |
if req.Method == "PUT" || req.Method == "POST" { |
45.1.5
by Julian Edwards
Tweaks as per allenap |
156 |
// This cannot be set for a GET, likewise it *must* be set for
|
157 |
// PUT and POST.
|
|
45.1.3
by Julian Edwards
changes to make PutBlock work - prevent Go from chunking the data |
158 |
req.Header.Set("Content-Length", "0") |
159 |
}
|
|
43.2.1
by Julian Edwards
Add content-length to outgoing requests |
160 |
return
|
161 |
}
|
|
17.1.2
by Julian Edwards
Add addMD5Header |
162 |
reqdata, err := ioutil.ReadAll(req.Body) |
163 |
if err != nil { |
|
164 |
panic(fmt.Errorf("Unable to read request body: %s", err)) |
|
165 |
}
|
|
166 |
// Replace the request's data because we just destroyed it by reading it.
|
|
167 |
req.Body = ioutil.NopCloser(bytes.NewReader(reqdata)) |
|
43.2.1
by Julian Edwards
Add content-length to outgoing requests |
168 |
req.Header.Set("Content-Length", fmt.Sprintf("%d", len(reqdata))) |
45.1.3
by Julian Edwards
changes to make PutBlock work - prevent Go from chunking the data |
169 |
// Stop Go's http lib from chunking the data because Azure will return
|
170 |
// an authorization error if it's chunked.
|
|
171 |
req.ContentLength = int64(len(reqdata)) |
|
17.1.2
by Julian Edwards
Add addMD5Header |
172 |
}
|
17.1.5
by Julian Edwards
Add Date header |
173 |
|
17.1.6
by Julian Edwards
allenap's review suggestions |
174 |
// Add a Date: header in RFC1123 format.
|
17.1.5
by Julian Edwards
Add Date header |
175 |
func addDateHeader(req *http.Request) { |
176 |
now := time.Now().UTC().Format(time.RFC1123) |
|
17.1.6
by Julian Edwards
allenap's review suggestions |
177 |
// The Azure API requires "GMT" and not "UTC".
|
17.1.5
by Julian Edwards
Add Date header |
178 |
now = strings.Replace(now, "UTC", "GMT", 1) |
45.1.5
by Julian Edwards
Tweaks as per allenap |
179 |
req.Header.Set("Date", now) |
17.1.5
by Julian Edwards
Add Date header |
180 |
}
|
181 |
||
101.1.1
by Jeroen Vermeulen
Extract signRequest from addStandardHeaders, and make it a method. |
182 |
// signRequest adds the Authorization: header to a Request.
|
183 |
// Don't make any further changes to the request before sending it, or the
|
|
184 |
// signature will not be valid.
|
|
185 |
func (context *StorageContext) signRequest(req *http.Request) { |
|
127.2.1
by Raphael Badin
If key is empty: anon access. |
186 |
// Only sign the request if the key is not empty.
|
187 |
if context.Key != "" { |
|
188 |
header := composeAuthHeader(req, context.Account, context.Key) |
|
189 |
req.Header.Set("Authorization", header) |
|
190 |
}
|
|
17.1.5
by Julian Edwards
Add Date header |
191 |
}
|
192 |
||
30.1.1
by Julian Edwards
Create StorageContext.Send() and add a ListBlobs() that uses it. |
193 |
// StorageContext keeps track of the mandatory parameters required to send a
|
139.1.1
by Gavin Panella
Camel-case everything, clean-up some comments. |
194 |
// request to the storage services API. It also has an HTTP Client to allow
|
195 |
// overriding for custom behaviour, during testing for example.
|
|
26.1.1
by Gavin Panella
Add StorageContext. |
196 |
type StorageContext struct { |
197 |
Account string |
|
127.2.2
by Raphael Badin
Fix comment. |
198 |
// Access key: access will be anonymous if the key is the empty string.
|
127.2.1
by Raphael Badin
If key is empty: anon access. |
199 |
Key string |
200 |
client *http.Client |
|
26.1.1
by Gavin Panella
Add StorageContext. |
201 |
}
|
202 |
||
139.1.1
by Gavin Panella
Camel-case everything, clean-up some comments. |
203 |
// getClient is used when sending a request. If a custom client is specified
|
204 |
// in context.client it is returned, otherwise net.http.DefaultClient is
|
|
205 |
// returned.
|
|
26.1.1
by Gavin Panella
Add StorageContext. |
206 |
func (context *StorageContext) getClient() *http.Client { |
36.1.3
by Gavin Panella
Make StorageContext.Client private. |
207 |
if context.client == nil { |
26.1.1
by Gavin Panella
Add StorageContext. |
208 |
return http.DefaultClient |
209 |
}
|
|
36.1.3
by Gavin Panella
Make StorageContext.Client private. |
210 |
return context.client |
26.1.1
by Gavin Panella
Add StorageContext. |
211 |
}
|
212 |
||
30.1.1
by Julian Edwards
Create StorageContext.Send() and add a ListBlobs() that uses it. |
213 |
// Any object that deserializes XML must meet this interface.
|
214 |
type Deserializer interface { |
|
215 |
Deserialize([]byte) error |
|
216 |
}
|
|
217 |
||
101.1.2
by Jeroen Vermeulen
Smooth out some redundant error-checking. Create performRequest method, with its Parameter Object struct. Use HTTPStatus rather than int for HTTP statuses. |
218 |
// requestParams is a Parameter Object for performRequest().
|
219 |
type requestParams struct { |
|
220 |
Method string // HTTP method, e.g. "GET" or "PUT". |
|
221 |
URL string // Resource locator, e.g. "http://example.com/my/resource". |
|
222 |
Body io.Reader // Optional request body. |
|
223 |
APIVersion string // Expected Azure API version, e.g. "2012-02-12". |
|
108.3.2
by Julian Edwards
Add x-ms-blob-type header to PutBlob request |
224 |
ExtraHeaders http.Header // Optional extra request headers. |
101.1.2
by Jeroen Vermeulen
Smooth out some redundant error-checking. Create performRequest method, with its Parameter Object struct. Use HTTPStatus rather than int for HTTP statuses. |
225 |
Result Deserializer // Optional object to parse API response into. |
226 |
ExpectedStatus HTTPStatus // Expected response status, e.g. http.StatusOK. |
|
227 |
}
|
|
228 |
||
229 |
// Check performs a basic sanity check on the request. This will only catch
|
|
230 |
// a few superficial problems that you can spot at compile time, to save a
|
|
231 |
// debugging cycle for the most basic mistakes.
|
|
232 |
func (params *requestParams) Check() { |
|
233 |
const panicPrefix = "invalid request: " |
|
234 |
if params.Method == "" { |
|
235 |
panic(errors.New(panicPrefix + "HTTP method not specified")) |
|
236 |
}
|
|
237 |
if params.URL == "" { |
|
238 |
panic(errors.New(panicPrefix + "URL not specified")) |
|
239 |
}
|
|
240 |
if params.APIVersion == "" { |
|
241 |
panic(errors.New(panicPrefix + "API version not specified")) |
|
242 |
}
|
|
243 |
if params.ExpectedStatus == 0 { |
|
244 |
panic(errors.New(panicPrefix + "expected HTTP status not specified")) |
|
245 |
}
|
|
246 |
methods := map[string]bool{"GET": true, "PUT": true, "POST": true, "DELETE": true} |
|
247 |
if _, ok := methods[params.Method]; !ok { |
|
248 |
panic(fmt.Errorf(panicPrefix+"unsupported HTTP method '%s'", params.Method)) |
|
249 |
}
|
|
250 |
}
|
|
251 |
||
252 |
// performRequest issues an HTTP request to Azure.
|
|
253 |
func (context *StorageContext) performRequest(params requestParams) (*http.Response, error) { |
|
101.1.10
by Jeroen Vermeulen
Review change: forgot to call requestParams.Check(). |
254 |
params.Check() |
101.1.2
by Jeroen Vermeulen
Smooth out some redundant error-checking. Create performRequest method, with its Parameter Object struct. Use HTTPStatus rather than int for HTTP statuses. |
255 |
req, err := http.NewRequest(params.Method, params.URL, params.Body) |
256 |
if err != nil { |
|
257 |
return nil, err |
|
258 |
}
|
|
108.3.2
by Julian Edwards
Add x-ms-blob-type header to PutBlob request |
259 |
// net/http has no way of adding headers en-masse, hence this abomination.
|
260 |
for header, values := range params.ExtraHeaders { |
|
261 |
for _, value := range values { |
|
262 |
req.Header.Add(header, value) |
|
263 |
}
|
|
264 |
}
|
|
101.1.7
by Jeroen Vermeulen
No longer need addStandardHeaders. |
265 |
addVersionHeader(req, params.APIVersion) |
266 |
addDateHeader(req) |
|
267 |
addContentHeaders(req) |
|
101.1.2
by Jeroen Vermeulen
Smooth out some redundant error-checking. Create performRequest method, with its Parameter Object struct. Use HTTPStatus rather than int for HTTP statuses. |
268 |
context.signRequest(req) |
269 |
return context.send(req, params.Result, params.ExpectedStatus) |
|
270 |
}
|
|
271 |
||
30.1.1
by Julian Edwards
Create StorageContext.Send() and add a ListBlobs() that uses it. |
272 |
// Send a request to the storage service and process the response.
|
273 |
// The "res" parameter is typically an XML struct that will deserialize the
|
|
59.1.1
by Julian Edwards
Add error handling to the storage context send() |
274 |
// raw XML into the struct data. The http Response object is returned.
|
65.1.1
by Jeroen Vermeulen
Unify Error and ServerError as HTTPError, with different implementations based on what information is available at runtime. |
275 |
//
|
139.1.1
by Gavin Panella
Camel-case everything, clean-up some comments. |
276 |
// If the response's HTTP status code is not the same as "expectedStatus"
|
65.1.1
by Jeroen Vermeulen
Unify Error and ServerError as HTTPError, with different implementations based on what information is available at runtime. |
277 |
// then an HTTPError will be returned as the error. When the returned error
|
278 |
// is an HTTPError, the request response is also returned. In other error
|
|
279 |
// cases, the returned response may be the one received from the server or
|
|
280 |
// it may be nil.
|
|
139.1.1
by Gavin Panella
Camel-case everything, clean-up some comments. |
281 |
func (context *StorageContext) send(req *http.Request, res Deserializer, expectedStatus HTTPStatus) (*http.Response, error) { |
30.1.1
by Julian Edwards
Create StorageContext.Send() and add a ListBlobs() that uses it. |
282 |
client := context.getClient() |
283 |
resp, err := client.Do(req) |
|
284 |
if err != nil { |
|
65.1.1
by Jeroen Vermeulen
Unify Error and ServerError as HTTPError, with different implementations based on what information is available at runtime. |
285 |
return nil, err |
59.1.1
by Julian Edwards
Add error handling to the storage context send() |
286 |
}
|
287 |
||
288 |
var data []byte |
|
289 |
||
139.1.1
by Gavin Panella
Camel-case everything, clean-up some comments. |
290 |
if resp.StatusCode != int(expectedStatus) { |
59.1.1
by Julian Edwards
Add error handling to the storage context send() |
291 |
if resp.Body != nil { |
292 |
data, err = ioutil.ReadAll(resp.Body) |
|
293 |
if err != nil { |
|
65.1.1
by Jeroen Vermeulen
Unify Error and ServerError as HTTPError, with different implementations based on what information is available at runtime. |
294 |
return resp, err |
59.1.1
by Julian Edwards
Add error handling to the storage context send() |
295 |
}
|
296 |
}
|
|
65.1.1
by Jeroen Vermeulen
Unify Error and ServerError as HTTPError, with different implementations based on what information is available at runtime. |
297 |
msg := newHTTPError(resp.StatusCode, data, "Azure request failed") |
298 |
return resp, msg |
|
59.1.1
by Julian Edwards
Add error handling to the storage context send() |
299 |
}
|
300 |
||
301 |
// If the caller didn't supply an object to deserialize the message into
|
|
302 |
// then just return.
|
|
36.1.1
by Gavin Panella
Return the HTTP response from Send. |
303 |
if res == nil { |
304 |
return resp, nil |
|
30.1.1
by Julian Edwards
Create StorageContext.Send() and add a ListBlobs() that uses it. |
305 |
}
|
306 |
||
101.1.8
by Jeroen Vermeulen
Fold base URL getter calls into addition of query params. |
307 |
// TODO: Also deserialize response headers into the "res" object.
|
59.1.1
by Julian Edwards
Add error handling to the storage context send() |
308 |
data, err = ioutil.ReadAll(resp.Body) |
30.1.1
by Julian Edwards
Create StorageContext.Send() and add a ListBlobs() that uses it. |
309 |
if err != nil { |
65.1.1
by Jeroen Vermeulen
Unify Error and ServerError as HTTPError, with different implementations based on what information is available at runtime. |
310 |
msg := fmt.Errorf("failed to read response data: %s", err) |
311 |
return resp, msg |
|
30.1.1
by Julian Edwards
Create StorageContext.Send() and add a ListBlobs() that uses it. |
312 |
}
|
313 |
err = res.Deserialize(data) |
|
314 |
if err != nil { |
|
65.1.1
by Jeroen Vermeulen
Unify Error and ServerError as HTTPError, with different implementations based on what information is available at runtime. |
315 |
msg := fmt.Errorf("Failed to deserialize data: %s", err) |
316 |
return resp, msg |
|
30.1.1
by Julian Edwards
Create StorageContext.Send() and add a ListBlobs() that uses it. |
317 |
}
|
318 |
||
36.1.1
by Gavin Panella
Return the HTTP response from Send. |
319 |
return resp, nil |
30.1.1
by Julian Edwards
Create StorageContext.Send() and add a ListBlobs() that uses it. |
320 |
}
|
321 |
||
98.3.1
by Jeroen Vermeulen
Test, and create, helpers to compute the basic storage URLs. |
322 |
// getAccountURL returns the base URL for the context's storage account.
|
323 |
// (The result ends in a slash.)
|
|
324 |
func (context *StorageContext) getAccountURL() string { |
|
98.3.7
by Jeroen Vermeulen
No longer need interpolateURL. If we ever need it again, it's in revision history. |
325 |
escapedAccount := url.QueryEscape(context.Account) |
326 |
return fmt.Sprintf("http://%s.blob.core.windows.net/", escapedAccount) |
|
98.3.1
by Jeroen Vermeulen
Test, and create, helpers to compute the basic storage URLs. |
327 |
}
|
328 |
||
329 |
// getContainerURL returns the URL for a given storage container.
|
|
330 |
// (The result does not end in a slash.)
|
|
331 |
func (context *StorageContext) getContainerURL(container string) string { |
|
332 |
return context.getAccountURL() + url.QueryEscape(container) |
|
333 |
}
|
|
334 |
||
114.2.1
by Raphael Badin
Export getFileURL. |
335 |
// GetFileURL returns the URL for a given file in the given container.
|
98.3.1
by Jeroen Vermeulen
Test, and create, helpers to compute the basic storage URLs. |
336 |
// (The result does not end in a slash.)
|
114.2.1
by Raphael Badin
Export getFileURL. |
337 |
func (context *StorageContext) GetFileURL(container, filename string) string { |
98.3.1
by Jeroen Vermeulen
Test, and create, helpers to compute the basic storage URLs. |
338 |
return context.getContainerURL(container) + "/" + url.QueryEscape(filename) |
339 |
}
|
|
340 |
||
106.1.3
by Gavin Panella
New struct ListContainersRequest, similar to ListBlobsRequest. |
341 |
type ListContainersRequest struct { |
342 |
Marker string |
|
343 |
}
|
|
344 |
||
106.1.2
by Gavin Panella
Rename ListContainers to ListAllContainers, and getListContainersBatch to ListContainers. |
345 |
// ListContainers calls the "List Containers" operation on the storage
|
98.1.3
by Jeroen Vermeulen
Support batching in ListBlobs(). Doesn't really test stripping and escaping of the marker yet; eliminate duplication first. |
346 |
// API, and returns a single batch of results.
|
93.3.7
by Jeroen Vermeulen
Satisfy test. |
347 |
// The marker argument should be empty for a new List Containers request. for
|
348 |
// subsequent calls to get additional batches of the same result, pass the
|
|
98.1.3
by Jeroen Vermeulen
Support batching in ListBlobs(). Doesn't really test stripping and escaping of the marker yet; eliminate duplication first. |
349 |
// NextMarker from the previous call's result.
|
106.1.3
by Gavin Panella
New struct ListContainersRequest, similar to ListBlobsRequest. |
350 |
func (context *StorageContext) ListContainers(request *ListContainersRequest) (*ContainerEnumerationResults, error) { |
101.1.8
by Jeroen Vermeulen
Fold base URL getter calls into addition of query params. |
351 |
uri := addURLQueryParams(context.getAccountURL(), "comp", "list") |
106.1.3
by Gavin Panella
New struct ListContainersRequest, similar to ListBlobsRequest. |
352 |
if request.Marker != "" { |
353 |
uri = addURLQueryParams(uri, "marker", request.Marker) |
|
96.2.2
by Jeroen Vermeulen
Satisfy test. |
354 |
}
|
93.3.7
by Jeroen Vermeulen
Satisfy test. |
355 |
containers := ContainerEnumerationResults{} |
101.1.4
by Jeroen Vermeulen
Merge trunk. |
356 |
_, err := context.performRequest(requestParams{ |
101.1.3
by Jeroen Vermeulen
Convert one call site to performRequest, and add error context. |
357 |
Method: "GET", |
358 |
URL: uri, |
|
359 |
APIVersion: "2012-02-12", |
|
360 |
Result: &containers, |
|
361 |
ExpectedStatus: http.StatusOK, |
|
362 |
})
|
|
93.3.8
by Jeroen Vermeulen
Review suggestion: don't try to relay unreliable results in the error case. |
363 |
if err != nil { |
122.2.1
by Raphael Badin
Propagate Azure errors, add IsNotFoundError() method. |
364 |
msg := "request for containers list failed: " |
365 |
return nil, extendError(err, msg) |
|
93.3.8
by Jeroen Vermeulen
Review suggestion: don't try to relay unreliable results in the error case. |
366 |
}
|
98.1.3
by Jeroen Vermeulen
Support batching in ListBlobs(). Doesn't really test stripping and escaping of the marker yet; eliminate duplication first. |
367 |
return &containers, nil |
17.1.6
by Julian Edwards
allenap's review suggestions |
368 |
}
|
30.1.1
by Julian Edwards
Create StorageContext.Send() and add a ListBlobs() that uses it. |
369 |
|
105.1.1
by Gavin Panella
New struct, ListBlobsRequest, for passing complex arguments. |
370 |
type ListBlobsRequest struct { |
371 |
Container string |
|
372 |
Marker string |
|
105.1.5
by Gavin Panella
Pass a prefix in the call if one is specified. |
373 |
Prefix string |
105.1.1
by Gavin Panella
New struct, ListBlobsRequest, for passing complex arguments. |
374 |
}
|
375 |
||
105.1.2
by Gavin Panella
Move ListBlobs to storage.go, rename it to ListAllBlobs, and rename getListBlobBatch to ListBlobs. |
376 |
// ListBlobs calls the "List Blobs" operation on the storage API, and returns
|
377 |
// a single batch of results.
|
|
378 |
// The request.Marker argument should be empty for a new List Blobs request.
|
|
379 |
// For subsequent calls to get additional batches of the same result, pass the
|
|
98.1.3
by Jeroen Vermeulen
Support batching in ListBlobs(). Doesn't really test stripping and escaping of the marker yet; eliminate duplication first. |
380 |
// NextMarker from the previous call's result.
|
105.1.2
by Gavin Panella
Move ListBlobs to storage.go, rename it to ListAllBlobs, and rename getListBlobBatch to ListBlobs. |
381 |
func (context *StorageContext) ListBlobs(request *ListBlobsRequest) (*BlobEnumerationResults, error) { |
101.1.8
by Jeroen Vermeulen
Fold base URL getter calls into addition of query params. |
382 |
uri := addURLQueryParams( |
105.1.1
by Gavin Panella
New struct, ListBlobsRequest, for passing complex arguments. |
383 |
context.getContainerURL(request.Container), |
98.3.5
by Jeroen Vermeulen
Review suggestions: use variadic parameters for addURLQueryParams(), scratch the singular version, and just panic when it's passed a malformed URL so there's no need to return an error. |
384 |
"restype", "container", |
385 |
"comp", "list") |
|
105.1.1
by Gavin Panella
New struct, ListBlobsRequest, for passing complex arguments. |
386 |
if request.Marker != "" { |
387 |
uri = addURLQueryParams(uri, "marker", request.Marker) |
|
98.1.3
by Jeroen Vermeulen
Support batching in ListBlobs(). Doesn't really test stripping and escaping of the marker yet; eliminate duplication first. |
388 |
}
|
105.1.5
by Gavin Panella
Pass a prefix in the call if one is specified. |
389 |
if request.Prefix != "" { |
390 |
uri = addURLQueryParams(uri, "prefix", request.Prefix) |
|
391 |
}
|
|
101.1.5
by Jeroen Vermeulen
Convert remaining callsites. |
392 |
blobs := BlobEnumerationResults{} |
393 |
_, err := context.performRequest(requestParams{ |
|
394 |
Method: "GET", |
|
395 |
URL: uri, |
|
396 |
APIVersion: "2012-02-12", |
|
397 |
Result: &blobs, |
|
398 |
ExpectedStatus: http.StatusOK, |
|
399 |
})
|
|
30.1.2
by Julian Edwards
check an error |
400 |
if err != nil { |
122.2.1
by Raphael Badin
Propagate Azure errors, add IsNotFoundError() method. |
401 |
msg := "request for blobs list failed: " |
402 |
return nil, extendError(err, msg) |
|
30.1.2
by Julian Edwards
check an error |
403 |
}
|
101.1.5
by Jeroen Vermeulen
Convert remaining callsites. |
404 |
return &blobs, err |
98.1.1
by Jeroen Vermeulen
Copy tests from earlier, oversized branch. Split out getListBlobsBatch(), but don't add batching yet. Tests: 1 panic, 1 failure. |
405 |
}
|
406 |
||
59.1.1
by Julian Edwards
Add error handling to the storage context send() |
407 |
// Send a request to the storage service to create a new container. If the
|
408 |
// request fails, error is non-nil.
|
|
37.2.1
by Gavin Panella
New CreateContainer Storage API method. |
409 |
func (context *StorageContext) CreateContainer(container string) error { |
101.1.8
by Jeroen Vermeulen
Fold base URL getter calls into addition of query params. |
410 |
uri := addURLQueryParams( |
411 |
context.getContainerURL(container), |
|
412 |
"restype", "container") |
|
101.1.5
by Jeroen Vermeulen
Convert remaining callsites. |
413 |
_, err := context.performRequest(requestParams{ |
414 |
Method: "PUT", |
|
415 |
URL: uri, |
|
416 |
APIVersion: "2012-02-12", |
|
417 |
ExpectedStatus: http.StatusCreated, |
|
418 |
})
|
|
37.2.1
by Gavin Panella
New CreateContainer Storage API method. |
419 |
if err != nil { |
122.2.1
by Raphael Badin
Propagate Azure errors, add IsNotFoundError() method. |
420 |
msg := fmt.Sprintf("failed to create container %s: ", container) |
421 |
return extendError(err, msg) |
|
37.2.1
by Gavin Panella
New CreateContainer Storage API method. |
422 |
}
|
37.2.2
by Gavin Panella
Sigh. |
423 |
return nil |
37.2.1
by Gavin Panella
New CreateContainer Storage API method. |
424 |
}
|
37.2.4
by Gavin Panella
Merge trunk. |
425 |
|
108.3.1
by Julian Edwards
Use struct as request params for PutBlob and add a blob_type |
426 |
type PutBlobRequest struct { |
108.3.6
by Julian Edwards
go fmt |
427 |
Container string // Container name in the storage account |
428 |
BlobType string // Pass "page" or "block" |
|
429 |
Filename string // Filename for the new blob |
|
115.1.2
by Julian Edwards
Take a size param for page putblobs |
430 |
Size int // Size for the new blob. Only required for page blobs. |
108.3.1
by Julian Edwards
Use struct as request params for PutBlob and add a blob_type |
431 |
}
|
432 |
||
37.1.1
by Julian Edwards
make PutBlobk |
433 |
// Send a request to create a space to upload a blob. Note that this does not
|
434 |
// do the uploading, it just makes an empty file.
|
|
108.3.1
by Julian Edwards
Use struct as request params for PutBlob and add a blob_type |
435 |
func (context *StorageContext) PutBlob(req *PutBlobRequest) error { |
108.3.4
by Julian Edwards
jtv's review comments addressed |
436 |
var blobType string |
437 |
switch req.BlobType { |
|
108.3.2
by Julian Edwards
Add x-ms-blob-type header to PutBlob request |
438 |
case "page": |
108.3.4
by Julian Edwards
jtv's review comments addressed |
439 |
blobType = "PageBlob" |
115.1.2
by Julian Edwards
Take a size param for page putblobs |
440 |
if req.Size == 0 { |
441 |
return fmt.Errorf("Must supply a size for a page blob") |
|
442 |
}
|
|
134.1.1
by Raphael Badin
Add GetDeployment method. Add Deployment.GetFQDN() method. |
443 |
if req.Size%512 != 0 { |
127.3.5
by Julian Edwards
gavin's review comments |
444 |
return fmt.Errorf("Size must be a multiple of 512 bytes") |
127.3.4
by Julian Edwards
PutBlob checks for size being multiple of 512 bytes |
445 |
}
|
108.3.2
by Julian Edwards
Add x-ms-blob-type header to PutBlob request |
446 |
case "block": |
108.3.4
by Julian Edwards
jtv's review comments addressed |
447 |
blobType = "BlockBlob" |
108.3.2
by Julian Edwards
Add x-ms-blob-type header to PutBlob request |
448 |
default: |
139.1.1
by Gavin Panella
Camel-case everything, clean-up some comments. |
449 |
panic("blockType must be 'page' or 'block'") |
108.3.1
by Julian Edwards
Use struct as request params for PutBlob and add a blob_type |
450 |
}
|
451 |
||
108.3.4
by Julian Edwards
jtv's review comments addressed |
452 |
extraHeaders := http.Header{} |
453 |
extraHeaders.Add("x-ms-blob-type", blobType) |
|
115.1.2
by Julian Edwards
Take a size param for page putblobs |
454 |
if req.BlobType == "page" { |
455 |
size := fmt.Sprintf("%d", req.Size) |
|
456 |
extraHeaders.Add("x-ms-blob-content-length", size) |
|
457 |
}
|
|
108.3.2
by Julian Edwards
Add x-ms-blob-type header to PutBlob request |
458 |
|
101.1.5
by Jeroen Vermeulen
Convert remaining callsites. |
459 |
_, err := context.performRequest(requestParams{ |
108.3.6
by Julian Edwards
go fmt |
460 |
Method: "PUT", |
114.2.1
by Raphael Badin
Export getFileURL. |
461 |
URL: context.GetFileURL(req.Container, req.Filename), |
101.1.5
by Jeroen Vermeulen
Convert remaining callsites. |
462 |
APIVersion: "2012-02-12", |
108.3.4
by Julian Edwards
jtv's review comments addressed |
463 |
ExtraHeaders: extraHeaders, |
101.1.5
by Jeroen Vermeulen
Convert remaining callsites. |
464 |
ExpectedStatus: http.StatusCreated, |
465 |
})
|
|
37.1.1
by Julian Edwards
make PutBlobk |
466 |
if err != nil { |
122.2.1
by Raphael Badin
Propagate Azure errors, add IsNotFoundError() method. |
467 |
msg := fmt.Sprintf("failed to create blob %s: ", req.Filename) |
468 |
return extendError(err, msg) |
|
37.1.2
by Julian Edwards
Extra status code check |
469 |
}
|
470 |
return nil |
|
37.1.1
by Julian Edwards
make PutBlobk |
471 |
}
|
42.1.1
by Julian Edwards
Add PutBlock |
472 |
|
113.1.2
by Julian Edwards
Add first cut of putpage |
473 |
type PutPageRequest struct { |
474 |
Container string // Container name in the storage account |
|
475 |
Filename string // The blob's file name |
|
476 |
StartRange int // Must be modulo 512, or an error is returned. |
|
477 |
EndRange int // Must be (modulo 512)-1, or an error is returned. |
|
478 |
Data io.Reader // The data to upload to the page. |
|
479 |
}
|
|
480 |
||
481 |
// Send a request to add a range of data into a page blob.
|
|
113.1.5
by Julian Edwards
Add reference to API page on web |
482 |
// See http://msdn.microsoft.com/en-us/library/windowsazure/ee691975.aspx
|
113.1.2
by Julian Edwards
Add first cut of putpage |
483 |
func (context *StorageContext) PutPage(req *PutPageRequest) error { |
127.3.5
by Julian Edwards
gavin's review comments |
484 |
validStart := (req.StartRange % 512) == 0 |
485 |
validEnd := (req.EndRange % 512) == 511 |
|
127.3.2
by Julian Edwards
Make PutPage reject invalid ranges |
486 |
if !(validStart && validEnd) { |
487 |
return fmt.Errorf( |
|
127.3.5
by Julian Edwards
gavin's review comments |
488 |
"StartRange must be a multiple of 512, EndRange must be one less than a multiple of 512") |
127.3.2
by Julian Edwards
Make PutPage reject invalid ranges |
489 |
}
|
113.1.2
by Julian Edwards
Add first cut of putpage |
490 |
uri := addURLQueryParams( |
114.2.1
by Raphael Badin
Export getFileURL. |
491 |
context.GetFileURL(req.Container, req.Filename), |
113.1.2
by Julian Edwards
Add first cut of putpage |
492 |
"comp", "page") |
493 |
||
494 |
extraHeaders := http.Header{} |
|
495 |
||
496 |
rangeData := fmt.Sprintf("bytes=%d-%d", req.StartRange, req.EndRange) |
|
497 |
extraHeaders.Add("x-ms-range", rangeData) |
|
498 |
extraHeaders.Add("x-ms-page-write", "update") |
|
499 |
||
500 |
_, err := context.performRequest(requestParams{ |
|
501 |
Method: "PUT", |
|
502 |
URL: uri, |
|
503 |
Body: req.Data, |
|
504 |
APIVersion: "2012-02-12", |
|
505 |
ExtraHeaders: extraHeaders, |
|
506 |
ExpectedStatus: http.StatusCreated, |
|
507 |
})
|
|
508 |
if err != nil { |
|
122.2.1
by Raphael Badin
Propagate Azure errors, add IsNotFoundError() method. |
509 |
msg := fmt.Sprintf("failed to put page for file %s: ", req.Filename) |
510 |
return extendError(err, msg) |
|
113.1.2
by Julian Edwards
Add first cut of putpage |
511 |
}
|
512 |
return nil |
|
513 |
}
|
|
514 |
||
51.1.2
by Julian Edwards
add GetBlockList |
515 |
// Send a request to fetch the list of blocks that have been uploaded as part
|
516 |
// of a block blob.
|
|
517 |
func (context *StorageContext) GetBlockList(container, filename string) (*GetBlockList, error) { |
|
101.1.8
by Jeroen Vermeulen
Fold base URL getter calls into addition of query params. |
518 |
uri := addURLQueryParams( |
114.2.1
by Raphael Badin
Export getFileURL. |
519 |
context.GetFileURL(container, filename), |
98.3.5
by Jeroen Vermeulen
Review suggestions: use variadic parameters for addURLQueryParams(), scratch the singular version, and just panic when it's passed a malformed URL so there's no need to return an error. |
520 |
"comp", "blocklist", |
521 |
"blocklisttype", "all") |
|
99.1.1
by Jeroen Vermeulen
Use deserialization built into send(). It's identical, including error messages. |
522 |
bl := GetBlockList{} |
101.1.5
by Jeroen Vermeulen
Convert remaining callsites. |
523 |
_, err := context.performRequest(requestParams{ |
524 |
Method: "GET", |
|
525 |
URL: uri, |
|
526 |
APIVersion: "2012-02-12", |
|
527 |
Result: &bl, |
|
528 |
ExpectedStatus: http.StatusOK, |
|
529 |
})
|
|
530 |
if err != nil { |
|
122.2.1
by Raphael Badin
Propagate Azure errors, add IsNotFoundError() method. |
531 |
msg := fmt.Sprintf("request for block list in file %s failed: ", filename) |
532 |
return nil, extendError(err, msg) |
|
51.1.2
by Julian Edwards
add GetBlockList |
533 |
}
|
99.1.1
by Jeroen Vermeulen
Use deserialization built into send(). It's identical, including error messages. |
534 |
return &bl, nil |
51.1.2
by Julian Edwards
add GetBlockList |
535 |
}
|
536 |
||
42.1.1
by Julian Edwards
Add PutBlock |
537 |
// Send a request to create a new block. The request payload contains the
|
538 |
// data block to upload.
|
|
539 |
func (context *StorageContext) PutBlock(container, filename, id string, data io.Reader) error { |
|
139.1.1
by Gavin Panella
Camel-case everything, clean-up some comments. |
540 |
base64ID := base64.StdEncoding.EncodeToString([]byte(id)) |
101.1.8
by Jeroen Vermeulen
Fold base URL getter calls into addition of query params. |
541 |
uri := addURLQueryParams( |
114.2.1
by Raphael Badin
Export getFileURL. |
542 |
context.GetFileURL(container, filename), |
98.3.5
by Jeroen Vermeulen
Review suggestions: use variadic parameters for addURLQueryParams(), scratch the singular version, and just panic when it's passed a malformed URL so there's no need to return an error. |
543 |
"comp", "block", |
139.1.1
by Gavin Panella
Camel-case everything, clean-up some comments. |
544 |
"blockid", base64ID) |
101.1.5
by Jeroen Vermeulen
Convert remaining callsites. |
545 |
_, err := context.performRequest(requestParams{ |
546 |
Method: "PUT", |
|
547 |
URL: uri, |
|
548 |
Body: data, |
|
549 |
APIVersion: "2012-02-12", |
|
550 |
ExpectedStatus: http.StatusCreated, |
|
551 |
})
|
|
552 |
if err != nil { |
|
122.2.1
by Raphael Badin
Propagate Azure errors, add IsNotFoundError() method. |
553 |
msg := fmt.Sprintf("failed to put block %s for file %s: ", id, filename) |
554 |
return extendError(err, msg) |
|
42.1.1
by Julian Edwards
Add PutBlock |
555 |
}
|
556 |
return nil |
|
557 |
}
|
|
45.1.1
by Julian Edwards
Add a PutBlockList call |
558 |
|
559 |
// Send a request to piece together blocks into a list that specifies a blob.
|
|
560 |
func (context *StorageContext) PutBlockList(container, filename string, blocklist *BlockList) error { |
|
101.1.8
by Jeroen Vermeulen
Fold base URL getter calls into addition of query params. |
561 |
uri := addURLQueryParams( |
114.2.1
by Raphael Badin
Export getFileURL. |
562 |
context.GetFileURL(container, filename), |
101.1.8
by Jeroen Vermeulen
Fold base URL getter calls into addition of query params. |
563 |
"comp", "blocklist") |
45.1.1
by Julian Edwards
Add a PutBlockList call |
564 |
data, err := blocklist.Serialize() |
565 |
if err != nil { |
|
566 |
return err |
|
567 |
}
|
|
139.1.1
by Gavin Panella
Camel-case everything, clean-up some comments. |
568 |
dataReader := bytes.NewReader(data) |
101.1.5
by Jeroen Vermeulen
Convert remaining callsites. |
569 |
|
570 |
_, err = context.performRequest(requestParams{ |
|
571 |
Method: "PUT", |
|
572 |
URL: uri, |
|
139.1.1
by Gavin Panella
Camel-case everything, clean-up some comments. |
573 |
Body: dataReader, |
101.1.5
by Jeroen Vermeulen
Convert remaining callsites. |
574 |
APIVersion: "2012-02-12", |
575 |
ExpectedStatus: http.StatusCreated, |
|
576 |
})
|
|
577 |
if err != nil { |
|
122.2.1
by Raphael Badin
Propagate Azure errors, add IsNotFoundError() method. |
578 |
msg := fmt.Sprintf("failed to put blocklist for file %s: ", filename) |
579 |
return extendError(err, msg) |
|
45.1.1
by Julian Edwards
Add a PutBlockList call |
580 |
}
|
581 |
return nil |
|
582 |
}
|
|
50.2.1
by Gavin Panella
New Storage API operation, DeleteBlob(). |
583 |
|
124.1.1
by Raphael Badin
Deleting a non-existant blob does not return an error. |
584 |
// Delete the specified blob from the given container. Deleting a non-existant
|
585 |
// blob will return without an error.
|
|
50.2.1
by Gavin Panella
New Storage API operation, DeleteBlob(). |
586 |
func (context *StorageContext) DeleteBlob(container, filename string) error { |
101.1.5
by Jeroen Vermeulen
Convert remaining callsites. |
587 |
_, err := context.performRequest(requestParams{ |
588 |
Method: "DELETE", |
|
114.2.1
by Raphael Badin
Export getFileURL. |
589 |
URL: context.GetFileURL(container, filename), |
101.1.5
by Jeroen Vermeulen
Convert remaining callsites. |
590 |
APIVersion: "2012-02-12", |
591 |
ExpectedStatus: http.StatusAccepted, |
|
592 |
})
|
|
50.2.1
by Gavin Panella
New Storage API operation, DeleteBlob(). |
593 |
if err != nil { |
124.1.1
by Raphael Badin
Deleting a non-existant blob does not return an error. |
594 |
// If the error is an Azure 404 error, return silently: the blob does
|
595 |
// not exist.
|
|
596 |
if IsNotFoundError(err) { |
|
597 |
return nil |
|
598 |
}
|
|
122.2.1
by Raphael Badin
Propagate Azure errors, add IsNotFoundError() method. |
599 |
msg := fmt.Sprintf("failed to delete blob %s: ", filename) |
600 |
return extendError(err, msg) |
|
50.2.1
by Gavin Panella
New Storage API operation, DeleteBlob(). |
601 |
}
|
602 |
return nil |
|
603 |
}
|
|
54.1.1
by Gavin Panella
New Storage API operation, GetBlob(). |
604 |
|
605 |
// Get the specified blob from the given container.
|
|
606 |
func (context *StorageContext) GetBlob(container, filename string) (io.ReadCloser, error) { |
|
101.1.5
by Jeroen Vermeulen
Convert remaining callsites. |
607 |
response, err := context.performRequest(requestParams{ |
608 |
Method: "GET", |
|
114.2.1
by Raphael Badin
Export getFileURL. |
609 |
URL: context.GetFileURL(container, filename), |
101.1.5
by Jeroen Vermeulen
Convert remaining callsites. |
610 |
APIVersion: "2012-02-12", |
611 |
ExpectedStatus: http.StatusOK, |
|
612 |
})
|
|
613 |
if err != nil { |
|
122.2.1
by Raphael Badin
Propagate Azure errors, add IsNotFoundError() method. |
614 |
msg := fmt.Sprintf("failed to get blob %s: ", filename) |
615 |
return nil, extendError(err, msg) |
|
101.1.2
by Jeroen Vermeulen
Smooth out some redundant error-checking. Create performRequest method, with its Parameter Object struct. Use HTTPStatus rather than int for HTTP statuses. |
616 |
}
|
617 |
return response.Body, nil |
|
54.1.1
by Gavin Panella
New Storage API operation, GetBlob(). |
618 |
}
|
127.4.2
by Julian Edwards
first stab at SetContainerACL |
619 |
|
620 |
type SetContainerACLRequest struct { |
|
621 |
Container string // Container name in the storage account |
|
622 |
Access string // "container", "blob", or "private" |
|
623 |
}
|
|
624 |
||
625 |
// SetContainerACL sets the specified container's access rights.
|
|
626 |
// See http://msdn.microsoft.com/en-us/library/windowsazure/dd179391.aspx
|
|
627 |
func (context *StorageContext) SetContainerACL(req *SetContainerACLRequest) error { |
|
628 |
uri := addURLQueryParams( |
|
629 |
context.getContainerURL(req.Container), |
|
630 |
"restype", "container", |
|
631 |
"comp", "acl") |
|
632 |
||
633 |
extraHeaders := http.Header{} |
|
634 |
switch req.Access { |
|
635 |
case "container", "blob": |
|
636 |
extraHeaders.Add("x-ms-blob-public-access", req.Access) |
|
637 |
case "private": |
|
638 |
// Don't add a header, Azure resets to private if it's omitted.
|
|
639 |
default: |
|
640 |
panic("Access must be one of 'container', 'blob' or 'private'") |
|
641 |
}
|
|
642 |
||
643 |
_, err := context.performRequest(requestParams{ |
|
127.4.8
by Julian Edwards
format |
644 |
Method: "PUT", |
645 |
URL: uri, |
|
646 |
APIVersion: "2009-09-19", |
|
647 |
ExtraHeaders: extraHeaders, |
|
127.4.2
by Julian Edwards
first stab at SetContainerACL |
648 |
ExpectedStatus: http.StatusOK, |
649 |
})
|
|
650 |
||
651 |
if err != nil { |
|
652 |
msg := fmt.Sprintf("failed to set ACL for container %s: ", req.Container) |
|
653 |
return extendError(err, msg) |
|
654 |
}
|
|
655 |
return nil |
|
656 |
}
|