301 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Go
		
	
	
			
		
		
	
	
			301 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
 | |
| }
 |