123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633 |
- 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
- }
|