~nskaggs/+junk/xenial-test

« back to all changes in this revision

Viewing changes to src/github.com/Azure/azure-sdk-for-go/storage/client.go

  • Committer: Nicholas Skaggs
  • Date: 2016-10-24 20:56:05 UTC
  • Revision ID: nicholas.skaggs@canonical.com-20161024205605-z8lta0uvuhtxwzwl
Initi with beta15

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
// Package storage provides clients for Microsoft Azure Storage Services.
 
2
package storage
 
3
 
 
4
import (
 
5
        "bytes"
 
6
        "encoding/base64"
 
7
        "encoding/xml"
 
8
        "errors"
 
9
        "fmt"
 
10
        "io"
 
11
        "io/ioutil"
 
12
        "net/http"
 
13
        "net/url"
 
14
        "regexp"
 
15
        "sort"
 
16
        "strconv"
 
17
        "strings"
 
18
)
 
19
 
 
20
const (
 
21
        // DefaultBaseURL is the domain name used for storage requests when a
 
22
        // default client is created.
 
23
        DefaultBaseURL = "core.windows.net"
 
24
 
 
25
        // DefaultAPIVersion is the  Azure Storage API version string used when a
 
26
        // basic client is created.
 
27
        DefaultAPIVersion = "2014-02-14"
 
28
 
 
29
        defaultUseHTTPS = true
 
30
 
 
31
        blobServiceName  = "blob"
 
32
        tableServiceName = "table"
 
33
        queueServiceName = "queue"
 
34
        fileServiceName  = "file"
 
35
)
 
36
 
 
37
// Client is the object that needs to be constructed to perform
 
38
// operations on the storage account.
 
39
type Client struct {
 
40
        accountName string
 
41
        accountKey  []byte
 
42
        useHTTPS    bool
 
43
        baseURL     string
 
44
        apiVersion  string
 
45
}
 
46
 
 
47
type storageResponse struct {
 
48
        statusCode int
 
49
        headers    http.Header
 
50
        body       io.ReadCloser
 
51
}
 
52
 
 
53
// AzureStorageServiceError contains fields of the error response from
 
54
// Azure Storage Service REST API. See https://msdn.microsoft.com/en-us/library/azure/dd179382.aspx
 
55
// Some fields might be specific to certain calls.
 
56
type AzureStorageServiceError struct {
 
57
        Code                      string `xml:"Code"`
 
58
        Message                   string `xml:"Message"`
 
59
        AuthenticationErrorDetail string `xml:"AuthenticationErrorDetail"`
 
60
        QueryParameterName        string `xml:"QueryParameterName"`
 
61
        QueryParameterValue       string `xml:"QueryParameterValue"`
 
62
        Reason                    string `xml:"Reason"`
 
63
        StatusCode                int
 
64
        RequestID                 string
 
65
}
 
66
 
 
67
// UnexpectedStatusCodeError is returned when a storage service responds with neither an error
 
68
// nor with an HTTP status code indicating success.
 
69
type UnexpectedStatusCodeError struct {
 
70
        allowed []int
 
71
        got     int
 
72
}
 
73
 
 
74
func (e UnexpectedStatusCodeError) Error() string {
 
75
        s := func(i int) string { return fmt.Sprintf("%d %s", i, http.StatusText(i)) }
 
76
 
 
77
        got := s(e.got)
 
78
        expected := []string{}
 
79
        for _, v := range e.allowed {
 
80
                expected = append(expected, s(v))
 
81
        }
 
82
        return fmt.Sprintf("storage: status code from service response is %s; was expecting %s", got, strings.Join(expected, " or "))
 
83
}
 
84
 
 
85
// Got is the actual status code returned by Azure.
 
86
func (e UnexpectedStatusCodeError) Got() int {
 
87
        return e.got
 
88
}
 
89
 
 
90
// NewBasicClient constructs a Client with given storage service name and
 
91
// key.
 
92
func NewBasicClient(accountName, accountKey string) (Client, error) {
 
93
        return NewClient(accountName, accountKey, DefaultBaseURL, DefaultAPIVersion, defaultUseHTTPS)
 
94
}
 
