641 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			Go
		
	
	
			
		
		
	
	
			641 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			Go
		
	
	
| package storage
 | |
| 
 | |
| // Copyright 2017 Microsoft Corporation
 | |
| //
 | |
| //  Licensed under the Apache License, Version 2.0 (the "License");
 | |
| //  you may not use this file except in compliance with the License.
 | |
| //  You may obtain a copy of the License at
 | |
| //
 | |
| //      http://www.apache.org/licenses/LICENSE-2.0
 | |
| //
 | |
| //  Unless required by applicable law or agreed to in writing, software
 | |
| //  distributed under the License is distributed on an "AS IS" BASIS,
 | |
| //  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | |
| //  See the License for the specific language governing permissions and
 | |
| //  limitations under the License.
 | |
| 
 | |
| import (
 | |
| 	"encoding/xml"
 | |
| 	"fmt"
 | |
| 	"io"
 | |
| 	"net/http"
 | |
| 	"net/url"
 | |
| 	"strconv"
 | |
| 	"strings"
 | |
| 	"time"
 | |
| )
 | |
| 
 | |
| // Container represents an Azure container.
 | |
| type Container struct {
 | |
| 	bsc        *BlobStorageClient
 | |
| 	Name       string              `xml:"Name"`
 | |
| 	Properties ContainerProperties `xml:"Properties"`
 | |
| 	Metadata   map[string]string
 | |
| 	sasuri     url.URL
 | |
| }
 | |
| 
 | |
| // Client returns the HTTP client used by the Container reference.
 | |
| func (c *Container) Client() *Client {
 | |
| 	return &c.bsc.client
 | |
| }
 | |
| 
 | |
| func (c *Container) buildPath() string {
 | |
| 	return fmt.Sprintf("/%s", c.Name)
 | |
| }
 | |
| 
 | |
| // GetURL gets the canonical URL to the container.
 | |
| // This method does not create a publicly accessible URL if the container
 | |
| // is private and this method does not check if the blob exists.
 | |
| func (c *Container) GetURL() string {
 | |
| 	container := c.Name
 | |
| 	if container == "" {
 | |
| 		container = "$root"
 | |
| 	}
 | |
| 	return c.bsc.client.getEndpoint(blobServiceName, pathForResource(container, ""), nil)
 | |
| }
 | |
| 
 | |
| // ContainerSASOptions are options to construct a container SAS
 | |
| // URI.
 | |
| // See https://docs.microsoft.com/en-us/rest/api/storageservices/constructing-a-service-sas
 | |
| type ContainerSASOptions struct {
 | |
| 	ContainerSASPermissions
 | |
| 	OverrideHeaders
 | |
| 	SASOptions
 | |
| }
 | |
| 
 | |
| // ContainerSASPermissions includes the available permissions for
 | |
| // a container SAS URI.
 | |
| type ContainerSASPermissions struct {
 | |
| 	BlobServiceSASPermissions
 | |
| 	List bool
 | |
| }
 | |
| 
 | |
| // GetSASURI creates an URL to the container which contains the Shared
 | |
| // Access Signature with the specified options.
 | |
| //
 | |
| // See https://docs.microsoft.com/en-us/rest/api/storageservices/constructing-a-service-sas
 | |
| func (c *Container) GetSASURI(options ContainerSASOptions) (string, error) {
 | |
| 	uri := c.GetURL()
 | |
| 	signedResource := "c"
 | |
| 	canonicalizedResource, err := c.bsc.client.buildCanonicalizedResource(uri, c.bsc.auth, true)
 | |
| 	if err != nil {
 | |
| 		return "", err
 | |
| 	}
 | |
| 
 | |
| 	// build permissions string
 | |
| 	permissions := options.BlobServiceSASPermissions.buildString()
 | |
| 	if options.List {
 | |
| 		permissions += "l"
 | |
| 	}
 | |
| 
 | |
| 	return c.bsc.client.blobAndFileSASURI(options.SASOptions, uri, permissions, canonicalizedResource, signedResource, options.OverrideHeaders)
 | |
| }
 | |
| 
 | |
| // ContainerProperties contains various properties of a container returned from
 | |
| // various endpoints like ListContainers.
 | |
