3e68d47da6
Signed-off-by: David Justice <david@devigned.com>
300 lines
10 KiB
Go
300 lines
10 KiB
Go
package storage
|
|
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
// Licensed under the MIT License. See License.txt in the project root for license information.
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/xml"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/url"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// BlockListType is used to filter out types of blocks in a Get Blocks List call
|
|
// for a block blob.
|
|
//
|
|
// See https://msdn.microsoft.com/en-us/library/azure/dd179400.aspx for all
|
|
// block types.
|
|
type BlockListType string
|
|
|
|
// Filters for listing blocks in block blobs
|
|
const (
|
|
BlockListTypeAll BlockListType = "all"
|
|
BlockListTypeCommitted BlockListType = "committed"
|
|
BlockListTypeUncommitted BlockListType = "uncommitted"
|
|
)
|
|
|
|
// Maximum sizes (per REST API) for various concepts
|
|
const (
|
|
MaxBlobBlockSize = 100 * 1024 * 1024
|
|
MaxBlobPageSize = 4 * 1024 * 1024
|
|
)
|
|
|
|
// BlockStatus defines states a block for a block blob can
|
|
// be in.
|
|
type BlockStatus string
|
|
|
|
// List of statuses that can be used to refer to a block in a block list
|
|
const (
|
|
BlockStatusUncommitted BlockStatus = "Uncommitted"
|
|
BlockStatusCommitted BlockStatus = "Committed"
|
|
BlockStatusLatest BlockStatus = "Latest"
|
|
)
|
|
|
|
// Block is used to create Block entities for Put Block List
|
|
// call.
|
|
type Block struct {
|
|
ID string
|
|
Status BlockStatus
|
|
}
|
|
|
|
// BlockListResponse contains the response fields from Get Block List call.
|
|
//
|
|
// See https://msdn.microsoft.com/en-us/library/azure/dd179400.aspx
|
|
type BlockListResponse struct {
|
|
XMLName xml.Name `xml:"BlockList"`
|
|
CommittedBlocks []BlockResponse `xml:"CommittedBlocks>Block"`
|
|
UncommittedBlocks []BlockResponse `xml:"UncommittedBlocks>Block"`
|
|
}
|
|
|
|
// BlockResponse contains the block information returned
|
|
// in the GetBlockListCall.
|
|
type BlockResponse struct {
|
|
Name string `xml:"Name"`
|
|
Size int64 `xml:"Size"`
|
|
}
|
|
|
|
// CreateBlockBlob initializes an empty block blob with no blocks.
|
|
//
|
|
// See CreateBlockBlobFromReader for more info on creating blobs.
|
|
//
|
|
// See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/Put-Blob
|
|
func (b *Blob) CreateBlockBlob(options *PutBlobOptions) error {
|
|
return b.CreateBlockBlobFromReader(nil, options)
|
|
}
|
|
|
|
// CreateBlockBlobFromReader initializes a block blob using data from
|
|
// reader. Size must be the number of bytes read from reader. To
|
|
// create an empty blob, use size==0 and reader==nil.
|
|
//
|
|
// Any headers set in blob.Properties or metadata in blob.Metadata
|
|
// will be set on the blob.
|
|
//
|
|
// The API rejects requests with size > 256 MiB (but this limit is not
|
|
// checked by the SDK). To write a larger blob, use CreateBlockBlob,
|
|
// PutBlock, and PutBlockList.
|
|
//
|
|
// To create a blob from scratch, call container.GetBlobReference() to
|
|
// get an empty blob, fill in blob.Properties and blob.Metadata as
|
|
// appropriate then call this method.
|
|
//
|
|
// See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/Put-Blob
|
|
func (b *Blob) CreateBlockBlobFromReader(blob io.Reader, options *PutBlobOptions) error {
|
|
params := url.Values{}
|
|
headers := b.Container.bsc.client.getStandardHeaders()
|
|
headers["x-ms-blob-type"] = string(BlobTypeBlock)
|
|
|
|
headers["Content-Length"] = "0"
|
|
var n int64
|
|
var err error
|
|
if blob != nil {
|
|
type lener interface {
|
|
Len() int
|
|
}
|
|
// TODO(rjeczalik): handle io.ReadSeeker, in case blob is *os.File etc.
|
|
if l, ok := blob.(lener); ok {
|
|
n = int64(l.Len())
|
|
} else {
|
|
var buf bytes.Buffer
|
|
n, err = io.Copy(&buf, blob)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
blob = &buf
|
|
}
|
|
|
|
headers["Content-Length"] = strconv.FormatInt(n, 10)
|
|
}
|
|
b.Properties.ContentLength = n
|
|
|
|
headers = mergeHeaders(headers, headersFromStruct(b.Properties))
|
|
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, blob, b.Container.bsc.auth)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return b.respondCreation(resp, BlobTypeBlock)
|
|
}
|
|
|
|
// PutBlockOptions includes the options for a put block operation
|
|
type PutBlockOptions struct {
|
|
Timeout uint
|
|
LeaseID string `header:"x-ms-lease-id"`
|
|
ContentMD5 string `header:"Content-MD5"`
|
|
RequestID string `header:"x-ms-client-request-id"`
|
|
}
|
|
|
|
// PutBlock saves the given data chunk to the specified block blob with
|
|
// given ID.
|
|
//
|
|
// The API rejects chunks larger than 100 MiB (but this limit is not
|
|
// checked by the SDK).
|
|
//
|
|
// See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/Put-Block
|
|
func (b *Blob) PutBlock(blockID string, chunk []byte, options *PutBlockOptions) error {
|
|
return b.PutBlockWithLength(blockID, uint64(len(chunk)), bytes.NewReader(chunk), options)
|
|
}
|
|
|
|
// PutBlockWithLength saves the given data stream of exactly specified size to
|
|
// the block blob with given ID. It is an alternative to PutBlocks where data
|
|
// comes as stream but the length is known in advance.
|
|
//
|
|
// The API rejects requests with size > 100 MiB (but this limit is not
|
|
// checked by the SDK).
|
|
//
|
|
// See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/Put-Block
|
|
func (b *Blob) PutBlockWithLength(blockID string, size uint64, blob io.Reader, options *PutBlockOptions) error {
|
|
query := url.Values{
|
|
"comp": {"block"},
|
|
"blockid": {blockID},
|
|
}
|
|
headers := b.Container.bsc.client.getStandardHeaders()
|
|
headers["Content-Length"] = fmt.Sprintf("%v", size)
|
|
|
|
if options != nil {
|
|
query = addTimeout(query, options.Timeout)
|
|
headers = mergeHeaders(headers, headersFromStruct(*options))
|
|
}
|
|
uri := b.Container.bsc.client.getEndpoint(blobServiceName, b.buildPath(), query)
|
|
|
|
resp, err := b.Container.bsc.client.exec(http.MethodPut, uri, headers, blob, b.Container.bsc.auth)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return b.respondCreation(resp, BlobTypeBlock)
|
|
}
|
|
|
|
// PutBlockFromURLOptions includes the options for a put block from URL operation
|
|
type PutBlockFromURLOptions struct {
|
|
PutBlockOptions
|
|
|
|
SourceContentMD5 string `header:"x-ms-source-content-md5"`
|
|
SourceContentCRC64 string `header:"x-ms-source-content-crc64"`
|
|
}
|
|
|
|
// PutBlockFromURL copy data of exactly specified size from specified URL to
|
|
// the block blob with given ID. It is an alternative to PutBlocks where data
|
|
// comes from a remote URL and the offset and length is known in advance.
|
|
//
|
|
// The API rejects requests with size > 100 MiB (but this limit is not
|
|
// checked by the SDK).
|
|
//
|
|
// See https://docs.microsoft.com/en-us/rest/api/storageservices/put-block-from-url
|
|
func (b *Blob) PutBlockFromURL(blockID string, blobURL string, offset int64, size uint64, options *PutBlockFromURLOptions) error {
|
|
query := url.Values{
|
|
"comp": {"block"},
|
|
"blockid": {blockID},
|
|
}
|
|
headers := b.Container.bsc.client.getStandardHeaders()
|
|
// The value of this header must be set to zero.
|
|
// When the length is not zero, the operation will fail with the status code 400 (Bad Request).
|
|
headers["Content-Length"] = "0"
|
|
headers["x-ms-copy-source"] = blobURL
|
|
headers["x-ms-source-range"] = fmt.Sprintf("bytes=%d-%d", offset, uint64(offset)+size-1)
|
|
|
|
if options != nil {
|
|
query = addTimeout(query, options.Timeout)
|
|
headers = mergeHeaders(headers, headersFromStruct(*options))
|
|
}
|
|
uri := b.Container.bsc.client.getEndpoint(blobServiceName, b.buildPath(), query)
|
|
|
|
resp, err := b.Container.bsc.client.exec(http.MethodPut, uri, headers, nil, b.Container.bsc.auth)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return b.respondCreation(resp, BlobTypeBlock)
|
|
}
|
|
|
|
// PutBlockListOptions includes the options for a put block list operation
|
|
type PutBlockListOptions 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"`
|
|
}
|
|
|
|
// PutBlockList saves list of blocks to the specified block blob.
|
|
//
|
|
// See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/Put-Block-List
|
|
func (b *Blob) PutBlockList(blocks []Block, options *PutBlockListOptions) error {
|
|
params := url.Values{"comp": {"blocklist"}}
|
|
blockListXML := prepareBlockListRequest(blocks)
|
|
headers := b.Container.bsc.client.getStandardHeaders()
|
|
headers["Content-Length"] = fmt.Sprintf("%v", len(blockListXML))
|
|
headers = mergeHeaders(headers, headersFromStruct(b.Properties))
|
|
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, strings.NewReader(blockListXML), b.Container.bsc.auth)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer drainRespBody(resp)
|
|
return checkRespCode(resp, []int{http.StatusCreated})
|
|
}
|
|
|
|
// GetBlockListOptions includes the options for a get block list operation
|
|
type GetBlockListOptions struct {
|
|
Timeout uint
|
|
Snapshot *time.Time
|
|
LeaseID string `header:"x-ms-lease-id"`
|
|
RequestID string `header:"x-ms-client-request-id"`
|
|
}
|
|
|
|
// GetBlockList retrieves list of blocks in the specified block blob.
|
|
//
|
|
// See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/Get-Block-List
|
|
func (b *Blob) GetBlockList(blockType BlockListType, options *GetBlockListOptions) (BlockListResponse, error) {
|
|
params := url.Values{
|
|
"comp": {"blocklist"},
|
|
"blocklisttype": {string(blockType)},
|
|
}
|
|
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)
|
|
|
|
var out BlockListResponse
|
|
resp, err := b.Container.bsc.client.exec(http.MethodGet, uri, headers, nil, b.Container.bsc.auth)
|
|
if err != nil {
|
|
return out, err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
err = xmlUnmarshal(resp.Body, &out)
|
|
return out, err
|
|
}
|