blob.go 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633
  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. "errors"
  18. "fmt"
  19. "io"
  20. "net/http"
  21. "net/url"
  22. "strconv"
  23. "strings"
  24. "time"
  25. )
  26. // A Blob is an entry in BlobListResponse.
  27. type Blob struct {
  28. Container *Container
  29. Name string `xml:"Name"`
  30. Snapshot time.Time `xml:"Snapshot"`
  31. Properties BlobProperties `xml:"Properties"`
  32. Metadata BlobMetadata `xml:"Metadata"`
  33. }
  34. // PutBlobOptions includes the options any put blob operation
  35. // (page, block, append)
  36. type PutBlobOptions struct {
  37. Timeout uint
  38. LeaseID string `header:"x-ms-lease-id"`
  39. Origin string `header:"Origin"`
  40. IfModifiedSince *time.Time `header:"If-Modified-Since"`
  41. IfUnmodifiedSince *time.Time `header:"If-Unmodified-Since"`
  42. IfMatch string `header:"If-Match"`
  43. IfNoneMatch string `header:"If-None-Match"`
  44. RequestID string `header:"x-ms-client-request-id"`
  45. }
  46. // BlobMetadata is a set of custom name/value pairs.
  47. //
  48. // See https://msdn.microsoft.com/en-us/library/azure/dd179404.aspx
  49. type BlobMetadata map[string]string
  50. type blobMetadataEntries struct {
  51. Entries []blobMetadataEntry `xml:",any"`
  52. }
  53. type blobMetadataEntry struct {
  54. XMLName xml.Name
  55. Value string `xml:",chardata"`
  56. }
  57. // UnmarshalXML converts the xml:Metadata into Metadata map
  58. func (bm *BlobMetadata) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
  59. var entries blobMetadataEntries
  60. if err := d.DecodeElement(&entries, &start); err != nil {
  61. return err
  62. }
  63. for _, entry := range entries.Entries {
  64. if *bm == nil {
  65. *bm = make(BlobMetadata)
  66. }
  67. (*bm)[strings.ToLower(entry.XMLName.Local)] = entry.Value
  68. }
  69. return nil
  70. }
  71. // MarshalXML implements the xml.Marshaler interface. It encodes
  72. // metadata name/value pairs as they would appear in an Azure
  73. // ListBlobs response.
  74. func (bm BlobMetadata) MarshalXML(enc *xml.Encoder, start xml.StartElement) error {
  75. entries := make([]blobMetadataEntry, 0, len(bm))
  76. for k, v := range bm {
  77. entries = append(entries, blobMetadataEntry{
  78. XMLName: xml.Name{Local: http.CanonicalHeaderKey(k)},
  79. Value: v,
  80. })
  81. }
  82. return enc.EncodeElement(blobMetadataEntries{
  83. Entries: entries,
  84. }, start)
  85. }
  86. // BlobProperties contains various properties of a blob
  87. // returned in various endpoints like ListBlobs or GetBlobProperties.
  88. type BlobProperties struct {
  89. LastModified TimeRFC1123 `xml:"Last-Modified"`
  90. Etag string `xml:"Etag"`
  91. ContentMD5 string `xml:"Content-MD5" header:"x-ms-blob-content-md5"`
  92. ContentLength int64 `xml:"Content-Length"`
  93. ContentType string `xml:"Content-Type" header:"x-ms-blob-content-type"`
  94. ContentEncoding string `xml:"Content-Encoding" header:"x-ms-blob-content-encoding"`
  95. CacheControl string `xml:"Cache-Control" header:"x-ms-blob-cache-control"`
  96. ContentLanguage string `xml:"Cache-Language" header:"x-ms-blob-content-language"`
  97. ContentDisposition string `xml:"Content-Disposition" header:"x-ms-blob-content-disposition"`
  98. BlobType BlobType `xml:"BlobType"`
  99. SequenceNumber int64 `xml:"x-ms-blob-sequence-number"`
  100. CopyID string `xml:"CopyId"`
  101. CopyStatus string `xml:"CopyStatus"`
  102. CopySource string `xml:"CopySource"`
  103. CopyProgress string `xml:"CopyProgress"`
  104. CopyCompletionTime TimeRFC1123 `xml:"CopyCompletionTime"`
  105. CopyStatusDescription string `xml:"CopyStatusDescription"`
  106. LeaseStatus string `xml:"LeaseStatus"`
  107. LeaseState string `xml:"LeaseState"`
  108. LeaseDuration string `xml:"LeaseDuration"`
  109. ServerEncrypted bool `xml:"ServerEncrypted"`
  110. IncrementalCopy bool `xml:"IncrementalCopy"`
  111. }
  112. // BlobType defines the type of the Azure Blob.
  113. type BlobType string
  114. // Types of page blobs
  115. const (
  116. BlobTypeBlock BlobType = "BlockBlob"
  117. BlobTypePage BlobType = "PageBlob"
  118. BlobTypeAppend BlobType = "AppendBlob"
  119. )
  120. func (b *Blob) buildPath() string {
  121. return b.Container.buildPath() + "/" + b.Name
  122. }
  123. // Exists returns true if a blob with given name exists on the specified
  124. // container of the storage account.
  125. func (b *Blob) Exists() (bool, error) {
  126. uri := b.Container.bsc.client.getEndpoint(blobServiceName, b.buildPath(), nil)
  127. headers := b.Container.bsc.client.getStandardHeaders()
  128. resp, err := b.Container.bsc.client.exec(http.MethodHead, uri, headers, nil, b.Container.bsc.auth)
  129. if resp != nil {
  130. defer drainRespBody(resp)
  131. if resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusNotFound {
  132. return resp.StatusCode == http.StatusOK, nil
  133. }
  134. }
  135. return false, err
  136. }
  137. // GetURL gets the canonical URL to the blob with the specified name in the
  138. // specified container.
  139. // This method does not create a publicly accessible URL if the blob or container
  140. // is private and this method does not check if the blob exists.
  141. func (b *Blob) GetURL() string {
  142. container := b.Container.Name
  143. if container == "" {
  144. container = "$root"
  145. }
  146. return b.Container.bsc.client.getEndpoint(blobServiceName, pathForResource(container, b.Name), nil)
  147. }
  148. // GetBlobRangeOptions includes the options for a get blob range operation
  149. type GetBlobRangeOptions struct {
  150. Range *BlobRange
  151. GetRangeContentMD5 bool
  152. *GetBlobOptions
  153. }
  154. // GetBlobOptions includes the options for a get blob operation
  155. type GetBlobOptions struct {
  156. Timeout uint
  157. Snapshot *time.Time
  158. LeaseID string `header:"x-ms-lease-id"`
  159. Origin string `header:"Origin"`
  160. IfModifiedSince *time.Time `header:"If-Modified-Since"`
  161. IfUnmodifiedSince *time.Time `header:"If-Unmodified-Since"`
  162. IfMatch string `header:"If-Match"`
  163. IfNoneMatch string `header:"If-None-Match"`
  164. RequestID string `header:"x-ms-client-request-id"`
  165. }
  166. // BlobRange represents the bytes range to be get
  167. type BlobRange struct {
  168. Start uint64
  169. End uint64
  170. }
  171. func (br BlobRange) String() string {
  172. if br.End == 0 {
  173. return fmt.Sprintf("bytes=%d-", br.Start)
  174. }
  175. return fmt.Sprintf("bytes=%d-%d", br.Start, br.End)
  176. }
  177. // Get returns a stream to read the blob. Caller must call both Read and Close()
  178. // to correctly close the underlying connection.
  179. //
  180. // See the GetRange method for use with a Range header.
  181. //
  182. // See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/Get-Blob
  183. func (b *Blob) Get(options *GetBlobOptions) (io.ReadCloser, error) {
  184. rangeOptions := GetBlobRangeOptions{
  185. GetBlobOptions: options,
  186. }
  187. resp, err := b.getRange(&rangeOptions)
  188. if err != nil {
  189. return nil, err
  190. }
  191. if err := checkRespCode(resp, []int{http.StatusOK}); err != nil {
  192. return nil, err
  193. }
  194. if err := b.writeProperties(resp.Header, true); err != nil {
  195. return resp.Body, err
  196. }
  197. return resp.Body, nil
  198. }
  199. // GetRange reads the specified range of a blob to a stream. The bytesRange
  200. // string must be in a format like "0-", "10-100" as defined in HTTP 1.1 spec.
  201. // Caller must call both Read and Close()// to correctly close the underlying
  202. // connection.
  203. // See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/Get-Blob
  204. func (b *Blob) GetRange(options *GetBlobRangeOptions) (io.ReadCloser, error) {
  205. resp, err := b.getRange(options)
  206. if err != nil {
  207. return nil, err
  208. }
  209. if err := checkRespCode(resp, []int{http.StatusPartialContent}); err != nil {
  210. return nil, err
  211. }
  212. // Content-Length header should not be updated, as the service returns the range length
  213. // (which is not alwys the full blob length)
  214. if err := b.writeProperties(resp.Header, false); err != nil {
  215. return resp.Body, err
  216. }
  217. return resp.Body, nil
  218. }
  219. func (b *Blob) getRange(options *GetBlobRangeOptions) (*http.Response, error) {
  220. params := url.Values{}
  221. headers := b.Container.bsc.client.getStandardHeaders()
  222. if options != nil {
  223. if options.Range != nil {
  224. headers["Range"] = options.Range.String()
  225. if options.GetRangeContentMD5 {
  226. headers["x-ms-range-get-content-md5"] = "true"
  227. }
  228. }
  229. if options.GetBlobOptions != nil {
  230. headers = mergeHeaders(headers, headersFromStruct(*options.GetBlobOptions))
  231. params = addTimeout(params, options.Timeout)
  232. params = addSnapshot(params, options.Snapshot)
  233. }
  234. }
  235. uri := b.Container.bsc.client.getEndpoint(blobServiceName, b.buildPath(), params)
  236. resp, err := b.Container.bsc.client.exec(http.MethodGet, uri, headers, nil, b.Container.bsc.auth)
  237. if err != nil {
  238. return nil, err
  239. }
  240. return resp, err
  241. }
  242. // SnapshotOptions includes the options for a snapshot blob operation
  243. type SnapshotOptions struct {
  244. Timeout uint
  245. LeaseID string `header:"x-ms-lease-id"`
  246. IfModifiedSince *time.Time `header:"If-Modified-Since"`
  247. IfUnmodifiedSince *time.Time `header:"If-Unmodified-Since"`
  248. IfMatch string `header:"If-Match"`
  249. IfNoneMatch string `header:"If-None-Match"`
  250. RequestID string `header:"x-ms-client-request-id"`
  251. }
  252. // CreateSnapshot creates a snapshot for a blob
  253. // See https://msdn.microsoft.com/en-us/library/azure/ee691971.aspx
  254. func (b *Blob) CreateSnapshot(options *SnapshotOptions) (snapshotTimestamp *time.Time, err error) {
  255. params := url.Values{"comp": {"snapshot"}}
  256. headers := b.Container.bsc.client.getStandardHeaders()
  257. headers = b.Container.bsc.client.addMetadataToHeaders(headers, b.Metadata)
  258. if options != nil {
  259. params = addTimeout(params, options.Timeout)
  260. headers = mergeHeaders(headers, headersFromStruct(*options))
  261. }
  262. uri := b.Container.bsc.client.getEndpoint(blobServiceName, b.buildPath(), params)
  263. resp, err := b.Container.bsc.client.exec(http.MethodPut, uri, headers, nil, b.Container.bsc.auth)
  264. if err != nil || resp == nil {
  265. return nil, err
  266. }
  267. defer drainRespBody(resp)
  268. if err := checkRespCode(resp, []int{http.StatusCreated}); err != nil {
  269. return nil, err
  270. }
  271. snapshotResponse := resp.Header.Get(http.CanonicalHeaderKey("x-ms-snapshot"))
  272. if snapshotResponse != "" {
  273. snapshotTimestamp, err := time.Parse(time.RFC3339, snapshotResponse)
  274. if err != nil {
  275. return nil, err
  276. }
  277. return &snapshotTimestamp, nil
  278. }
  279. return nil, errors.New("Snapshot not created")
  280. }
  281. // GetBlobPropertiesOptions includes the options for a get blob properties operation
  282. type GetBlobPropertiesOptions struct {
  283. Timeout uint
  284. Snapshot *time.Time
  285. LeaseID string `header:"x-ms-lease-id"`
  286. IfModifiedSince *time.Time `header:"If-Modified-Since"`
  287. IfUnmodifiedSince *time.Time `header:"If-Unmodified-Since"`
  288. IfMatch string `header:"If-Match"`
  289. IfNoneMatch string `header:"If-None-Match"`
  290. RequestID string `header:"x-ms-client-request-id"`
  291. }
  292. // GetProperties provides various information about the specified blob.
  293. // See https://msdn.microsoft.com/en-us/library/azure/dd179394.aspx
  294. func (b *Blob) GetProperties(options *GetBlobPropertiesOptions) error {
  295. params := url.Values{}
  296. headers := b.Container.bsc.client.getStandardHeaders()
  297. if options != nil {
  298. params = addTimeout(params, options.Timeout)
  299. params = addSnapshot(params, options.Snapshot)
  300. headers = mergeHeaders(headers, headersFromStruct(*options))
  301. }
  302. uri := b.Container.bsc.client.getEndpoint(blobServiceName, b.buildPath(), params)
  303. resp, err := b.Container.bsc.client.exec(http.MethodHead, uri, headers, nil, b.Container.bsc.auth)
  304. if err != nil {
  305. return err
  306. }
  307. defer drainRespBody(resp)
  308. if err = checkRespCode(resp, []int{http.StatusOK}); err != nil {
  309. return err
  310. }
  311. return b.writeProperties(resp.Header, true)
  312. }
  313. func (b *Blob) writeProperties(h http.Header, includeContentLen bool) error {
  314. var err error
  315. contentLength := b.Properties.ContentLength
  316. if includeContentLen {
  317. contentLengthStr := h.Get("Content-Length")
  318. if contentLengthStr != "" {
  319. contentLength, err = strconv.ParseInt(contentLengthStr, 0, 64)
  320. if err != nil {
  321. return err
  322. }
  323. }
  324. }
  325. var sequenceNum int64
  326. sequenceNumStr := h.Get("x-ms-blob-sequence-number")
  327. if sequenceNumStr != "" {
  328. sequenceNum, err = strconv.ParseInt(sequenceNumStr, 0, 64)
  329. if err != nil {
  330. return err
  331. }
  332. }
  333. lastModified, err := getTimeFromHeaders(h, "Last-Modified")
  334. if err != nil {
  335. return err
  336. }
  337. copyCompletionTime, err := getTimeFromHeaders(h, "x-ms-copy-completion-time")
  338. if err != nil {
  339. return err
  340. }
  341. b.Properties = BlobProperties{
  342. LastModified: TimeRFC1123(*lastModified),
  343. Etag: h.Get("Etag"),
  344. ContentMD5: h.Get("Content-MD5"),
  345. ContentLength: contentLength,
  346. ContentEncoding: h.Get("Content-Encoding"),
  347. ContentType: h.Get("Content-Type"),
  348. ContentDisposition: h.Get("Content-Disposition"),
  349. CacheControl: h.Get("Cache-Control"),
  350. ContentLanguage: h.Get("Content-Language"),
  351. SequenceNumber: sequenceNum,
  352. CopyCompletionTime: TimeRFC1123(*copyCompletionTime),
  353. CopyStatusDescription: h.Get("x-ms-copy-status-description"),
  354. CopyID: h.Get("x-ms-copy-id"),
  355. CopyProgress: h.Get("x-ms-copy-progress"),
  356. CopySource: h.Get("x-ms-copy-source"),
  357. CopyStatus: h.Get("x-ms-copy-status"),
  358. BlobType: BlobType(h.Get("x-ms-blob-type")),
  359. LeaseStatus: h.Get("x-ms-lease-status"),
  360. LeaseState: h.Get("x-ms-lease-state"),
  361. }
  362. b.writeMetadata(h)
  363. return nil
  364. }
  365. // SetBlobPropertiesOptions contains various properties of a blob and is an entry
  366. // in SetProperties
  367. type SetBlobPropertiesOptions struct {
  368. Timeout uint
  369. LeaseID string `header:"x-ms-lease-id"`
  370. Origin string `header:"Origin"`
  371. IfModifiedSince *time.Time `header:"If-Modified-Since"`
  372. IfUnmodifiedSince *time.Time `header:"If-Unmodified-Since"`
  373. IfMatch string `header:"If-Match"`
  374. IfNoneMatch string `header:"If-None-Match"`
  375. SequenceNumberAction *SequenceNumberAction
  376. RequestID string `header:"x-ms-client-request-id"`
  377. }
  378. // SequenceNumberAction defines how the blob's sequence number should be modified
  379. type SequenceNumberAction string
  380. // Options for sequence number action
  381. const (
  382. SequenceNumberActionMax SequenceNumberAction = "max"
  383. SequenceNumberActionUpdate SequenceNumberAction = "update"
  384. SequenceNumberActionIncrement SequenceNumberAction = "increment"
  385. )
  386. // SetProperties replaces the BlobHeaders for the specified blob.
  387. //
  388. // Some keys may be converted to Camel-Case before sending. All keys
  389. // are returned in lower case by GetBlobProperties. HTTP header names
  390. // are case-insensitive so case munging should not matter to other
  391. // applications either.
  392. //
  393. // See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/Set-Blob-Properties
  394. func (b *Blob) SetProperties(options *SetBlobPropertiesOptions) error {
  395. params := url.Values{"comp": {"properties"}}
  396. headers := b.Container.bsc.client.getStandardHeaders()
  397. headers = mergeHeaders(headers, headersFromStruct(b.Properties))
  398. if options != nil {
  399. params = addTimeout(params, options.Timeout)
  400. headers = mergeHeaders(headers, headersFromStruct(*options))
  401. }
  402. uri := b.Container.bsc.client.getEndpoint(blobServiceName, b.buildPath(), params)
  403. if b.Properties.BlobType == BlobTypePage {
  404. headers = addToHeaders(headers, "x-ms-blob-content-length", fmt.Sprintf("%v", b.Properties.ContentLength))
  405. if options != nil && options.SequenceNumberAction != nil {
  406. headers = addToHeaders(headers, "x-ms-sequence-number-action", string(*options.SequenceNumberAction))
  407. if *options.SequenceNumberAction != SequenceNumberActionIncrement {
  408. headers = addToHeaders(headers, "x-ms-blob-sequence-number", fmt.Sprintf("%v", b.Properties.SequenceNumber))
  409. }
  410. }
  411. }
  412. resp, err := b.Container.bsc.client.exec(http.MethodPut, uri, headers, nil, b.Container.bsc.auth)
  413. if err != nil {
  414. return err
  415. }
  416. defer drainRespBody(resp)
  417. return checkRespCode(resp, []int{http.StatusOK})
  418. }
  419. // SetBlobMetadataOptions includes the options for a set blob metadata operation
  420. type SetBlobMetadataOptions struct {
  421. Timeout uint
  422. LeaseID string `header:"x-ms-lease-id"`
  423. IfModifiedSince *time.Time `header:"If-Modified-Since"`
  424. IfUnmodifiedSince *time.Time `header:"If-Unmodified-Since"`
  425. IfMatch string `header:"If-Match"`
  426. IfNoneMatch string `header:"If-None-Match"`
  427. RequestID string `header:"x-ms-client-request-id"`
  428. }
  429. // SetMetadata replaces the metadata for the specified blob.
  430. //
  431. // Some keys may be converted to Camel-Case before sending. All keys
  432. // are returned in lower case by GetBlobMetadata. HTTP header names
  433. // are case-insensitive so case munging should not matter to other
  434. // applications either.
  435. //
  436. // See https://msdn.microsoft.com/en-us/library/azure/dd179414.aspx
  437. func (b *Blob) SetMetadata(options *SetBlobMetadataOptions) error {
  438. params := url.Values{"comp": {"metadata"}}
  439. headers := b.Container.bsc.client.getStandardHeaders()
  440. headers = b.Container.bsc.client.addMetadataToHeaders(headers, b.Metadata)
  441. if options != nil {
  442. params = addTimeout(params, options.Timeout)
  443. headers = mergeHeaders(headers, headersFromStruct(*options))
  444. }
  445. uri := b.Container.bsc.client.getEndpoint(blobServiceName, b.buildPath(), params)
  446. resp, err := b.Container.bsc.client.exec(http.MethodPut, uri, headers, nil, b.Container.bsc.auth)
  447. if err != nil {
  448. return err
  449. }
  450. defer drainRespBody(resp)
  451. return checkRespCode(resp, []int{http.StatusOK})
  452. }
  453. // GetBlobMetadataOptions includes the options for a get blob metadata operation
  454. type GetBlobMetadataOptions struct {
  455. Timeout uint
  456. Snapshot *time.Time
  457. LeaseID string `header:"x-ms-lease-id"`
  458. IfModifiedSince *time.Time `header:"If-Modified-Since"`
  459. IfUnmodifiedSince *time.Time `header:"If-Unmodified-Since"`
  460. IfMatch string `header:"If-Match"`
  461. IfNoneMatch string `header:"If-None-Match"`
  462. RequestID string `header:"x-ms-client-request-id"`
  463. }
  464. // GetMetadata returns all user-defined metadata for the specified blob.
  465. //
  466. // All metadata keys will be returned in lower case. (HTTP header
  467. // names are case-insensitive.)
  468. //
  469. // See https://msdn.microsoft.com/en-us/library/azure/dd179414.aspx
  470. func (b *Blob) GetMetadata(options *GetBlobMetadataOptions) error {
  471. params := url.Values{"comp": {"metadata"}}
  472. headers := b.Container.bsc.client.getStandardHeaders()
  473. if options != nil {
  474. params = addTimeout(params, options.Timeout)
  475. params = addSnapshot(params, options.Snapshot)
  476. headers = mergeHeaders(headers, headersFromStruct(*options))
  477. }
  478. uri := b.Container.bsc.client.getEndpoint(blobServiceName, b.buildPath(), params)
  479. resp, err := b.Container.bsc.client.exec(http.MethodGet, uri, headers, nil, b.Container.bsc.auth)
  480. if err != nil {
  481. return err
  482. }
  483. defer drainRespBody(resp)
  484. if err := checkRespCode(resp, []int{http.StatusOK}); err != nil {
  485. return err
  486. }
  487. b.writeMetadata(resp.Header)
  488. return nil
  489. }
  490. func (b *Blob) writeMetadata(h http.Header) {
  491. b.Metadata = BlobMetadata(writeMetadata(h))
  492. }
  493. // DeleteBlobOptions includes the options for a delete blob operation
  494. type DeleteBlobOptions struct {
  495. Timeout uint
  496. Snapshot *time.Time
  497. LeaseID string `header:"x-ms-lease-id"`
  498. DeleteSnapshots *bool
  499. IfModifiedSince *time.Time `header:"If-Modified-Since"`
  500. IfUnmodifiedSince *time.Time `header:"If-Unmodified-Since"`
  501. IfMatch string `header:"If-Match"`
  502. IfNoneMatch string `header:"If-None-Match"`
  503. RequestID string `header:"x-ms-client-request-id"`
  504. }
  505. // Delete deletes the given blob from the specified container.
  506. // If the blob does not exists at the time of the Delete Blob operation, it
  507. // returns error.
  508. // See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/Delete-Blob
  509. func (b *Blob) Delete(options *DeleteBlobOptions) error {
  510. resp, err := b.delete(options)
  511. if err != nil {
  512. return err
  513. }
  514. defer drainRespBody(resp)
  515. return checkRespCode(resp, []int{http.StatusAccepted})
  516. }
  517. // DeleteIfExists deletes the given blob from the specified container If the
  518. // blob is deleted with this call, returns true. Otherwise returns false.
  519. //
  520. // See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/Delete-Blob
  521. func (b *Blob) DeleteIfExists(options *DeleteBlobOptions) (bool, error) {
  522. resp, err := b.delete(options)
  523. if resp != nil {
  524. defer drainRespBody(resp)
  525. if resp.StatusCode == http.StatusAccepted || resp.StatusCode == http.StatusNotFound {
  526. return resp.StatusCode == http.StatusAccepted, nil
  527. }
  528. }
  529. return false, err
  530. }
  531. func (b *Blob) delete(options *DeleteBlobOptions) (*http.Response, error) {
  532. params := url.Values{}
  533. headers := b.Container.bsc.client.getStandardHeaders()
  534. if options != nil {
  535. params = addTimeout(params, options.Timeout)
  536. params = addSnapshot(params, options.Snapshot)
  537. headers = mergeHeaders(headers, headersFromStruct(*options))
  538. if options.DeleteSnapshots != nil {
  539. if *options.DeleteSnapshots {
  540. headers["x-ms-delete-snapshots"] = "include"
  541. } else {
  542. headers["x-ms-delete-snapshots"] = "only"
  543. }
  544. }
  545. }
  546. uri := b.Container.bsc.client.getEndpoint(blobServiceName, b.buildPath(), params)
  547. return b.Container.bsc.client.exec(http.MethodDelete, uri, headers, nil, b.Container.bsc.auth)
  548. }
  549. // helper method to construct the path to either a blob or container
  550. func pathForResource(container, name string) string {
  551. if name != "" {
  552. return fmt.Sprintf("/%s/%s", container, name)
  553. }
  554. return fmt.Sprintf("/%s", container)
  555. }
  556. func (b *Blob) respondCreation(resp *http.Response, bt BlobType) error {
  557. defer drainRespBody(resp)
  558. err := checkRespCode(resp, []int{http.StatusCreated})
  559. if err != nil {
  560. return err
  561. }
  562. b.Properties.BlobType = bt
  563. return nil
  564. }