| type ContainerProperties struct {
 | |
| 	LastModified  string              `xml:"Last-Modified"`
 | |
| 	Etag          string              `xml:"Etag"`
 | |
| 	LeaseStatus   string              `xml:"LeaseStatus"`
 | |
| 	LeaseState    string              `xml:"LeaseState"`
 | |
| 	LeaseDuration string              `xml:"LeaseDuration"`
 | |
| 	PublicAccess  ContainerAccessType `xml:"PublicAccess"`
 | |
| }
 | |
| 
 | |
| // ContainerListResponse contains the response fields from
 | |
| // ListContainers call.
 | |
| //
 | |
| // See https://msdn.microsoft.com/en-us/library/azure/dd179352.aspx
 | |
| type ContainerListResponse struct {
 | |
| 	XMLName    xml.Name    `xml:"EnumerationResults"`
 | |
| 	Xmlns      string      `xml:"xmlns,attr"`
 | |
| 	Prefix     string      `xml:"Prefix"`
 | |
| 	Marker     string      `xml:"Marker"`
 | |
| 	NextMarker string      `xml:"NextMarker"`
 | |
| 	MaxResults int64       `xml:"MaxResults"`
 | |
| 	Containers []Container `xml:"Containers>Container"`
 | |
| }
 | |
| 
 | |
| // BlobListResponse contains the response fields from ListBlobs call.
 | |
| //
 | |
| // See https://msdn.microsoft.com/en-us/library/azure/dd135734.aspx
 | |
| type BlobListResponse struct {
 | |
| 	XMLName    xml.Name `xml:"EnumerationResults"`
 | |
| 	Xmlns      string   `xml:"xmlns,attr"`
 | |
| 	Prefix     string   `xml:"Prefix"`
 | |
| 	Marker     string   `xml:"Marker"`
 | |
| 	NextMarker string   `xml:"NextMarker"`
 | |
| 	MaxResults int64    `xml:"MaxResults"`
 | |
| 	Blobs      []Blob   `xml:"Blobs>Blob"`
 | |
| 
 | |
| 	// BlobPrefix is used to traverse blobs as if it were a file system.
 | |
| 	// It is returned if ListBlobsParameters.Delimiter is specified.
 | |
| 	// The list here can be thought of as "folders" that may contain
 | |
| 	// other folders or blobs.
 | |
| 	BlobPrefixes []string `xml:"Blobs>BlobPrefix>Name"`
 | |
| 
 | |
| 	// Delimiter is used to traverse blobs as if it were a file system.
 | |
| 	// It is returned if ListBlobsParameters.Delimiter is specified.
 | |
| 	Delimiter string `xml:"Delimiter"`
 | |
| }
 | |
| 
 | |
| // IncludeBlobDataset has options to include in a list blobs operation
 | |
| type IncludeBlobDataset struct {
 | |
| 	Snapshots        bool
 | |
| 	Metadata         bool
 | |
| 	UncommittedBlobs bool
 | |
| 	Copy             bool
 | |
| }
 | |
| 
 | |
| // ListBlobsParameters defines the set of customizable
 | |
| // parameters to make a List Blobs call.
 | |
| //
 | |
| // See https://msdn.microsoft.com/en-us/library/azure/dd135734.aspx
 | |
| type ListBlobsParameters struct {
 | |
| 	Prefix     string
 | |
| 	Delimiter  string
 | |
| 	Marker     string
 | |
| 	Include    *IncludeBlobDataset
 | |
| 	MaxResults uint
 | |
| 	Timeout    uint
 | |
| 	RequestID  string
 | |
| }
 | |
| 
 | |
| func (p ListBlobsParameters) getParameters() url.Values {
 | |
| 	out := url.Values{}
 | |
| 
 | |
| 	if p.Prefix != "" {
 | |
| 		out.Set("prefix", p.Prefix)
 | |
| 	}
 | |
| 	if p.Delimiter != "" {
 | |
| 		out.Set("delimiter", p.Delimiter)
 | |
| 	}
 | |
| 	if p.Marker != "" {
 | |
| 		out.Set("marker", p.Marker)
 | |
| 	}
 | |
| 	if p.Include != nil {
 | |
| 		include := []string{}
 | |
| 		include = addString(include, p.Include.Snapshots, "snapshots")
 | |
| 		include = addString(include, p.Include.Metadata, "metadata")
 | |
| 		include = addString(include, p.Include.UncommittedBlobs, "uncommittedblobs")
 | |
| 		include = addString(include, p.Include.Copy, "copy")
 | |
| 		fullInclude := strings.Join(include, ",")
 | |
| 		out.Set("include", fullInclude)
 | |
| 	}
 | |
| 	if p.MaxResults != 0 {
 | |
| 		out.Set("maxresults", strconv.FormatUint(uint64(p.MaxResults), 10))
 | |
| 	}
 | |
| 	if p.Timeout != 0 {
 | |
| 		out.Set("timeout", strconv.FormatUint(uint64(p.Timeout), 10))
 | |
| 	}
 | |
| 
 | |
| 	return out
 | |
| }
 | |