95
 
 
96
// NewClient constructs a Client. This should be used if the caller wants
 
97
// to specify whether to use HTTPS, a specific REST API version or a custom
 
98
// storage endpoint than Azure Public Cloud.
 
99
func NewClient(accountName, accountKey, blobServiceBaseURL, apiVersion string, useHTTPS bool) (Client, error) {
 
100
        var c Client
 
101
        if accountName == "" {
 
102
                return c, fmt.Errorf("azure: account name required")
 
103
        } else if accountKey == "" {
 
104
                return c, fmt.Errorf("azure: account key required")
 
105
        } else if blobServiceBaseURL == "" {
 
106
                return c, fmt.Errorf("azure: base storage service url required")
 
107
        }
 
108
 
 
109
        key, err := base64.StdEncoding.DecodeString(accountKey)
 
110
        if err != nil {
 
111
                return c, err
 
112
        }
 
113
 
 
114
        return Client{
 
115
                accountName: accountName,
 
116
                accountKey:  key,
 
117
                useHTTPS:    useHTTPS,
 
118
                baseURL:     blobServiceBaseURL,
 
119
                apiVersion:  apiVersion,
 
120
        }, nil
 
121
}
 
122
 
 
123
func (c Client) getBaseURL(service string) string {
 
124
        scheme := "http"
 
125
        if c.useHTTPS {
 
126
                scheme = "https"
 
127
        }
 
128
 
 
129
        host := fmt.Sprintf("%s.%s.%s", c.accountName, service, c.baseURL)
 
130
 
 
131
        u := &url.URL{
 
132
                Scheme: scheme,
 
133
                Host:   host}
 
134
        return u.String()
 
135
}
 
136
 
 
137
func (c Client) getEndpoint(service, path string, params url.Values) string {
 
138
        u, err := url.Parse(c.getBaseURL(service))
 
139
        if err != nil {
 
140
                // really should not be happening
 
141
                panic(err)
 
142
        }
 
143
 
 
144
        if path == "" {
 
145
                path = "/" // API doesn't accept path segments not starting with '/'
 
146
        }
 
147
 
 
148
        u.Path = path
 
149
        u.RawQuery = params.Encode()
 
150
        return u.String()
 
151
}
 
152
 
 
153
// GetBlobService returns a BlobStorageClient which can operate on the blob
 
154
// service of the storage account.
 
155
func (c Client) GetBlobService() BlobStorageClient {
 
156
        return BlobStorageClient{c}
 
157
}
 
158
 
 
159
// GetQueueService returns a QueueServiceClient which can operate on the queue
 
160
// service of the storage account.
 
161
func (c Client) GetQueueService() QueueServiceClient {
 
162
        return QueueServiceClient{c}
 
163
}
 
164
 
 
165
// GetFileService returns a FileServiceClient which can operate on the file
 
166
// service of the storage account.
 
167
func (c Client) GetFileService() FileServiceClient {
 
168
        return FileServiceClient{c}
 
169
}
 
170
 
 
171
func (c Client) createAuthorizationHeader(canonicalizedString string) string {
 
172
        signature := c.computeHmac256(canonicalizedString)
 
173
        return fmt.Sprintf("%s %s:%s", "SharedKey", c.accountName, signature)
 
174
}
 
175
 
 
176
func (c Client) getAuthorizationHeader(verb, url string, headers map[string]string) (string, error) {
 
177
        canonicalizedResource, err := c.buildCanonicalizedResource(url)
 
178
        if err != nil {
 
179
                return "", err
 
180
        }
 
181
 
 
182
        canonicalizedString := c.buildCanonicalizedString(verb, headers, canonicalizedResource)
 
183
        return c.createAuthorizationHeader(canonicalizedString), nil
 
184
}
 
185
 
 
186
func (c Client) getStandardHeaders() map[string]string {
 
187
        return map[string]string{
 
188
                "x-ms-version": c.apiVersion,
 
189
                "x-ms-date":    currentTimeRfc1123Formatted(),
 
190
        }
 
191
}
 
