container.go 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641
  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. "encoding/xml"
  17. "fmt"
  18. "io"
  19. "net/http"
  20. "net/url"
  21. "strconv"
  22. "strings"
  23. "time"
  24. )
  25. // Container represents an Azure container.
  26. type Container struct {
  27. bsc *BlobStorageClient
  28. Name string `xml:"Name"`
  29. Properties ContainerProperties `xml:"Properties"`
  30. Metadata map[string]string
  31. sasuri url.URL
  32. }
  33. // Client returns the HTTP client used by the Container reference.
  34. func (c *Container) Client() *Client {
  35. return &c.bsc.client
  36. }
  37. func (c *Container) buildPath() string {
  38. return fmt.Sprintf("/%s", c.Name)
  39. }
  40. // GetURL gets the canonical URL to the container.
  41. // This method does not create a publicly accessible URL if the container
  42. // is private and this method does not check if the blob exists.
  43. func (c *Container) GetURL() string {
  44. container := c.Name
  45. if container == "" {
  46. container = "$root"
  47. }
  48. return c.bsc.client.getEndpoint(blobServiceName, pathForResource(container, ""), nil)
  49. }
  50. // ContainerSASOptions are options to construct a container SAS
  51. // URI.
  52. // See https://docs.microsoft.com/en-us/rest/api/storageservices/constructing-a-service-sas
  53. type ContainerSASOptions struct {
  54. ContainerSASPermissions
  55. OverrideHeaders
  56. SASOptions
  57. }
  58. // ContainerSASPermissions includes the available permissions for
  59. // a container SAS URI.
  60. type ContainerSASPermissions struct {
  61. BlobServiceSASPermissions
  62. List bool
  63. }
  64. // GetSASURI creates an URL to the container which contains the Shared
  65. // Access Signature with the specified options.
  66. //
  67. // See https://docs.microsoft.com/en-us/rest/api/storageservices/constructing-a-service-sas
  68. func (c *Container) GetSASURI(options ContainerSASOptions) (string, error) {
  69. uri := c.GetURL()
  70. signedResource := "c"
  71. canonicalizedResource, err := c.bsc.client.buildCanonicalizedResource(uri, c.bsc.auth, true)
  72. if err != nil {
  73. return "", err
  74. }
  75. // build permissions string
  76. permissions := options.BlobServiceSASPermissions.buildString()
  77. if options.List {
  78. permissions += "l"
  79. }
  80. return c.bsc.client.blobAndFileSASURI(options.SASOptions, uri, permissions, canonicalizedResource, signedResource, options.OverrideHeaders)
  81. }
  82. // ContainerProperties contains various properties of a container returned from
  83. // various endpoints like ListContainers.
  84. type ContainerProperties struct {
  85. LastModified string `xml:"Last-Modified"`
  86. Etag string `xml:"Etag"`
  87. LeaseStatus string `xml:"LeaseStatus"`
  88. LeaseState string `xml:"LeaseState"`
  89. LeaseDuration string `xml:"LeaseDuration"`
  90. PublicAccess ContainerAccessType `xml:"PublicAccess"`
  91. }
  92. // ContainerListResponse contains the response fields from
  93. // ListContainers call.
  94. //
  95. // See https://msdn.microsoft.com/en-us/library/azure/dd179352.aspx
  96. type ContainerListResponse struct {
  97. XMLName xml.Name `xml:"EnumerationResults"`
  98. Xmlns string `xml:"xmlns,attr"`
  99. Prefix string `xml:"Prefix"`
  100. Marker string `xml:"Marker"`
  101. NextMarker string `xml:"NextMarker"`
  102. MaxResults int64 `xml:"MaxResults"`
  103. Containers []Container `xml:"Containers>Container"`
  104. }
  105. // BlobListResponse contains the response fields from ListBlobs call.
  106. //
  107. // See https://msdn.microsoft.com/en-us/library/azure/dd135734.aspx
  108. type BlobListResponse struct {
  109. XMLName xml.Name `xml:"EnumerationResults"`
  110. Xmlns string `xml:"xmlns,attr"`
  111. Prefix string `xml:"Prefix"`
  112. Marker string `xml:"Marker"`
  113. NextMarker string `xml:"NextMarker"`
  114. MaxResults int64 `xml:"MaxResults"`
  115. Blobs []Blob `xml:"Blobs>Blob"`
  116. // BlobPrefix is used to traverse blobs as if it were a file system.
  117. // It is returned if ListBlobsParameters.Delimiter is specified.
  118. // The list here can be thought of as "folders" that may contain
  119. // other folders or blobs.
  120. BlobPrefixes []string `xml:"Blobs>BlobPrefix>Name"`
  121. // Delimiter is used to traverse blobs as if it were a file system.
  122. // It is returned if ListBlobsParameters.Delimiter is specified.
  123. Delimiter string `xml:"Delimiter"`
  124. }
  125. // IncludeBlobDataset has options to include in a list blobs operation
  126. type IncludeBlobDataset struct {
  127. Snapshots bool
  128. Metadata bool
  129. UncommittedBlobs bool
  130. Copy bool
  131. }
  132. // ListBlobsParameters defines the set of customizable
  133. // parameters to make a List Blobs call.
  134. //
  135. // See https://msdn.microsoft.com/en-us/library/azure/dd135734.aspx
  136. type ListBlobsParameters struct {
  137. Prefix string
  138. Delimiter string
  139. Marker string
  140. Include *IncludeBlobDataset
  141. MaxResults uint
  142. Timeout uint
  143. RequestID string
  144. }
  145. func (p ListBlobsParameters) getParameters() url.Values {
  146. out := url.Values{}
  147. if p.Prefix != "" {
  148. out.Set("prefix", p.Prefix)
  149. }
  150. if p.Delimiter != "" {
  151. out.Set("delimiter", p.Delimiter)
  152. }
  153. if p.Marker != "" {
  154. out.Set("marker", p.Marker)
  155. }
  156. if p.Include != nil {
  157. include := []string{}
  158. include = addString(include, p.Include.Snapshots, "snapshots")
  159. include = addString(include, p.Include.Metadata, "metadata")
  160. include = addString(include, p.Include.UncommittedBlobs, "uncommittedblobs")
  161. include = addString(include, p.Include.Copy, "copy")
  162. fullInclude := strings.Join(include, ",")
  163. out.Set("include", fullInclude)
  164. }
  165. if p.MaxResults != 0 {
  166. out.Set("maxresults", strconv.FormatUint(uint64(p.MaxResults), 10))
  167. }
  168. if p.Timeout != 0 {
  169. out.Set("timeout", strconv.FormatUint(uint64(p.Timeout), 10))
  170. }
  171. return out
  172. }
  173. func addString(datasets []string, include bool, text string) []string {
  174. if include {
  175. datasets = append(datasets, text)
  176. }
  177. return datasets
  178. }
  179. // ContainerAccessType defines the access level to the container from a public
  180. // request.
  181. //
  182. // See https://msdn.microsoft.com/en-us/library/azure/dd179468.aspx and "x-ms-
  183. // blob-public-access" header.
  184. type ContainerAccessType string
  185. // Access options for containers
  186. const (
  187. ContainerAccessTypePrivate ContainerAccessType = ""
  188. ContainerAccessTypeBlob ContainerAccessType = "blob"
  189. ContainerAccessTypeContainer ContainerAccessType = "container"
  190. )
  191. // ContainerAccessPolicy represents each access policy in the container ACL.
  192. type ContainerAccessPolicy struct {
  193. ID string
  194. StartTime time.Time
  195. ExpiryTime time.Time
  196. CanRead bool
  197. CanWrite bool
  198. CanDelete bool
  199. }
  200. // ContainerPermissions represents the container ACLs.
  201. type ContainerPermissions struct {
  202. AccessType ContainerAccessType
  203. AccessPolicies []ContainerAccessPolicy
  204. }
  205. // ContainerAccessHeader references header used when setting/getting container ACL
  206. const (
  207. ContainerAccessHeader string = "x-ms-blob-public-access"
  208. )
  209. // GetBlobReference returns a Blob object for the specified blob name.
  210. func (c *Container) GetBlobReference(name string) *Blob {
  211. return &Blob{
  212. Container: c,
  213. Name: name,
  214. }
  215. }
  216. // CreateContainerOptions includes the options for a create container operation
  217. type CreateContainerOptions struct {
  218. Timeout uint
  219. Access ContainerAccessType `header:"x-ms-blob-public-access"`
  220. RequestID string `header:"x-ms-client-request-id"`
  221. }
  222. // Create creates a blob container within the storage account
  223. // with given name and access level. Returns error if container already exists.
  224. //
  225. // See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/Create-Container
  226. func (c *Container) Create(options *CreateContainerOptions) error {
  227. resp, err := c.create(options)
  228. if err != nil {
  229. return err
  230. }
  231. defer drainRespBody(resp)
  232. return checkRespCode(resp, []int{http.StatusCreated})
  233. }
  234. // CreateIfNotExists creates a blob container if it does not exist. Returns
  235. // true if container is newly created or false if container already exists.
  236. func (c *Container) CreateIfNotExists(options *CreateContainerOptions) (bool, error) {
  237. resp, err := c.create(options)
  238. if resp != nil {
  239. defer drainRespBody(resp)
  240. if resp.StatusCode == http.StatusCreated || resp.StatusCode == http.StatusConflict {
  241. return resp.StatusCode == http.StatusCreated, nil
  242. }
  243. }
  244. return false, err
  245. }
  246. func (c *Container) create(options *CreateContainerOptions) (*http.Response, error) {
  247. query := url.Values{"restype": {"container"}}
  248. headers := c.bsc.client.getStandardHeaders()
  249. headers = c.bsc.client.addMetadataToHeaders(headers, c.Metadata)
  250. if options != nil {
  251. query = addTimeout(query, options.Timeout)
  252. headers = mergeHeaders(headers, headersFromStruct(*options))
  253. }
  254. uri := c.bsc.client.getEndpoint(blobServiceName, c.buildPath(), query)
  255. return c.bsc.client.exec(http.MethodPut, uri, headers, nil, c.bsc.auth)
  256. }
  257. // Exists returns true if a container with given name exists
  258. // on the storage account, otherwise returns false.
  259. func (c *Container) Exists() (bool, error) {
  260. q := url.Values{"restype": {"container"}}
  261. var uri string
  262. if c.bsc.client.isServiceSASClient() {
  263. q = mergeParams(q, c.sasuri.Query())
  264. newURI := c.sasuri
  265. newURI.RawQuery = q.Encode()
  266. uri = newURI.String()
  267. } else {
  268. uri = c.bsc.client.getEndpoint(blobServiceName, c.buildPath(), q)
  269. }
  270. headers := c.bsc.client.getStandardHeaders()
  271. resp, err := c.bsc.client.exec(http.MethodHead, uri, headers, nil, c.bsc.auth)
  272. if resp != nil {
  273. defer drainRespBody(resp)
  274. if resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusNotFound {
  275. return resp.StatusCode == http.StatusOK, nil
  276. }
  277. }
  278. return false, err
  279. }
  280. // SetContainerPermissionOptions includes options for a set container permissions operation
  281. type SetContainerPermissionOptions struct {
  282. Timeout uint
  283. LeaseID string `header:"x-ms-lease-id"`
  284. IfModifiedSince *time.Time `header:"If-Modified-Since"`
  285. IfUnmodifiedSince *time.Time `header:"If-Unmodified-Since"`
  286. RequestID string `header:"x-ms-client-request-id"`
  287. }
  288. // SetPermissions sets up container permissions
  289. // See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/Set-Container-ACL
  290. func (c *Container) SetPermissions(permissions ContainerPermissions, options *SetContainerPermissionOptions) error {
  291. body, length, err := generateContainerACLpayload(permissions.AccessPolicies)
  292. if err != nil {
  293. return err
  294. }
  295. params := url.Values{
  296. "restype": {"container"},
  297. "comp": {"acl"},
  298. }
  299. headers := c.bsc.client.getStandardHeaders()
  300. headers = addToHeaders(headers, ContainerAccessHeader, string(permissions.AccessType))
  301. headers["Content-Length"] = strconv.Itoa(length)
  302. if options != nil {
  303. params = addTimeout(params, options.Timeout)
  304. headers = mergeHeaders(headers, headersFromStruct(*options))
  305. }
  306. uri := c.bsc.client.getEndpoint(blobServiceName, c.buildPath(), params)
  307. resp, err := c.bsc.client.exec(http.MethodPut, uri, headers, body, c.bsc.auth)
  308. if err != nil {
  309. return err
  310. }
  311. defer drainRespBody(resp)
  312. return checkRespCode(resp, []int{http.StatusOK})
  313. }
  314. // GetContainerPermissionOptions includes options for a get container permissions operation
  315. type GetContainerPermissionOptions struct {
  316. Timeout uint
  317. LeaseID string `header:"x-ms-lease-id"`
  318. RequestID string `header:"x-ms-client-request-id"`
  319. }
  320. // GetPermissions gets the container permissions as per https://msdn.microsoft.com/en-us/library/azure/dd179469.aspx
  321. // If timeout is 0 then it will not be passed to Azure
  322. // leaseID will only be passed to Azure if populated
  323. func (c *Container) GetPermissions(options *GetContainerPermissionOptions) (*ContainerPermissions, error) {
  324. params := url.Values{
  325. "restype": {"container"},
  326. "comp": {"acl"},
  327. }
  328. headers := c.bsc.client.getStandardHeaders()
  329. if options != nil {
  330. params = addTimeout(params, options.Timeout)
  331. headers = mergeHeaders(headers, headersFromStruct(*options))
  332. }
  333. uri := c.bsc.client.getEndpoint(blobServiceName, c.buildPath(), params)
  334. resp, err := c.bsc.client.exec(http.MethodGet, uri, headers, nil, c.bsc.auth)
  335. if err != nil {
  336. return nil, err
  337. }
  338. defer resp.Body.Close()
  339. var ap AccessPolicy
  340. err = xmlUnmarshal(resp.Body, &ap.SignedIdentifiersList)
  341. if err != nil {
  342. return nil, err
  343. }
  344. return buildAccessPolicy(ap, &resp.Header), nil
  345. }
  346. func buildAccessPolicy(ap AccessPolicy, headers *http.Header) *ContainerPermissions {
  347. // containerAccess. Blob, Container, empty
  348. containerAccess := headers.Get(http.CanonicalHeaderKey(ContainerAccessHeader))
  349. permissions := ContainerPermissions{
  350. AccessType: ContainerAccessType(containerAccess),
  351. AccessPolicies: []ContainerAccessPolicy{},
  352. }
  353. for _, policy := range ap.SignedIdentifiersList.SignedIdentifiers {
  354. capd := ContainerAccessPolicy{
  355. ID: policy.ID,
  356. StartTime: policy.AccessPolicy.StartTime,
  357. ExpiryTime: policy.AccessPolicy.ExpiryTime,
  358. }
  359. capd.CanRead = updatePermissions(policy.AccessPolicy.Permission, "r")
  360. capd.CanWrite = updatePermissions(policy.AccessPolicy.Permission, "w")
  361. capd.CanDelete = updatePermissions(policy.AccessPolicy.Permission, "d")
  362. permissions.AccessPolicies = append(permissions.AccessPolicies, capd)
  363. }
  364. return &permissions
  365. }
  366. // DeleteContainerOptions includes options for a delete container operation
  367. type DeleteContainerOptions struct {
  368. Timeout uint
  369. LeaseID string `header:"x-ms-lease-id"`
  370. IfModifiedSince *time.Time `header:"If-Modified-Since"`
  371. IfUnmodifiedSince *time.Time `header:"If-Unmodified-Since"`
  372. RequestID string `header:"x-ms-client-request-id"`
  373. }
  374. // Delete deletes the container with given name on the storage
  375. // account. If the container does not exist returns error.
  376. //
  377. // See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/delete-container
  378. func (c *Container) Delete(options *DeleteContainerOptions) error {
  379. resp, err := c.delete(options)
  380. if err != nil {
  381. return err
  382. }
  383. defer drainRespBody(resp)
  384. return checkRespCode(resp, []int{http.StatusAccepted})
  385. }
  386. // DeleteIfExists deletes the container with given name on the storage
  387. // account if it exists. Returns true if container is deleted with this call, or
  388. // false if the container did not exist at the time of the Delete Container
  389. // operation.
  390. //
  391. // See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/delete-container
  392. func (c *Container) DeleteIfExists(options *DeleteContainerOptions) (bool, error) {
  393. resp, err := c.delete(options)
  394. if resp != nil {
  395. defer drainRespBody(resp)
  396. if resp.StatusCode == http.StatusAccepted || resp.StatusCode == http.StatusNotFound {
  397. return resp.StatusCode == http.StatusAccepted, nil
  398. }
  399. }
  400. return false, err
  401. }
  402. func (c *Container) delete(options *DeleteContainerOptions) (*http.Response, error) {
  403. query := url.Values{"restype": {"container"}}
  404. headers := c.bsc.client.getStandardHeaders()
  405. if options != nil {
  406. query = addTimeout(query, options.Timeout)
  407. headers = mergeHeaders(headers, headersFromStruct(*options))
  408. }
  409. uri := c.bsc.client.getEndpoint(blobServiceName, c.buildPath(), query)
  410. return c.bsc.client.exec(http.MethodDelete, uri, headers, nil, c.bsc.auth)
  411. }
  412. // ListBlobs returns an object that contains list of blobs in the container,
  413. // pagination token and other information in the response of List Blobs call.
  414. //
  415. // See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/List-Blobs
  416. func (c *Container) ListBlobs(params ListBlobsParameters) (BlobListResponse, error) {
  417. q := mergeParams(params.getParameters(), url.Values{
  418. "restype": {"container"},
  419. "comp": {"list"},
  420. })
  421. var uri string
  422. if c.bsc.client.isServiceSASClient() {
  423. q = mergeParams(q, c.sasuri.Query())
  424. newURI := c.sasuri
  425. newURI.RawQuery = q.Encode()
  426. uri = newURI.String()
  427. } else {
  428. uri = c.bsc.client.getEndpoint(blobServiceName, c.buildPath(), q)
  429. }
  430. headers := c.bsc.client.getStandardHeaders()
  431. headers = addToHeaders(headers, "x-ms-client-request-id", params.RequestID)
  432. var out BlobListResponse
  433. resp, err := c.bsc.client.exec(http.MethodGet, uri, headers, nil, c.bsc.auth)
  434. if err != nil {
  435. return out, err
  436. }
  437. defer resp.Body.Close()
  438. err = xmlUnmarshal(resp.Body, &out)
  439. for i := range out.Blobs {
  440. out.Blobs[i].Container = c
  441. }
  442. return out, err
  443. }
  444. // ContainerMetadataOptions includes options for container metadata operations
  445. type ContainerMetadataOptions struct {
  446. Timeout uint
  447. LeaseID string `header:"x-ms-lease-id"`
  448. RequestID string `header:"x-ms-client-request-id"`
  449. }
  450. // SetMetadata replaces the metadata for the specified container.
  451. //
  452. // Some keys may be converted to Camel-Case before sending. All keys
  453. // are returned in lower case by GetBlobMetadata. HTTP header names
  454. // are case-insensitive so case munging should not matter to other
  455. // applications either.
  456. //
  457. // See https://docs.microsoft.com/en-us/rest/api/storageservices/set-container-metadata
  458. func (c *Container) SetMetadata(options *ContainerMetadataOptions) error {
  459. params := url.Values{
  460. "comp": {"metadata"},
  461. "restype": {"container"},
  462. }
  463. headers := c.bsc.client.getStandardHeaders()
  464. headers = c.bsc.client.addMetadataToHeaders(headers, c.Metadata)
  465. if options != nil {
  466. params = addTimeout(params, options.Timeout)
  467. headers = mergeHeaders(headers, headersFromStruct(*options))
  468. }
  469. uri := c.bsc.client.getEndpoint(blobServiceName, c.buildPath(), params)
  470. resp, err := c.bsc.client.exec(http.MethodPut, uri, headers, nil, c.bsc.auth)
  471. if err != nil {
  472. return err
  473. }
  474. defer drainRespBody(resp)
  475. return checkRespCode(resp, []int{http.StatusOK})
  476. }
  477. // GetMetadata returns all user-defined metadata for the specified container.
  478. //
  479. // All metadata keys will be returned in lower case. (HTTP header
  480. // names are case-insensitive.)
  481. //
  482. // See https://docs.microsoft.com/en-us/rest/api/storageservices/get-container-metadata
  483. func (c *Container) GetMetadata(options *ContainerMetadataOptions) error {
  484. params := url.Values{
  485. "comp": {"metadata"},
  486. "restype": {"container"},
  487. }
  488. headers := c.bsc.client.getStandardHeaders()
  489. if options != nil {
  490. params = addTimeout(params, options.Timeout)
  491. headers = mergeHeaders(headers, headersFromStruct(*options))
  492. }
  493. uri := c.bsc.client.getEndpoint(blobServiceName, c.buildPath(), params)
  494. resp, err := c.bsc.client.exec(http.MethodGet, uri, headers, nil, c.bsc.auth)
  495. if err != nil {
  496. return err
  497. }
  498. defer drainRespBody(resp)
  499. if err := checkRespCode(resp, []int{http.StatusOK}); err != nil {
  500. return err
  501. }
  502. c.writeMetadata(resp.Header)
  503. return nil
  504. }
  505. func (c *Container) writeMetadata(h http.Header) {
  506. c.Metadata = writeMetadata(h)
  507. }
  508. func generateContainerACLpayload(policies []ContainerAccessPolicy) (io.Reader, int, error) {
  509. sil := SignedIdentifiers{
  510. SignedIdentifiers: []SignedIdentifier{},
  511. }
  512. for _, capd := range policies {
  513. permission := capd.generateContainerPermissions()
  514. signedIdentifier := convertAccessPolicyToXMLStructs(capd.ID, capd.StartTime, capd.ExpiryTime, permission)
  515. sil.SignedIdentifiers = append(sil.SignedIdentifiers, signedIdentifier)
  516. }
  517. return xmlMarshal(sil)
  518. }
  519. func (capd *ContainerAccessPolicy) generateContainerPermissions() (permissions string) {
  520. // generate the permissions string (rwd).
  521. // still want the end user API to have bool flags.
  522. permissions = ""
  523. if capd.CanRead {
  524. permissions += "r"
  525. }
  526. if capd.CanWrite {
  527. permissions += "w"
  528. }
  529. if capd.CanDelete {
  530. permissions += "d"
  531. }
  532. return permissions
  533. }
  534. // GetProperties updated the properties of the container.
  535. //
  536. // See https://docs.microsoft.com/en-us/rest/api/storageservices/get-container-properties
  537. func (c *Container) GetProperties() error {
  538. params := url.Values{
  539. "restype": {"container"},
  540. }
  541. headers := c.bsc.client.getStandardHeaders()
  542. uri := c.bsc.client.getEndpoint(blobServiceName, c.buildPath(), params)
  543. resp, err := c.bsc.client.exec(http.MethodGet, uri, headers, nil, c.bsc.auth)
  544. if err != nil {
  545. return err
  546. }
  547. defer resp.Body.Close()
  548. if err := checkRespCode(resp, []int{http.StatusOK}); err != nil {
  549. return err
  550. }
  551. // update properties
  552. c.Properties.Etag = resp.Header.Get(headerEtag)
  553. c.Properties.LeaseStatus = resp.Header.Get("x-ms-lease-status")
  554. c.Properties.LeaseState = resp.Header.Get("x-ms-lease-state")
  555. c.Properties.LeaseDuration = resp.Header.Get("x-ms-lease-duration")
  556. c.Properties.LastModified = resp.Header.Get("Last-Modified")
  557. c.Properties.PublicAccess = ContainerAccessType(resp.Header.Get(ContainerAccessHeader))
  558. return nil
  559. }