| 
 | |
| func addString(datasets []string, include bool, text string) []string {
 | |
| 	if include {
 | |
| 		datasets = append(datasets, text)
 | |
| 	}
 | |
| 	return datasets
 | |
| }
 | |
| 
 | |
| // ContainerAccessType defines the access level to the container from a public
 | |
| // request.
 | |
| //
 | |
| // See https://msdn.microsoft.com/en-us/library/azure/dd179468.aspx and "x-ms-
 | |
| // blob-public-access" header.
 | |
| type ContainerAccessType string
 | |
| 
 | |
| // Access options for containers
 | |
| const (
 | |
| 	ContainerAccessTypePrivate   ContainerAccessType = ""
 | |
| 	ContainerAccessTypeBlob      ContainerAccessType = "blob"
 | |
| 	ContainerAccessTypeContainer ContainerAccessType = "container"
 | |
| )
 | |
| 
 | |
| // ContainerAccessPolicy represents each access policy in the container ACL.
 | |
| type ContainerAccessPolicy struct {
 | |
| 	ID         string
 | |
| 	StartTime  time.Time
 | |
| 	ExpiryTime time.Time
 | |
| 	CanRead    bool
 | |
| 	CanWrite   bool
 | |
| 	CanDelete  bool
 | |
| }
 | |
| 
 | |
| // ContainerPermissions represents the container ACLs.
 | |
| type ContainerPermissions struct {
 | |
| 	AccessType     ContainerAccessType
 | |
| 	AccessPolicies []ContainerAccessPolicy
 | |
| }
 | |
| 
 | |
| // ContainerAccessHeader references header used when setting/getting container ACL
 | |
| const (
 | |
| 	ContainerAccessHeader string = "x-ms-blob-public-access"
 | |
| )
 | |
| 
 | |
| // GetBlobReference returns a Blob object for the specified blob name.
 | |