192
 
 
193
func (c Client) buildCanonicalizedHeader(headers map[string]string) string {
 
194
        cm := make(map[string]string)
 
195
 
 
196
        for k, v := range headers {
 
197
                headerName := strings.TrimSpace(strings.ToLower(k))
 
198
                match, _ := regexp.MatchString("x-ms-", headerName)
 
199
                if match {
 
200
                        cm[headerName] = v
 
201
                }
 
202
        }
 
203
 
 
204
        if len(cm) == 0 {
 
205
                return ""
 
206
        }
 
207
 
 
208
        keys := make([]string, 0, len(cm))
 
209
        for key := range cm {
 
210
                keys = append(keys, key)
 
211
        }
 
212
 
 
213
        sort.Strings(keys)
 
214
 
 
215
        ch := ""
 
216
 
 
217
        for i, key := range keys {
 
218
                if i == len(keys)-1 {
 
219
                        ch += fmt.Sprintf("%s:%s", key, cm[key])
 
220
                } else {
 
221
                        ch += fmt.Sprintf("%s:%s\n", key, cm[key])
 
222
                }
 
223
        }
 
224
        return ch
 
225
}
 
226
 
 
227
func (c Client) buildCanonicalizedResource(uri string) (string, error) {
 
228
        errMsg := "buildCanonicalizedResource error: %s"
 
229
        u, err := url.Parse(uri)
 
230
        if err != nil {
 
231
                return "", fmt.Errorf(errMsg, err.Error())
 
232
        }
 
233
 
 
234
        cr := "/" + c.accountName
 
235
        if len(u.Path) > 0 {
 
236
                cr += u.Path
 
237
        }
 
238
 
 
239
        params, err := url.ParseQuery(u.RawQuery)
 
240
        if err != nil {
 
241
                return "", fmt.Errorf(errMsg, err.Error())
 
242
        }
 
243
 
 
244
        if len(params) > 0 {
 
245
                cr += "\n"
 
246
                keys := make([]string, 0, len(params))
 
247
                for key := range params {
 
248
                        keys = append(keys, key)
 
249
                }
 
250
 
 
251
                sort.Strings(keys)
 
252
 
 
253
                for i, key := range keys {
 
254
                        if len(params[key]) > 1 {
 
255
                                sort.Strings(params[key])
 
256
                        }
 
257
 
 
258
                        if i == len(keys)-1 {
 
259
                                cr += fmt.Sprintf("%s:%s", key, strings.Join(params[key], ","))
 
260
                        } else {
 
261
                                cr += fmt.Sprintf("%s:%s\n", key, strings.Join(params[key], ","))
 
262
                        }
 
263
                }
 
264
        }
 
265
        return cr, nil
 
266
}
 
267
 
 
268
func (c Client) buildCanonicalizedString(verb string, headers map[string]string, canonicalizedResource string) string {
 
269
        canonicalizedString := fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s",
 
270
                verb,
 
271
                headers["Content-Encoding"],
 
272
                headers["Content-Language"],
 
273
                headers["Content-Length"],
 
274
                headers["Content-MD5"],
 
275
                headers["Content-Type"],
 
276
                headers["Date"],
 
277
                headers["If-Modified-Since"],
 
278
                headers["If-Match"],
 
279
                headers["If-None-Match"],
 
280
                headers["If-Unmodified-Since"],
 
281
                headers["Range"],
 
282
                c.buildCanonicalizedHeader(headers),
 
283
                canonicalizedResource)
 
284
 
 
285
        return canonicalizedString
 
286
}
 
