1 package storage
2
3
4
5
6 import (
7 "encoding/xml"
8 "fmt"
9 "io"
10 "net/http"
11 "net/url"
12 "strconv"
13 "strings"
14 "time"
15 )
16
17
18 type Container struct {
19 bsc *BlobStorageClient
20 Name string `xml:"Name"`
21 Properties ContainerProperties `xml:"Properties"`
22 Metadata map[string]string
23 sasuri url.URL
24 }
25
26
27 func (c *Container) Client() *Client {
28 return &c.bsc.client
29 }
30
31 func (c *Container) buildPath() string {
32 return fmt.Sprintf("/%s", c.Name)
33 }
34
35
36
37
38 func (c *Container) GetURL() string {
39 container := c.Name
40 if container == "" {
41 container = "$root"
42 }
43 return c.bsc.client.getEndpoint(blobServiceName, pathForResource(container, ""), nil)
44 }
45
46
47
48
49 type ContainerSASOptions struct {
50 ContainerSASPermissions
51 OverrideHeaders
52 SASOptions
53 }
54
55
56
57 type ContainerSASPermissions struct {
58 BlobServiceSASPermissions
59 List bool
60 }
61
62
63
64
65
66 func (c *Container) GetSASURI(options ContainerSASOptions) (string, error) {
67 uri := c.GetURL()
68 signedResource := "c"
69 canonicalizedResource, err := c.bsc.client.buildCanonicalizedResource(uri, c.bsc.auth, true)
70 if err != nil {
71 return "", err
72 }
73
74
75 permissions := options.BlobServiceSASPermissions.buildString()
76 if options.List {
77 permissions += "l"
78 }
79
80 return c.bsc.client.blobAndFileSASURI(options.SASOptions, uri, permissions, canonicalizedResource, signedResource, options.OverrideHeaders)
81 }
82
83
84
85 type ContainerProperties struct {
86 LastModified string `xml:"Last-Modified"`
87 Etag string `xml:"Etag"`
88 LeaseStatus string `xml:"LeaseStatus"`
89 LeaseState string `xml:"LeaseState"`
90 LeaseDuration string `xml:"LeaseDuration"`
91 PublicAccess ContainerAccessType `xml:"PublicAccess"`
92 }
93
94
95
96
97
98 type ContainerListResponse struct {
99 XMLName xml.Name `xml:"EnumerationResults"`
100 Xmlns string `xml:"xmlns,attr"`
101 Prefix string `xml:"Prefix"`
102 Marker string `xml:"Marker"`
103 NextMarker string `xml:"NextMarker"`
104 MaxResults int64 `xml:"MaxResults"`
105 Containers []Container `xml:"Containers>Container"`
106 }
107
108
109
110
111 type BlobListResponse struct {
112 XMLName xml.Name `xml:"EnumerationResults"`
113 Xmlns string `xml:"xmlns,attr"`
114 Prefix string `xml:"Prefix"`
115 Marker string `xml:"Marker"`
116 NextMarker string `xml:"NextMarker"`
117 MaxResults int64 `xml:"MaxResults"`
118 Blobs []Blob `xml:"Blobs>Blob"`
119
120
121
122
123
124 BlobPrefixes []string `xml:"Blobs>BlobPrefix>Name"`
125
126
127
128 Delimiter string `xml:"Delimiter"`
129 }
130
131
132 type IncludeBlobDataset struct {
133 Snapshots bool
134 Metadata bool
135 UncommittedBlobs bool
136 Copy bool
137 }
138
139
140
141
142
143 type ListBlobsParameters struct {
144 Prefix string
145 Delimiter string
146 Marker string
147 Include *IncludeBlobDataset
148 MaxResults uint
149 Timeout uint
150 RequestID string
151 }
152
153 func (p ListBlobsParameters) getParameters() url.Values {
154 out := url.Values{}
155
156 if p.Prefix != "" {
157 out.Set("prefix", p.Prefix)
158 }
159 if p.Delimiter != "" {
160 out.Set("delimiter", p.Delimiter)
161 }
162 if p.Marker != "" {
163 out.Set("marker", p.Marker)
164 }
165 if p.Include != nil {
166 include := []string{}
167 include = addString(include, p.Include.Snapshots, "snapshots")
168 include = addString(include, p.Include.Metadata, "metadata")
169 include = addString(include, p.Include.UncommittedBlobs, "uncommittedblobs")
170 include = addString(include, p.Include.Copy, "copy")
171 fullInclude := strings.Join(include, ",")
172 out.Set("include", fullInclude)
173 }
174 if p.MaxResults != 0 {
175 out.Set("maxresults", strconv.FormatUint(uint64(p.MaxResults), 10))
176 }
177 if p.Timeout != 0 {
178 out.Set("timeout", strconv.FormatUint(uint64(p.Timeout), 10))
179 }
180
181 return out
182 }
183
184 func addString(datasets []string, include bool, text string) []string {
185 if include {
186 datasets = append(datasets, text)
187 }
188 return datasets
189 }
190
191
192
193
194
195
196 type ContainerAccessType string
197
198
199 const (
200 ContainerAccessTypePrivate ContainerAccessType = ""
201 ContainerAccessTypeBlob ContainerAccessType = "blob"
202 ContainerAccessTypeContainer ContainerAccessType = "container"
203 )
204
205
206 type ContainerAccessPolicy struct {
207 ID string
208 StartTime time.Time
209 ExpiryTime time.Time
210 CanRead bool
211 CanWrite bool
212 CanDelete bool
213 }
214
215
216 type ContainerPermissions struct {
217 AccessType ContainerAccessType
218 AccessPolicies []ContainerAccessPolicy
219 }
220
221
222 const (
223 ContainerAccessHeader string = "x-ms-blob-public-access"
224 )
225
226
227 func (c *Container) GetBlobReference(name string) *Blob {
228 return &Blob{
229 Container: c,
230 Name: name,
231 }
232 }
233
234
235 type CreateContainerOptions struct {
236 Timeout uint
237 Access ContainerAccessType `header:"x-ms-blob-public-access"`
238 RequestID string `header:"x-ms-client-request-id"`
239 }
240
241
242
243
244
245 func (c *Container) Create(options *CreateContainerOptions) error {
246 resp, err := c.create(options)
247 if err != nil {
248 return err
249 }
250 defer drainRespBody(resp)
251 return checkRespCode(resp, []int{http.StatusCreated})
252 }
253
254
255
256 func (c *Container) CreateIfNotExists(options *CreateContainerOptions) (bool, error) {
257 resp, err := c.create(options)
258 if resp != nil {
259 defer drainRespBody(resp)
260 if resp.StatusCode == http.StatusCreated || resp.StatusCode == http.StatusConflict {
261 return resp.StatusCode == http.StatusCreated, nil
262 }
263 }
264 return false, err
265 }
266
267 func (c *Container) create(options *CreateContainerOptions) (*http.Response, error) {
268 query := url.Values{"restype": {"container"}}
269 headers := c.bsc.client.getStandardHeaders()
270 headers = c.bsc.client.addMetadataToHeaders(headers, c.Metadata)
271
272 if options != nil {
273 query = addTimeout(query, options.Timeout)
274 headers = mergeHeaders(headers, headersFromStruct(*options))
275 }
276 uri := c.bsc.client.getEndpoint(blobServiceName, c.buildPath(), query)
277
278 return c.bsc.client.exec(http.MethodPut, uri, headers, nil, c.bsc.auth)
279 }
280
281
282
283 func (c *Container) Exists() (bool, error) {
284 q := url.Values{"restype": {"container"}}
285 var uri string
286 if c.bsc.client.isServiceSASClient() {
287 q = mergeParams(q, c.sasuri.Query())
288 newURI := c.sasuri
289 newURI.RawQuery = q.Encode()
290 uri = newURI.String()
291
292 } else {
293 uri = c.bsc.client.getEndpoint(blobServiceName, c.buildPath(), q)
294 }
295 headers := c.bsc.client.getStandardHeaders()
296
297 resp, err := c.bsc.client.exec(http.MethodHead, uri, headers, nil, c.bsc.auth)
298 if resp != nil {
299 defer drainRespBody(resp)
300 if resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusNotFound {
301 return resp.StatusCode == http.StatusOK, nil
302 }
303 }
304 return false, err
305 }
306
307
308 type SetContainerPermissionOptions struct {
309 Timeout uint
310 LeaseID string `header:"x-ms-lease-id"`
311 IfModifiedSince *time.Time `header:"If-Modified-Since"`
312 IfUnmodifiedSince *time.Time `header:"If-Unmodified-Since"`
313 RequestID string `header:"x-ms-client-request-id"`
314 }
315
316
317
318 func (c *Container) SetPermissions(permissions ContainerPermissions, options *SetContainerPermissionOptions) error {
319 body, length, err := generateContainerACLpayload(permissions.AccessPolicies)
320 if err != nil {
321 return err
322 }
323 params := url.Values{
324 "restype": {"container"},
325 "comp": {"acl"},
326 }
327 headers := c.bsc.client.getStandardHeaders()
328 headers = addToHeaders(headers, ContainerAccessHeader, string(permissions.AccessType))
329 headers["Content-Length"] = strconv.Itoa(length)
330
331 if options != nil {
332 params = addTimeout(params, options.Timeout)
333 headers = mergeHeaders(headers, headersFromStruct(*options))
334 }
335 uri := c.bsc.client.getEndpoint(blobServiceName, c.buildPath(), params)
336
337 resp, err := c.bsc.client.exec(http.MethodPut, uri, headers, body, c.bsc.auth)
338 if err != nil {
339 return err
340 }
341 defer drainRespBody(resp)
342 return checkRespCode(resp, []int{http.StatusOK})
343 }
344
345
346 type GetContainerPermissionOptions struct {
347 Timeout uint
348 LeaseID string `header:"x-ms-lease-id"`
349 RequestID string `header:"x-ms-client-request-id"`
350 }
351
352
353
354
355 func (c *Container) GetPermissions(options *GetContainerPermissionOptions) (*ContainerPermissions, error) {
356 params := url.Values{
357 "restype": {"container"},
358 "comp": {"acl"},
359 }
360 headers := c.bsc.client.getStandardHeaders()
361
362 if options != nil {
363 params = addTimeout(params, options.Timeout)
364 headers = mergeHeaders(headers, headersFromStruct(*options))
365 }
366 uri := c.bsc.client.getEndpoint(blobServiceName, c.buildPath(), params)
367
368 resp, err := c.bsc.client.exec(http.MethodGet, uri, headers, nil, c.bsc.auth)
369 if err != nil {
370 return nil, err
371 }
372 defer resp.Body.Close()
373
374 var ap AccessPolicy
375 err = xmlUnmarshal(resp.Body, &ap.SignedIdentifiersList)
376 if err != nil {
377 return nil, err
378 }
379 return buildAccessPolicy(ap, &resp.Header), nil
380 }
381
382 func buildAccessPolicy(ap AccessPolicy, headers *http.Header) *ContainerPermissions {
383
384 containerAccess := headers.Get(http.CanonicalHeaderKey(ContainerAccessHeader))
385 permissions := ContainerPermissions{
386 AccessType: ContainerAccessType(containerAccess),
387 AccessPolicies: []ContainerAccessPolicy{},
388 }
389
390 for _, policy := range ap.SignedIdentifiersList.SignedIdentifiers {
391 capd := ContainerAccessPolicy{
392 ID: policy.ID,
393 StartTime: policy.AccessPolicy.StartTime,
394 ExpiryTime: policy.AccessPolicy.ExpiryTime,
395 }
396 capd.CanRead = updatePermissions(policy.AccessPolicy.Permission, "r")
397 capd.CanWrite = updatePermissions(policy.AccessPolicy.Permission, "w")
398 capd.CanDelete = updatePermissions(policy.AccessPolicy.Permission, "d")
399
400 permissions.AccessPolicies = append(permissions.AccessPolicies, capd)
401 }
402 return &permissions
403 }
404
405
406 type DeleteContainerOptions struct {
407 Timeout uint
408 LeaseID string `header:"x-ms-lease-id"`
409 IfModifiedSince *time.Time `header:"If-Modified-Since"`
410 IfUnmodifiedSince *time.Time `header:"If-Unmodified-Since"`
411 RequestID string `header:"x-ms-client-request-id"`
412 }
413
414
415
416
417
418 func (c *Container) Delete(options *DeleteContainerOptions) error {
419 resp, err := c.delete(options)
420 if err != nil {
421 return err
422 }
423 defer drainRespBody(resp)
424 return checkRespCode(resp, []int{http.StatusAccepted})
425 }
426
427
428
429
430
431
432
433 func (c *Container) DeleteIfExists(options *DeleteContainerOptions) (bool, error) {
434 resp, err := c.delete(options)
435 if resp != nil {
436 defer drainRespBody(resp)
437 if resp.StatusCode == http.StatusAccepted || resp.StatusCode == http.StatusNotFound {
438 return resp.StatusCode == http.StatusAccepted, nil
439 }
440 }
441 return false, err
442 }
443
444 func (c *Container) delete(options *DeleteContainerOptions) (*http.Response, error) {
445 query := url.Values{"restype": {"container"}}
446 headers := c.bsc.client.getStandardHeaders()
447
448 if options != nil {
449 query = addTimeout(query, options.Timeout)
450 headers = mergeHeaders(headers, headersFromStruct(*options))
451 }
452 uri := c.bsc.client.getEndpoint(blobServiceName, c.buildPath(), query)
453
454 return c.bsc.client.exec(http.MethodDelete, uri, headers, nil, c.bsc.auth)
455 }
456
457
458
459
460
461 func (c *Container) ListBlobs(params ListBlobsParameters) (BlobListResponse, error) {
462 q := mergeParams(params.getParameters(), url.Values{
463 "restype": {"container"},
464 "comp": {"list"},
465 })
466 var uri string
467 if c.bsc.client.isServiceSASClient() {
468 q = mergeParams(q, c.sasuri.Query())
469 newURI := c.sasuri
470 newURI.RawQuery = q.Encode()
471 uri = newURI.String()
472 } else {
473 uri = c.bsc.client.getEndpoint(blobServiceName, c.buildPath(), q)
474 }
475
476 headers := c.bsc.client.getStandardHeaders()
477 headers = addToHeaders(headers, "x-ms-client-request-id", params.RequestID)
478
479 var out BlobListResponse
480 resp, err := c.bsc.client.exec(http.MethodGet, uri, headers, nil, c.bsc.auth)
481 if err != nil {
482 return out, err
483 }
484 defer resp.Body.Close()
485
486 err = xmlUnmarshal(resp.Body, &out)
487 for i := range out.Blobs {
488 out.Blobs[i].Container = c
489 }
490 return out, err
491 }
492
493
494 type ContainerMetadataOptions struct {
495 Timeout uint
496 LeaseID string `header:"x-ms-lease-id"`
497 RequestID string `header:"x-ms-client-request-id"`
498 }
499
500
501
502
503
504
505
506
507
508 func (c *Container) SetMetadata(options *ContainerMetadataOptions) error {
509 params := url.Values{
510 "comp": {"metadata"},
511 "restype": {"container"},
512 }
513 headers := c.bsc.client.getStandardHeaders()
514 headers = c.bsc.client.addMetadataToHeaders(headers, c.Metadata)
515
516 if options != nil {
517 params = addTimeout(params, options.Timeout)
518 headers = mergeHeaders(headers, headersFromStruct(*options))
519 }
520
521 uri := c.bsc.client.getEndpoint(blobServiceName, c.buildPath(), params)
522
523 resp, err := c.bsc.client.exec(http.MethodPut, uri, headers, nil, c.bsc.auth)
524 if err != nil {
525 return err
526 }
527 defer drainRespBody(resp)
528 return checkRespCode(resp, []int{http.StatusOK})
529 }
530
531
532
533
534
535
536
537 func (c *Container) GetMetadata(options *ContainerMetadataOptions) error {
538 params := url.Values{
539 "comp": {"metadata"},
540 "restype": {"container"},
541 }
542 headers := c.bsc.client.getStandardHeaders()
543
544 if options != nil {
545 params = addTimeout(params, options.Timeout)
546 headers = mergeHeaders(headers, headersFromStruct(*options))
547 }
548
549 uri := c.bsc.client.getEndpoint(blobServiceName, c.buildPath(), params)
550
551 resp, err := c.bsc.client.exec(http.MethodGet, uri, headers, nil, c.bsc.auth)
552 if err != nil {
553 return err
554 }
555 defer drainRespBody(resp)
556 if err := checkRespCode(resp, []int{http.StatusOK}); err != nil {
557 return err
558 }
559
560 c.writeMetadata(resp.Header)
561 return nil
562 }
563
564 func (c *Container) writeMetadata(h http.Header) {
565 c.Metadata = writeMetadata(h)
566 }
567
568 func generateContainerACLpayload(policies []ContainerAccessPolicy) (io.Reader, int, error) {
569 sil := SignedIdentifiers{
570 SignedIdentifiers: []SignedIdentifier{},
571 }
572 for _, capd := range policies {
573 permission := capd.generateContainerPermissions()
574 signedIdentifier := convertAccessPolicyToXMLStructs(capd.ID, capd.StartTime, capd.ExpiryTime, permission)
575 sil.SignedIdentifiers = append(sil.SignedIdentifiers, signedIdentifier)
576 }
577 return xmlMarshal(sil)
578 }
579
580 func (capd *ContainerAccessPolicy) generateContainerPermissions() (permissions string) {
581
582
583 permissions = ""
584
585 if capd.CanRead {
586 permissions += "r"
587 }
588
589 if capd.CanWrite {
590 permissions += "w"
591 }
592
593 if capd.CanDelete {
594 permissions += "d"
595 }
596
597 return permissions
598 }
599
600
601
602
603 func (c *Container) GetProperties() error {
604 params := url.Values{
605 "restype": {"container"},
606 }
607 headers := c.bsc.client.getStandardHeaders()
608
609 uri := c.bsc.client.getEndpoint(blobServiceName, c.buildPath(), params)
610
611 resp, err := c.bsc.client.exec(http.MethodGet, uri, headers, nil, c.bsc.auth)
612 if err != nil {
613 return err
614 }
615 defer resp.Body.Close()
616 if err := checkRespCode(resp, []int{http.StatusOK}); err != nil {
617 return err
618 }
619
620
621 c.Properties.Etag = resp.Header.Get(headerEtag)
622 c.Properties.LeaseStatus = resp.Header.Get("x-ms-lease-status")
623 c.Properties.LeaseState = resp.Header.Get("x-ms-lease-state")
624 c.Properties.LeaseDuration = resp.Header.Get("x-ms-lease-duration")
625 c.Properties.LastModified = resp.Header.Get("Last-Modified")
626 c.Properties.PublicAccess = ContainerAccessType(resp.Header.Get(ContainerAccessHeader))
627
628 return nil
629 }
630
View as plain text