| func (c *Container) GetBlobReference(name string) *Blob {
 | |
| 	return &Blob{
 | |
| 		Container: c,
 | |
| 		Name:      name,
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // CreateContainerOptions includes the options for a create container operation
 | |
| type CreateContainerOptions struct {
 | |
| 	Timeout   uint
 | |
| 	Access    ContainerAccessType `header:"x-ms-blob-public-access"`
 | |
| 	RequestID string              `header:"x-ms-client-request-id"`
 | |
| }
 | |
| 
 | |
| // Create creates a blob container within the storage account
 | |
| // with given name and access level. Returns error if container already exists.
 | |
| //
 | |
| // See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/Create-Container
 | |
| func (c *Container) Create(options *CreateContainerOptions) error {
 | |
| 	resp, err := c.create(options)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	defer drainRespBody(resp)
 | |
| 	return checkRespCode(resp, []int{http.StatusCreated})
 | |
| }
 | |
| 
 | |
| // CreateIfNotExists creates a blob container if it does not exist. Returns
 | |
| // true if container is newly created or false if container already exists.
 | |
| func (c *Container) CreateIfNotExists(options *CreateContainerOptions) (bool, error) {
 | |
| 	resp, err := c.create(options)
 | |
| 	if resp != nil {
 | |
| 		defer drainRespBody(resp)
 | |
| 		if resp.StatusCode == http.StatusCreated || resp.StatusCode == http.StatusConflict {
 | |
| 			return resp.StatusCode == http.StatusCreated, nil
 | |
| 		}
 | |
| 	}
 | |
| 	return false, err
 | |
| }
 | |
| 
 | |
| func (c *Container) create(options *CreateContainerOptions) (*http.Response, error) {
 | |
| 	query := url.Values{"restype": {"container"}}
 | |
| 	headers := c.bsc.client.getStandardHeaders()
 | |
| 	headers = c.bsc.client.addMetadataToHeaders(headers, c.Metadata)
 | |
| 
 | |
| 	if options != nil {
 | |
| 		query = addTimeout(query, options.Timeout)
 | |
| 		headers = mergeHeaders(headers, headersFromStruct(*options))
 | |
| 	}
 | |
| 	uri := c.bsc.client.getEndpoint(blobServiceName, c.buildPath(), query)
 | |
| 
 | |
| 	return c.bsc.client.exec(http.MethodPut, uri, headers, nil, c.bsc.auth)
 | |
| }
 | |
| 
 | |
| // Exists returns true if a container with given name exists
 | |
| // on the storage account, otherwise returns false.
 | |
| func (c *Container) Exists() (bool, error) {
 | |
| 	q := url.Values{"restype": {"container"}}
 | |
| 	var uri string
 | |
| 	if c.bsc.client.isServiceSASClient() {
 | |
| 		q = mergeParams(q, c.sasuri.Query())
 | |
| 		newURI := c.sasuri
 | |
| 		newURI.RawQuery = q.Encode()
 | |
| 		uri = newURI.String()
 | |
| 
 | |
| 	} else {
 | |
| 		uri = c.bsc.client.getEndpoint(blobServiceName, c.buildPath(), q)
 | |
| 	}
 | |
| 	headers := c.bsc.client.getStandardHeaders()
 | |
| 
 | |
| 	resp, err := c.bsc.client.exec(http.MethodHead, uri, headers, nil, c.bsc.auth)
 | |
| 	if resp != nil {
 | |
| 		defer drainRespBody(resp)
 | |
| 		if resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusNotFound {
 | |
| 			return resp.StatusCode == http.StatusOK, nil
 | |
| 		}
 | |
| 	}
 | |
| 	return false, err
 | |
| }
 | |
| 
 | |
| // SetContainerPermissionOptions includes options for a set container permissions operation
 | |
| type SetContainerPermissionOptions struct {
 | |
| 	Timeout           uint
 | |
| 	LeaseID           string     `header:"x-ms-lease-id"`
 | |
| 	IfModifiedSince   *time.Time `header:"If-Modified-Since"`
 | |
| 	IfUnmodifiedSince *time.Time `header:"If-Unmodified-Since"`
 | |
| 	RequestID         string     `header:"x-ms-client-request-id"`
 | |
| }
 | |
| 
 | |
| // SetPermissions sets up container permissions
 | |
| // See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/Set-Container-ACL
 | |
| func (c *Container) SetPermissions(permissions ContainerPermissions, options *SetContainerPermissionOptions) error {
 | |
| 	body, length, err := generateContainerACLpayload(permissions.AccessPolicies)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	params := url.Values{
 | |
| 		"restype": {"container"},
 | |
| 		"comp":    {"acl"},
 | |
| 	}
 | |
| 	headers := c.bsc.client.getStandardHeaders()
 | |
| 	headers = addToHeaders(headers, ContainerAccessHeader, string(permissions.AccessType))
 | |
| 	headers["Content-Length"] = strconv.Itoa(length)
 | |
| 
 | |
| 	if options != nil {
 | |
| 		params = addTimeout(params, options.Timeout)
 | |
| 		headers = mergeHeaders(headers, headersFromStruct(*options))
 | |
| 	}
 | |
| 	uri := c.bsc.client.getEndpoint(blobServiceName, c.buildPath(), params)
 | |
| 
 | |
| 	resp, err := c.bsc.client.exec(http.MethodPut, uri, headers, body, c.bsc.auth)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	defer drainRespBody(resp)
 | |
| 	return checkRespCode(resp, []int{http.StatusOK})
 | |
| }
 | |
| 
 | |
| // GetContainerPermissionOptions includes options for a get container permissions operation
 | |
| type GetContainerPermissionOptions struct {
 | |
| 	Timeout   uint
 | |
| 	LeaseID   string `header:"x-ms-lease-id"`
 | |
| 	RequestID string `header:"x-ms-client-request-id"`
 | |
| }
 | |
| 
 | |
| // GetPermissions gets the container permissions as per https://msdn.microsoft.com/en-us/library/azure/dd179469.aspx
 | |
| // If timeout is 0 then it will not be passed to Azure
 | |
| // leaseID will only be passed to Azure if populated
 | |
| func (c *Container) GetPermissions(options *GetContainerPermissionOptions) (*ContainerPermissions, error) {
 | |
| 	params := url.Values{
 | |
| 		"restype": {"container"},
 | |
| 		"comp":    {"acl"},
 | |
| 	}
 | |
| 	headers := c.bsc.client.getStandardHeaders()
 | |
| 
 | |
| 	if options != nil {
 | |
| 		params = addTimeout(params, options.Timeout)
 | |
| 		headers = mergeHeaders(headers, headersFromStruct(*options))
 | |
| 	}
 | |
| 	uri := c.bsc.client.getEndpoint(blobServiceName, c.buildPath(), params)
 | |
| 
 | |
| 	resp, err := c.bsc.client.exec(http.MethodGet, uri, headers, nil, c.bsc.auth)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	defer resp.Body.Close()
 | |
| 
 | |
| 	var ap AccessPolicy
 | |
| 	err = xmlUnmarshal(resp.Body, &ap.SignedIdentifiersList)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	return buildAccessPolicy(ap, &resp.Header), nil
 | |
| }
 | |
| 
 | |
| func buildAccessPolicy(ap AccessPolicy, headers *http.Header) *ContainerPermissions {
 | |
| 	// containerAccess. Blob, Container, empty
 | |
| 	containerAccess := headers.Get(http.CanonicalHeaderKey(ContainerAccessHeader))
 | |
| 	permissions := ContainerPermissions{
 | |
| 		AccessType:     ContainerAccessType(containerAccess),
 | |
| 		AccessPolicies: []ContainerAccessPolicy{},
 | |
| 	}
 | |
| 
 | |
| 	for _, policy := range ap.SignedIdentifiersList.SignedIdentifiers {
 | |
| 		capd := ContainerAccessPolicy{
 | |
| 			ID:         policy.ID,
 | |
| 			StartTime:  policy.AccessPolicy.StartTime,
 | |
| 			ExpiryTime: policy.AccessPolicy.ExpiryTime,
 | |
| 		}
 | |
| 		capd.CanRead = updatePermissions(policy.AccessPolicy.Permission, "r")
 | |
| 		capd.CanWrite = updatePermissions(policy.AccessPolicy.Permission, "w")
 | |
| 		capd.CanDelete = updatePermissions(policy.AccessPolicy.Permission, "d")
 | |
| 
 | |
| 		permissions.AccessPolicies = append(permissions.AccessPolicies, capd)
 | |
| 	}
 | |
| 	return &permissions
 | |
| }
 | |
| 
 | |
| // DeleteContainerOptions includes options for a delete container operation
 | |
| type DeleteContainerOptions struct {
 | |
| 	Timeout           uint
 | |
| 	LeaseID           string     `header:"x-ms-lease-id"`
 | |
| 	IfModifiedSince   *time.Time `header:"If-Modified-Since"`
 | |
| 	IfUnmodifiedSince *time.Time `header:"If-Unmodified-Since"`
 | |
| 	RequestID         string     `header:"x-ms-client-request-id"`
 | |
| }
 | |
| 
 | |
| // Delete deletes the container with given name on the storage
 | |
| // account. If the container does not exist returns error.
 | |
| //
 | |
| // See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/delete-container
 | |
| func (c *Container) Delete(options *DeleteContainerOptions) error {
 | |
| 	resp, err := c.delete(options)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	defer drainRespBody(resp)
 | |
| 	return checkRespCode(resp, []int{http.StatusAccepted})
 | |
| }
 | |
| 
 | |
| // DeleteIfExists deletes the container with given name on the storage
 | |
| // account if it exists. Returns true if container is deleted with this call, or
 | |
| // false if the container did not exist at the time of the Delete Container
 | |
| // operation.
 | |
| //
 | |
| // See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/delete-container
 | |
| func (c *Container) DeleteIfExists(options *DeleteContainerOptions) (bool, error) {
 | |
| 	resp, err := c.delete(options)
 | |
| 	if resp != nil {
 | |
| 		defer drainRespBody(resp)
 | |
| 		if resp.StatusCode == http.StatusAccepted || resp.StatusCode == http.StatusNotFound {
 | |
| 			return resp.StatusCode == http.StatusAccepted, nil
 | |
| 		}
 | |
| 	}
 | |
| 	return false, err
 | |
| }
 | |
| 
 | |
| func (c *Container) delete(options *DeleteContainerOptions) (*http.Response, error) {
 | |
| 	query := url.Values{"restype": {"container"}}
 | |
| 	headers := c.bsc.client.getStandardHeaders()
 | |
| 
 | |
| 	if options != nil {
 | |
| 		query = addTimeout(query, options.Timeout)
 | |
| 		headers = mergeHeaders(headers, headersFromStruct(*options))
 | |
| 	}
 | |
| 	uri := c.bsc.client.getEndpoint(blobServiceName, c.buildPath(), query)
 | |
| 
 | |
| 	return c.bsc.client.exec(http.MethodDelete, uri, headers, nil, c.bsc.auth)
 | |
| }
 | |
| 
 | |
| // ListBlobs returns an object that contains list of blobs in the container,
 | |
| // pagination token and other information in the response of List Blobs call.
 | |
| //
 | |
| // See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/List-Blobs
 | |
| func (c *Container) ListBlobs(params ListBlobsParameters) (BlobListResponse, error) {
 | |
| 	q := mergeParams(params.getParameters(), url.Values{
 | |
| 		"restype": {"container"},
 | |
| 		"comp":    {"list"},
 | |
| 	})
 | |
| 	var uri string
 | |
| 	if c.bsc.client.isServiceSASClient() {
 | |
| 		q = mergeParams(q, c.sasuri.Query())
 | |
| 		newURI := c.sasuri
 | |
| 		newURI.RawQuery = q.Encode()
 | |
| 		uri = newURI.String()
 | |
| 	} else {
 | |
| 		uri = c.bsc.client.getEndpoint(blobServiceName, c.buildPath(), q)
 | |
| 	}
 | |
| 
 | |
| 	headers := c.bsc.client.getStandardHeaders()
 | |
| 	headers = addToHeaders(headers, "x-ms-client-request-id", params.RequestID)
 | |
| 
 | |
| 	var out BlobListResponse
 | |
| 	resp, err := c.bsc.client.exec(http.MethodGet, uri, headers, nil, c.bsc.auth)
 | |
| 	if err != nil {
 | |
| 		return out, err
 | |
| 	}
 | |
| 	defer resp.Body.Close()
 | |
| 
 | |
| 	err = xmlUnmarshal(resp.Body, &out)
 | |
| 	for i := range out.Blobs {
 | |
| 		out.Blobs[i].Container = c
 | |
| 	}
 | |
| 	return out, err
 | |
| }
 | |
| 
 | |
| // ContainerMetadataOptions includes options for container metadata operations
 | |
| type ContainerMetadataOptions struct {
 | |
| 	Timeout   uint
 | |
| 	LeaseID   string `header:"x-ms-lease-id"`
 | |
| 	RequestID string `header:"x-ms-client-request-id"`
 | |
| }
 | |
| 
 | |
| // SetMetadata replaces the metadata for the specified container.
 | |
| //
 | |
| // Some keys may be converted to Camel-Case before sending. All keys
 | |
| // are returned in lower case by GetBlobMetadata. HTTP header names
 | |
| // are case-insensitive so case munging should not matter to other
 | |
| // applications either.
 | |
| //
 | |
| // See https://docs.microsoft.com/en-us/rest/api/storageservices/set-container-metadata
 | |
| func (c *Container) SetMetadata(options *ContainerMetadataOptions) error {
 | |
| 	params := url.Values{
 | |
| 		"comp":    {"metadata"},
 | |
| 		"restype": {"container"},
 | |
| 	}
 | |
| 	headers := c.bsc.client.getStandardHeaders()
 | |
| 	headers = c.bsc.client.addMetadataToHeaders(headers, c.Metadata)
 | |
| 
 | |
| 	if options != nil {
 | |
| 		params = addTimeout(params, options.Timeout)
 | |
| 		headers = mergeHeaders(headers, headersFromStruct(*options))
 | |
| 	}
 | |
| 
 | |
| 	uri := c.bsc.client.getEndpoint(blobServiceName, c.buildPath(), params)
 | |
| 
 | |
| 	resp, err := c.bsc.client.exec(http.MethodPut, uri, headers, nil, c.bsc.auth)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	defer drainRespBody(resp)
 | |
| 	return checkRespCode(resp, []int{http.StatusOK})
 | |
| }
 | |
| 
 | |
| // GetMetadata returns all user-defined metadata for the specified container.
 | |
| //
 | |
| // All metadata keys will be returned in lower case. (HTTP header
 | |
| // names are case-insensitive.)
 | |
| //
 | |
| // See https://docs.microsoft.com/en-us/rest/api/storageservices/get-container-metadata
 | |
| func (c *Container) GetMetadata(options *ContainerMetadataOptions) error {
 | |
| 	params := url.Values{
 | |
| 		"comp":    {"metadata"},
 | |
| 		"restype": {"container"},
 | |
| 	}
 | |
| 	headers := c.bsc.client.getStandardHeaders()
 | |
| 
 | |
| 	if options != nil {
 | |
| 		params = addTimeout(params, options.Timeout)
 | |
| 		headers = mergeHeaders(headers, headersFromStruct(*options))
 | |
| 	}
 | |
| 
 | |
| 	uri := c.bsc.client.getEndpoint(blobServiceName, c.buildPath(), params)
 | |
| 
 | |
| 	resp, err := c.bsc.client.exec(http.MethodGet, uri, headers, nil, c.bsc.auth)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	defer drainRespBody(resp)
 | |
| 	if err := checkRespCode(resp, []int{http.StatusOK}); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	c.writeMetadata(resp.Header)
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (c *Container) writeMetadata(h http.Header) {
 | |
| 	c.Metadata = writeMetadata(h)
 | |
| }
 | |
| 
 | |
| func generateContainerACLpayload(policies []ContainerAccessPolicy) (io.Reader, int, error) {
 | |
| 	sil := SignedIdentifiers{
 | |
| 		SignedIdentifiers: []SignedIdentifier{},
 | |
| 	}
 | |
| 	for _, capd := range policies {
 | |
| 		permission := capd.generateContainerPermissions()
 | |
| 		signedIdentifier := convertAccessPolicyToXMLStructs(capd.ID, capd.StartTime, capd.ExpiryTime, permission)
 | |
| 		sil.SignedIdentifiers = append(sil.SignedIdentifiers, signedIdentifier)
 | |
| 	}
 | |
| 	return xmlMarshal(sil)
 | |
| }
 | |
| 
 | |
| func (capd *ContainerAccessPolicy) generateContainerPermissions() (permissions string) {
 | |
| 	// generate the permissions string (rwd).
 | |
| 	// still want the end user API to have bool flags.
 | |
| 	permissions = ""
 | |
| 
 | |
| 	if capd.CanRead {
 | |
| 		permissions += "r"
 | |
| 	}
 | |
| 
 | |
| 	if capd.CanWrite {
 | |
| 		permissions += "w"
 | |
| 	}
 | |
| 
 | |
| 	if capd.CanDelete {
 | |
| 		permissions += "d"
 | |
| 	}
 | |
| 
 | |
| 	return permissions
 | |
| }
 | |
| 
 | |
| // GetProperties updated the properties of the container.
 | |
| //
 | |
| // See https://docs.microsoft.com/en-us/rest/api/storageservices/get-container-properties
 | |
| func (c *Container) GetProperties() error {
 | |
| 	params := url.Values{
 | |
| 		"restype": {"container"},
 | |
| 	}
 | |
| 	headers := c.bsc.client.getStandardHeaders()
 | |
| 
 | |
| 	uri := c.bsc.client.getEndpoint(blobServiceName, c.buildPath(), params)
 | |
| 
 | |
| 	resp, err := c.bsc.client.exec(http.MethodGet, uri, headers, nil, c.bsc.auth)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	defer resp.Body.Close()
 | |
| 	if err := checkRespCode(resp, []int{http.StatusOK}); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	// update properties
 | |
| 	c.Properties.Etag = resp.Header.Get(headerEtag)
 | |
| 	c.Properties.LeaseStatus = resp.Header.Get("x-ms-lease-status")
 | |
| 	c.Properties.LeaseState = resp.Header.Get("x-ms-lease-state")
 | |
| 	c.Properties.LeaseDuration = resp.Header.Get("x-ms-lease-duration")
 | |
| 	c.Properties.LastModified = resp.Header.Get("Last-Modified")
 | |
| 	c.Properties.PublicAccess = ContainerAccessType(resp.Header.Get(ContainerAccessHeader))
 | |
| 
 | |
| 	return nil
 | |
| }
 |