287
 
 
288
func (c Client) exec(verb, url string, headers map[string]string, body io.Reader) (*storageResponse, error) {
 
289
        authHeader, err := c.getAuthorizationHeader(verb, url, headers)
 
290
        if err != nil {
 
291
                return nil, err
 
292
        }
 
293
        headers["Authorization"] = authHeader
 
294
 
 
295
        if err != nil {
 
296
                return nil, err
 
297
        }
 
298
 
 
299
        req, err := http.NewRequest(verb, url, body)
 
300
        if err != nil {
 
301
                return nil, errors.New("azure/storage: error creating request: " + err.Error())
 
302
        }
 
303
        if clstr, ok := headers["Content-Length"]; ok {
 
304
                // content length header is being signed, but completely ignored by golang.
 
305
                // instead we have to use the ContentLength property on the request struct
 
306
                // (see https://golang.org/src/net/http/request.go?s=18140:18370#L536 and
 
307
                // https://golang.org/src/net/http/transfer.go?s=1739:2467#L49)
 
308
                req.ContentLength, err = strconv.ParseInt(clstr, 10, 64)
 
309
                if err != nil {
 
310
                        return nil, err
 
311
                }
 
312
        }
 
313
        for k, v := range headers {
 
314
                req.Header.Add(k, v)
 
315
        }
 
316
        httpClient := http.Client{}
 
317
        resp, err := httpClient.Do(req)
 
318
        if err != nil {
 
319
                return nil, err
 
320
        }
 
321
 
 
322
        statusCode := resp.StatusCode
 
323
        if statusCode >= 400 && statusCode <= 505 {
 
324
                var respBody []byte
 
325
                respBody, err = readResponseBody(resp)
 
326
                if err != nil {
 
327
                        return nil, err
 
328
                }
 
329
 
 
330
                if len(respBody) == 0 {
 
331
                        // no error in response body
 
332
                        err = fmt.Errorf("storage: service returned without a response body (%s)", resp.Status)
 
333
                } else {
 
334
                        // response contains storage service error object, unmarshal
 
335
                        storageErr, errIn := serviceErrFromXML(respBody, resp.StatusCode, resp.Header.Get("x-ms-request-id"))
 
336
                        if err != nil { // error unmarshaling the error response
 
337
                                err = errIn
 
338
                        }
 
339
                        err = storageErr
 
340
                }
 
341
                return &storageResponse{
 
342
                        statusCode: resp.StatusCode,
 
343
                        headers:    resp.Header,
 
344
                        body:       ioutil.NopCloser(bytes.NewReader(respBody)), /* restore the body */
 
345
                }, err
 
346
        }
 
347
 
 
348
        return &storageResponse{
 
349
                statusCode: resp.StatusCode,
 
350
                headers:    resp.Header,
 
351
                body:       resp.Body}, nil
 
352
}
 
353
 
 
354
func readResponseBody(resp *http.Response) ([]byte, error) {
 
355
        defer resp.Body.Close()
 
356
        out, err := ioutil.ReadAll(resp.Body)
 
357
        if err == io.EOF {
 
358
                err = nil
 
359
        }
 
360
        return out, err
 
361
}
 
362
 
 
363
func serviceErrFromXML(body []byte, statusCode int, requestID string) (AzureStorageServiceError, error) {
 
364
        var storageErr AzureStorageServiceError
 
365
        if err := xml.Unmarshal(body, &storageErr); err != nil {
 
366
                return storageErr, err
 
367
        }
 
368
        storageErr.StatusCode = statusCode
 
369
        storageErr.RequestID = requestID
 
370
        return storageErr, nil
 
371
}
 
372
 
 
373
func (e AzureStorageServiceError) Error() string {
 
374
        return fmt.Sprintf("storage: service returned error: StatusCode=%d, ErrorCode=%s, ErrorMessage=%s, RequestId=%s", e.StatusCode, e.Code, e.Message, e.RequestID)
 
375
}
 
376
 
 
377
// checkRespCode returns UnexpectedStatusError if the given response code is not
 
378
// one of the allowed status codes; otherwise nil.
 
379
func checkRespCode(respCode int, allowed []int) error {
 
380
        for _, v := range allowed {
 
381
                if respCode == v {
 
382
                        return nil
 
383
                }
 
384
        }
 
385
        return UnexpectedStatusCodeError{allowed, respCode}
 
386
}