633 lines
		
	
	
		
			22 KiB
		
	
	
	
		
			Go
		
	
	
			
		
		
	
	
			633 lines
		
	
	
		
			22 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"
 | |
| 	"errors"
 | |
| 	"fmt"
 | |
| 	"io"
 | |
| 	"net/http"
 | |
| 	"net/url"
 | |
| 	"strconv"
 | |
| 	"strings"
 | |
| 	"time"
 | |
| )
 | |
| 
 | |
| // A Blob is an entry in BlobListResponse.
 | |
| type Blob struct {
 | |
| 	Container  *Container
 | |
| 	Name       string         `xml:"Name"`
 | |
| 	Snapshot   time.Time      `xml:"Snapshot"`
 | |
| 	Properties BlobProperties `xml:"Properties"`
 | |
| 	Metadata   BlobMetadata   `xml:"Metadata"`
 | |
| }
 | |
| 
 | |
| // PutBlobOptions includes the options any put blob operation
 | |
| // (page, block, append)
 | |
| type PutBlobOptions struct {
 | |
| 	Timeout           uint
 | |
| 	LeaseID           string     `header:"x-ms-lease-id"`
 | |
| 	Origin            string     `header:"Origin"`
 | |
| 	IfModifiedSince   *time.Time `header:"If-Modified-Since"`
 | |
| 	IfUnmodifiedSince *time.Time `header:"If-Unmodified-Since"`
 | |
| 	IfMatch           string     `header:"If-Match"`
 | |
| 	IfNoneMatch       string     `header:"If-None-Match"`
 | |
| 	RequestID         string     `header:"x-ms-client-request-id"`
 | |
| }
 | |
| 
 | |
| // BlobMetadata is a set of custom name/value pairs.
 | |
| //
 | |
| // See https://msdn.microsoft.com/en-us/library/azure/dd179404.aspx
 | |
| type BlobMetadata map[string]string
 | |
| 
 | |
| type blobMetadataEntries struct {
 | |
| 	Entries []blobMetadataEntry `xml:",any"`
 | |
| }
 | |
| type blobMetadataEntry struct {
 | |
| 	XMLName xml.Name
 | |
| 	Value   string `xml:",chardata"`
 | |
| }
 | |
| 
 | |
| // UnmarshalXML converts the xml:Metadata into Metadata map
 | |
| func (bm *BlobMetadata) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
 | |
| 	var entries blobMetadataEntries
 | |
| 	if err := d.DecodeElement(&entries, &start); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	for _, entry := range entries.Entries {
 | |
| 		if *bm == nil {
 | |
| 			*bm = make(BlobMetadata)
 | |
| 		}
 | |
| 		(*bm)[strings.ToLower(entry.XMLName.Local)] = entry.Value
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // MarshalXML implements the xml.Marshaler interface. It encodes
 | |
| // metadata name/value pairs as they would appear in an Azure
 | |
| // ListBlobs response.
 | |
| func (bm BlobMetadata) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {
 | |
| 	entries := make([]blobMetadataEntry, 0, len(bm))
 | |
| 	for k, v := range bm {
 | |
| 		entries = append(entries, blobMetadataEntry{
 | |
| 			XMLName: xml.Name{Local: http.CanonicalHeaderKey(k)},
 | |
| 			Value:   v,
 | |
| 		})
 | |
| 	}
 | |
| 	return enc.EncodeElement(blobMetadataEntries{
 | |
| 		Entries: entries,
 | |
| 	}, start)
 | |
| }
 | |
| 
 | |
| // BlobProperties contains various properties of a blob
 | |
| // returned in various endpoints like ListBlobs or GetBlobProperties.
 | |
| type BlobProperties struct {
 | |
| 	LastModified          TimeRFC1123 `xml:"Last-Modified"`
 | |
| 	Etag                  string      `xml:"Etag"`
 | |
| 	ContentMD5            string      `xml:"Content-MD5" header:"x-ms-blob-content-md5"`
 | |
| 	ContentLength         int64       `xml:"Content-Length"`
 | |
| 	ContentType           string      `xml:"Content-Type" header:"x-ms-blob-content-type"`
 | |
| 	ContentEncoding       string      `xml:"Content-Encoding" header:"x-ms-blob-content-encoding"`
 | |
| 	CacheControl          string      `xml:"Cache-Control" header:"x-ms-blob-cache-control"`
 | |
| 	ContentLanguage       string      `xml:"Cache-Language" header:"x-ms-blob-content-language"`
 | |
| 	ContentDisposition    string      `xml:"Content-Disposition" header:"x-ms-blob-content-disposition"`
 | |
| 	BlobType              BlobType    `xml:"BlobType"`
 | |
| 	SequenceNumber        int64       `xml:"x-ms-blob-sequence-number"`
 | |
| 	CopyID                string      `xml:"CopyId"`
 | |
| 	CopyStatus            string      `xml:"CopyStatus"`
 | |
| 	CopySource            string      `xml:"CopySource"`
 | |
| 	CopyProgress          string      `xml:"CopyProgress"`
 | |
| 	CopyCompletionTime    TimeRFC1123 `xml:"CopyCompletionTime"`
 | |
| 	CopyStatusDescription string      `xml:"CopyStatusDescription"`
 | |
| 	LeaseStatus           string      `xml:"LeaseStatus"`
 | |
| 	LeaseState            string      `xml:"LeaseState"`
 | |
| 	LeaseDuration         string      `xml:"LeaseDuration"`
 | |
| 	ServerEncrypted       bool        `xml:"ServerEncrypted"`
 | |
| 	IncrementalCopy       bool        `xml:"IncrementalCopy"`
 | |
| }
 | |
| 
 | |
| // BlobType defines the type of the Azure Blob.
 | |
| type BlobType string
 | |
| 
 | |
| // Types of page blobs
 | |
| const (
 | |
| 	BlobTypeBlock  BlobType = "BlockBlob"
 | |
| 	BlobTypePage   BlobType = "PageBlob"
 | |
| 	BlobTypeAppend BlobType = "AppendBlob"
 | |
| )
 | |
| 
 | |
| func (b *Blob) buildPath() string {
 | |
| 	return b.Container.buildPath() + "/" + b.Name
 | |
| }
 | |
| 
 | |
| // Exists returns true if a blob with given name exists on the specified
 | |
| // container of the storage account.
 | |
| func (b *Blob) Exists() (bool, error) {
 | |
| 	uri := b.Container.bsc.client.getEndpoint(blobServiceName, b.buildPath(), nil)
 | |
| 	headers := b.Container.bsc.client.getStandardHeaders()
 | |
| 	resp, err := b.Container.bsc.client.exec(http.MethodHead, uri, headers, nil, b.Container.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
 | |
| }
 | |
| 
 | |
| // GetURL gets the canonical URL to the blob with the specified name in the
 | |
| // specified container.
 | |
| // This method does not create a publicly accessible URL if the blob or container
 | |
| // is private and this method does not check if the blob exists.
 | |
| func (b *Blob) GetURL() string {
 | |
| 	container := b.Container.Name
 | |
| 	if container == "" {
 | |
| 		container = "$root"
 | |
| 	}
 | |
| 	return b.Container.bsc.client.getEndpoint(blobServiceName, pathForResource(container, b.Name), nil)
 | |
| }
 | |
| 
 | |
| // GetBlobRangeOptions includes the options for a get blob range operation
 | |
| type GetBlobRangeOptions struct {
 | |
| 	Range              *BlobRange
 | |
| 	GetRangeContentMD5 bool
 | |
| 	*GetBlobOptions
 | |
| }
 | |
| 
 | |
| // GetBlobOptions includes the options for a get blob operation
 | |
| type GetBlobOptions struct {
 | |
| 	Timeout           uint
 | |
| 	Snapshot          *time.Time
 | |
| 	LeaseID           string     `header:"x-ms-lease-id"`
 | |
| 	Origin            string     `header:"Origin"`
 | |
| 	IfModifiedSince   *time.Time `header:"If-Modified-Since"`
 | |
| 	IfUnmodifiedSince *time.Time `header:"If-Unmodified-Since"`
 | |
| 	IfMatch           string     `header:"If-Match"`
 | |
| 	IfNoneMatch       string     `header:"If-None-Match"`
 | |
| 	RequestID         string     `header:"x-ms-client-request-id"`
 | |
| }
 | |
| 
 | |
| // BlobRange represents the bytes range to be get
 | |
| type BlobRange struct {
 | |
| 	Start uint64
 | |
| 	End   uint64
 | |
| }
 | |
| 
 | |
| func (br BlobRange) String() string {
 | |
| 	if br.End == 0 {
 | |
| 		return fmt.Sprintf("bytes=%d-", br.Start)
 | |
| 	}
 | |
| 	return fmt.Sprintf("bytes=%d-%d", br.Start, br.End)
 | |
| }
 | |
| 
 | |
| // Get returns a stream to read the blob. Caller must call both Read and Close()
 | |
| // to correctly close the underlying connection.
 | |
| //
 | |
| // See the GetRange method for use with a Range header.
 | |
| //
 | |
| // See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/Get-Blob
 | |
| func (b *Blob) Get(options *GetBlobOptions) (io.ReadCloser, error) {
 | |
| 	rangeOptions := GetBlobRangeOptions{
 | |
| 		GetBlobOptions: options,
 | |
| 	}
 | |
| 	resp, err := b.getRange(&rangeOptions)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	if err := checkRespCode(resp, []int{http.StatusOK}); err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	if err := b.writeProperties(resp.Header, true); err != nil {
 | |
| 		return resp.Body, err
 | |
| 	}
 | |
| 	return resp.Body, nil
 | |
| }
 | |
| 
 | |
| // GetRange reads the specified range of a blob to a stream. The bytesRange
 | |
| // string must be in a format like "0-", "10-100" as defined in HTTP 1.1 spec.
 | |
| // Caller must call both Read and Close()// to correctly close the underlying
 | |
| // connection.
 | |
| // See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/Get-Blob
 | |
| func (b *Blob) GetRange(options *GetBlobRangeOptions) (io.ReadCloser, error) {
 | |
| 	resp, err := b.getRange(options)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	if err := checkRespCode(resp, []int{http.StatusPartialContent}); err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	// Content-Length header should not be updated, as the service returns the range length
 | |
| 	// (which is not alwys the full blob length)
 | |
| 	if err := b.writeProperties(resp.Header, false); err != nil {
 | |
| 		return resp.Body, err
 | |
| 	}
 | |
| 	return resp.Body, nil
 | |
| }
 | |
| 
 | |
| func (b *Blob) getRange(options *GetBlobRangeOptions) (*http.Response, error) {
 | |
| 	params := url.Values{}
 | |
| 	headers := b.Container.bsc.client.getStandardHeaders()
 | |
| 
 | |
| 	if options != nil {
 | |
| 		if options.Range != nil {
 | |
| 			headers["Range"] = options.Range.String()
 | |
| 			if options.GetRangeContentMD5 {
 | |
| 				headers["x-ms-range-get-content-md5"] = "true"
 | |
| 			}
 | |
| 		}
 | |
| 		if options.GetBlobOptions != nil {
 | |
| 			headers = mergeHeaders(headers, headersFromStruct(*options.GetBlobOptions))
 | |
| 			params = addTimeout(params, options.Timeout)
 | |
| 			params = addSnapshot(params, options.Snapshot)
 | |
| 		}
 | |
| 	}
 | |
| 	uri := b.Container.bsc.client.getEndpoint(blobServiceName, b.buildPath(), params)
 | |
| 
 | |
| 	resp, err := b.Container.bsc.client.exec(http.MethodGet, uri, headers, nil, b.Container.bsc.auth)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	return resp, err
 | |
| }
 | |
| 
 | |
| // SnapshotOptions includes the options for a snapshot blob operation
 | |
| type SnapshotOptions struct {
 | |
| 	Timeout           uint
 | |
| 	LeaseID           string     `header:"x-ms-lease-id"`
 | |
| 	IfModifiedSince   *time.Time `header:"If-Modified-Since"`
 | |
| 	IfUnmodifiedSince *time.Time `header:"If-Unmodified-Since"`
 | |
| 	IfMatch           string     `header:"If-Match"`
 | |
| 	IfNoneMatch       string     `header:"If-None-Match"`
 | |
| 	RequestID         string     `header:"x-ms-client-request-id"`
 | |
| }
 | |
| 
 | |
| // CreateSnapshot creates a snapshot for a blob
 | |
| // See https://msdn.microsoft.com/en-us/library/azure/ee691971.aspx
 | |
| func (b *Blob) CreateSnapshot(options *SnapshotOptions) (snapshotTimestamp *time.Time, err error) {
 | |
| 	params := url.Values{"comp": {"snapshot"}}
 | |
| 	headers := b.Container.bsc.client.getStandardHeaders()
 | |
| 	headers = b.Container.bsc.client.addMetadataToHeaders(headers, b.Metadata)
 | |
| 
 | |
| 	if options != nil {
 | |
| 		params = addTimeout(params, options.Timeout)
 | |
| 		headers = mergeHeaders(headers, headersFromStruct(*options))
 | |
| 	}
 | |
| 	uri := b.Container.bsc.client.getEndpoint(blobServiceName, b.buildPath(), params)
 | |
| 
 | |
| 	resp, err := b.Container.bsc.client.exec(http.MethodPut, uri, headers, nil, b.Container.bsc.auth)
 | |
| 	if err != nil || resp == nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	defer drainRespBody(resp)
 | |
| 
 | |
| 	if err := checkRespCode(resp, []int{http.StatusCreated}); err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	snapshotResponse := resp.Header.Get(http.CanonicalHeaderKey("x-ms-snapshot"))
 | |
| 	if snapshotResponse != "" {
 | |
| 		snapshotTimestamp, err := time.Parse(time.RFC3339, snapshotResponse)
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 		return &snapshotTimestamp, nil
 | |
| 	}
 | |
| 
 | |
| 	return nil, errors.New("Snapshot not created")
 | |
| }
 | |
| 
 | |
| // GetBlobPropertiesOptions includes the options for a get blob properties operation
 | |
| type GetBlobPropertiesOptions struct {
 | |
| 	Timeout           uint
 | |
| 	Snapshot          *time.Time
 | |
| 	LeaseID           string     `header:"x-ms-lease-id"`
 | |
| 	IfModifiedSince   *time.Time `header:"If-Modified-Since"`
 | |
| 	IfUnmodifiedSince *time.Time `header:"If-Unmodified-Since"`
 | |
| 	IfMatch           string     `header:"If-Match"`
 | |
| 	IfNoneMatch       string     `header:"If-None-Match"`
 | |
| 	RequestID         string     `header:"x-ms-client-request-id"`
 | |
| }
 | |
| 
 | |
| // GetProperties provides various information about the specified blob.
 | |
| // See https://msdn.microsoft.com/en-us/library/azure/dd179394.aspx
 | |
| func (b *Blob) GetProperties(options *GetBlobPropertiesOptions) error {
 | |
| 	params := url.Values{}
 | |
| 	headers := b.Container.bsc.client.getStandardHeaders()
 | |
| 
 | |
| 	if options != nil {
 | |
| 		params = addTimeout(params, options.Timeout)
 | |
| 		params = addSnapshot(params, options.Snapshot)
 | |
| 		headers = mergeHeaders(headers, headersFromStruct(*options))
 | |
| 	}
 | |
| 	uri := b.Container.bsc.client.getEndpoint(blobServiceName, b.buildPath(), params)
 | |
| 
 | |
| 	resp, err := b.Container.bsc.client.exec(http.MethodHead, uri, headers, nil, b.Container.bsc.auth)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	defer drainRespBody(resp)
 | |
| 
 | |
| 	if err = checkRespCode(resp, []int{http.StatusOK}); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	return b.writeProperties(resp.Header, true)
 | |
| }
 | |
| 
 | |
| func (b *Blob) writeProperties(h http.Header, includeContentLen bool) error {
 | |
| 	var err error
 | |
| 
 | |
| 	contentLength := b.Properties.ContentLength
 | |
| 	if includeContentLen {
 | |
| 		contentLengthStr := h.Get("Content-Length")
 | |
| 		if contentLengthStr != "" {
 | |
| 			contentLength, err = strconv.ParseInt(contentLengthStr, 0, 64)
 | |
| 			if err != nil {
 | |
| 				return err
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	var sequenceNum int64
 | |
| 	sequenceNumStr := h.Get("x-ms-blob-sequence-number")
 | |
| 	if sequenceNumStr != "" {
 | |
| 		sequenceNum, err = strconv.ParseInt(sequenceNumStr, 0, 64)
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	lastModified, err := getTimeFromHeaders(h, "Last-Modified")
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	copyCompletionTime, err := getTimeFromHeaders(h, "x-ms-copy-completion-time")
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	b.Properties = BlobProperties{
 | |
| 		LastModified:          TimeRFC1123(*lastModified),
 | |
| 		Etag:                  h.Get("Etag"),
 | |
| 		ContentMD5:            h.Get("Content-MD5"),
 | |
| 		ContentLength:         contentLength,
 | |
| 		ContentEncoding:       h.Get("Content-Encoding"),
 | |
| 		ContentType:           h.Get("Content-Type"),
 | |
| 		ContentDisposition:    h.Get("Content-Disposition"),
 | |
| 		CacheControl:          h.Get("Cache-Control"),
 | |
| 		ContentLanguage:       h.Get("Content-Language"),
 | |
| 		SequenceNumber:        sequenceNum,
 | |
| 		CopyCompletionTime:    TimeRFC1123(*copyCompletionTime),
 | |
| 		CopyStatusDescription: h.Get("x-ms-copy-status-description"),
 | |
| 		CopyID:                h.Get("x-ms-copy-id"),
 | |
| 		CopyProgress:          h.Get("x-ms-copy-progress"),
 | |
| 		CopySource:            h.Get("x-ms-copy-source"),
 | |
| 		CopyStatus:            h.Get("x-ms-copy-status"),
 | |
| 		BlobType:              BlobType(h.Get("x-ms-blob-type")),
 | |
| 		LeaseStatus:           h.Get("x-ms-lease-status"),
 | |
| 		LeaseState:            h.Get("x-ms-lease-state"),
 | |
| 	}
 | |
| 	b.writeMetadata(h)
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // SetBlobPropertiesOptions contains various properties of a blob and is an entry
 | |
| // in SetProperties
 | |
| type SetBlobPropertiesOptions struct {
 | |
| 	Timeout              uint
 | |
| 	LeaseID              string     `header:"x-ms-lease-id"`
 | |
| 	Origin               string     `header:"Origin"`
 | |
| 	IfModifiedSince      *time.Time `header:"If-Modified-Since"`
 | |
| 	IfUnmodifiedSince    *time.Time `header:"If-Unmodified-Since"`
 | |
| 	IfMatch              string     `header:"If-Match"`
 | |
| 	IfNoneMatch          string     `header:"If-None-Match"`
 | |
| 	SequenceNumberAction *SequenceNumberAction
 | |
| 	RequestID            string `header:"x-ms-client-request-id"`
 | |
| }
 | |
| 
 | |
| // SequenceNumberAction defines how the blob's sequence number should be modified
 | |
| type SequenceNumberAction string
 | |
| 
 | |
| // Options for sequence number action
 | |
| const (
 | |
| 	SequenceNumberActionMax       SequenceNumberAction = "max"
 | |
| 	SequenceNumberActionUpdate    SequenceNumberAction = "update"
 | |
| 	SequenceNumberActionIncrement SequenceNumberAction = "increment"
 | |
| )
 | |
| 
 | |
| // SetProperties replaces the BlobHeaders for the specified blob.
 | |
| //
 | |
| // Some keys may be converted to Camel-Case before sending. All keys
 | |
| // are returned in lower case by GetBlobProperties. 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/fileservices/Set-Blob-Properties
 | |
| func (b *Blob) SetProperties(options *SetBlobPropertiesOptions) error {
 | |
| 	params := url.Values{"comp": {"properties"}}
 | |
| 	headers := b.Container.bsc.client.getStandardHeaders()
 | |
| 	headers = mergeHeaders(headers, headersFromStruct(b.Properties))
 | |
| 
 | |
| 	if options != nil {
 | |
| 		params = addTimeout(params, options.Timeout)
 | |
| 		headers = mergeHeaders(headers, headersFromStruct(*options))
 | |
| 	}
 | |
| 	uri := b.Container.bsc.client.getEndpoint(blobServiceName, b.buildPath(), params)
 | |
| 
 | |
| 	if b.Properties.BlobType == BlobTypePage {
 | |
| 		headers = addToHeaders(headers, "x-ms-blob-content-length", fmt.Sprintf("%v", b.Properties.ContentLength))
 | |
| 		if options != nil && options.SequenceNumberAction != nil {
 | |
| 			headers = addToHeaders(headers, "x-ms-sequence-number-action", string(*options.SequenceNumberAction))
 | |
| 			if *options.SequenceNumberAction != SequenceNumberActionIncrement {
 | |
| 				headers = addToHeaders(headers, "x-ms-blob-sequence-number", fmt.Sprintf("%v", b.Properties.SequenceNumber))
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	resp, err := b.Container.bsc.client.exec(http.MethodPut, uri, headers, nil, b.Container.bsc.auth)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	defer drainRespBody(resp)
 | |
| 	return checkRespCode(resp, []int{http.StatusOK})
 | |
| }
 | |
| 
 | |
| // SetBlobMetadataOptions includes the options for a set blob metadata operation
 | |
| type SetBlobMetadataOptions struct {
 | |
| 	Timeout           uint
 | |
| 	LeaseID           string     `header:"x-ms-lease-id"`
 | |
| 	IfModifiedSince   *time.Time `header:"If-Modified-Since"`
 | |
| 	IfUnmodifiedSince *time.Time `header:"If-Unmodified-Since"`
 | |
| 	IfMatch           string     `header:"If-Match"`
 | |
| 	IfNoneMatch       string     `header:"If-None-Match"`
 | |
| 	RequestID         string     `header:"x-ms-client-request-id"`
 | |
| }
 | |
| 
 | |
| // SetMetadata replaces the metadata for the specified blob.
 | |
| //
 | |
| // 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://msdn.microsoft.com/en-us/library/azure/dd179414.aspx
 | |
| func (b *Blob) SetMetadata(options *SetBlobMetadataOptions) error {
 | |
| 	params := url.Values{"comp": {"metadata"}}
 | |
| 	headers := b.Container.bsc.client.getStandardHeaders()
 | |
| 	headers = b.Container.bsc.client.addMetadataToHeaders(headers, b.Metadata)
 | |
| 
 | |
| 	if options != nil {
 | |
| 		params = addTimeout(params, options.Timeout)
 | |
| 		headers = mergeHeaders(headers, headersFromStruct(*options))
 | |
| 	}
 | |
| 	uri := b.Container.bsc.client.getEndpoint(blobServiceName, b.buildPath(), params)
 | |
| 
 | |
| 	resp, err := b.Container.bsc.client.exec(http.MethodPut, uri, headers, nil, b.Container.bsc.auth)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	defer drainRespBody(resp)
 | |
| 	return checkRespCode(resp, []int{http.StatusOK})
 | |
| }
 | |
| 
 | |
| // GetBlobMetadataOptions includes the options for a get blob metadata operation
 | |
| type GetBlobMetadataOptions struct {
 | |
| 	Timeout           uint
 | |
| 	Snapshot          *time.Time
 | |
| 	LeaseID           string     `header:"x-ms-lease-id"`
 | |
| 	IfModifiedSince   *time.Time `header:"If-Modified-Since"`
 | |
| 	IfUnmodifiedSince *time.Time `header:"If-Unmodified-Since"`
 | |
| 	IfMatch           string     `header:"If-Match"`
 | |
| 	IfNoneMatch       string     `header:"If-None-Match"`
 | |
| 	RequestID         string     `header:"x-ms-client-request-id"`
 | |
| }
 | |
| 
 | |
| // GetMetadata returns all user-defined metadata for the specified blob.
 | |
| //
 | |
| // All metadata keys will be returned in lower case. (HTTP header
 | |
| // names are case-insensitive.)
 | |
| //
 | |
| // See https://msdn.microsoft.com/en-us/library/azure/dd179414.aspx
 | |
| func (b *Blob) GetMetadata(options *GetBlobMetadataOptions) error {
 | |
| 	params := url.Values{"comp": {"metadata"}}
 | |
| 	headers := b.Container.bsc.client.getStandardHeaders()
 | |
| 
 | |
| 	if options != nil {
 | |
| 		params = addTimeout(params, options.Timeout)
 | |
| 		params = addSnapshot(params, options.Snapshot)
 | |
| 		headers = mergeHeaders(headers, headersFromStruct(*options))
 | |
| 	}
 | |
| 	uri := b.Container.bsc.client.getEndpoint(blobServiceName, b.buildPath(), params)
 | |
| 
 | |
| 	resp, err := b.Container.bsc.client.exec(http.MethodGet, uri, headers, nil, b.Container.bsc.auth)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	defer drainRespBody(resp)
 | |
| 
 | |
| 	if err := checkRespCode(resp, []int{http.StatusOK}); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	b.writeMetadata(resp.Header)
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (b *Blob) writeMetadata(h http.Header) {
 | |
| 	b.Metadata = BlobMetadata(writeMetadata(h))
 | |
| }
 | |
| 
 | |
| // DeleteBlobOptions includes the options for a delete blob operation
 | |
| type DeleteBlobOptions struct {
 | |
| 	Timeout           uint
 | |
| 	Snapshot          *time.Time
 | |
| 	LeaseID           string `header:"x-ms-lease-id"`
 | |
| 	DeleteSnapshots   *bool
 | |
| 	IfModifiedSince   *time.Time `header:"If-Modified-Since"`
 | |
| 	IfUnmodifiedSince *time.Time `header:"If-Unmodified-Since"`
 | |
| 	IfMatch           string     `header:"If-Match"`
 | |
| 	IfNoneMatch       string     `header:"If-None-Match"`
 | |
| 	RequestID         string     `header:"x-ms-client-request-id"`
 | |
| }
 | |
| 
 | |
| // Delete deletes the given blob from the specified container.
 | |
| // If the blob does not exists at the time of the Delete Blob operation, it
 | |
| // returns error.
 | |
| // See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/Delete-Blob
 | |
| func (b *Blob) Delete(options *DeleteBlobOptions) error {
 | |
| 	resp, err := b.delete(options)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	defer drainRespBody(resp)
 | |
| 	return checkRespCode(resp, []int{http.StatusAccepted})
 | |
| }
 | |
| 
 | |
| // DeleteIfExists deletes the given blob from the specified container If the
 | |
| // blob is deleted with this call, returns true. Otherwise returns false.
 | |
| //
 | |
| // See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/Delete-Blob
 | |
| func (b *Blob) DeleteIfExists(options *DeleteBlobOptions) (bool, error) {
 | |
| 	resp, err := b.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 (b *Blob) delete(options *DeleteBlobOptions) (*http.Response, error) {
 | |
| 	params := url.Values{}
 | |
| 	headers := b.Container.bsc.client.getStandardHeaders()
 | |
| 
 | |
| 	if options != nil {
 | |
| 		params = addTimeout(params, options.Timeout)
 | |
| 		params = addSnapshot(params, options.Snapshot)
 | |
| 		headers = mergeHeaders(headers, headersFromStruct(*options))
 | |
| 		if options.DeleteSnapshots != nil {
 | |
| 			if *options.DeleteSnapshots {
 | |
| 				headers["x-ms-delete-snapshots"] = "include"
 | |
| 			} else {
 | |
| 				headers["x-ms-delete-snapshots"] = "only"
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	uri := b.Container.bsc.client.getEndpoint(blobServiceName, b.buildPath(), params)
 | |
| 	return b.Container.bsc.client.exec(http.MethodDelete, uri, headers, nil, b.Container.bsc.auth)
 | |
| }
 | |
| 
 | |
| // helper method to construct the path to either a blob or container
 | |
| func pathForResource(container, name string) string {
 | |
| 	if name != "" {
 | |
| 		return fmt.Sprintf("/%s/%s", container, name)
 | |
| 	}
 | |
| 	return fmt.Sprintf("/%s", container)
 | |
| }
 | |
| 
 | |
| func (b *Blob) respondCreation(resp *http.Response, bt BlobType) error {
 | |
| 	defer drainRespBody(resp)
 | |
| 	err := checkRespCode(resp, []int{http.StatusCreated})
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	b.Properties.BlobType = bt
 | |
| 	return nil
 | |
| }
 |